Skip to content

Commit 049bb42

Browse files
bharatviswa504anuengineer
authored andcommitted
HDDS-1723. Create new OzoneManagerLock class. (#1006)
* HDDS-1723. Create new OzoneManagerLock class. * add logs * fix exception message * add license * fix findbug * throw exception when acquiring 2nd lock failed. * fix review comments * fix missed review comment * fix checkstyle * fix review comments
1 parent ab0b180 commit 049bb42

File tree

7 files changed

+838
-2
lines changed

7 files changed

+838
-2
lines changed

hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConsts.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ public static Versioning getVersioning(boolean versioning) {
170170
public static final String OM_USER_PREFIX = "$";
171171
public static final String OM_S3_PREFIX ="S3:";
172172
public static final String OM_S3_VOLUME_PREFIX = "s3";
173+
public static final String OM_S3_SECRET = "S3Secret:";
173174

174175
/**
175176
* Max chunk size limit.

hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/lock/LockManager.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,10 @@ public void unlock(T resource) {
8383
ActiveLock lock = activeLocks.get(resource);
8484
if (lock == null) {
8585
// Someone is releasing a lock which was never acquired. Log and return.
86-
LOG.warn("Trying to release the lock on {}, which was never acquired.",
86+
LOG.error("Trying to release the lock on {}, which was never acquired.",
8787
resource);
88-
return;
88+
throw new IllegalMonitorStateException("Releasing lock on resource "
89+
+ resource + " without acquiring lock");
8990
}
9091
lock.unlock();
9192
activeLocks.computeIfPresent(resource, (k, v) -> {
Lines changed: 343 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,343 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
package org.apache.hadoop.ozone.om.lock;
20+
21+
22+
import java.util.ArrayList;
23+
import java.util.List;
24+
25+
import org.slf4j.Logger;
26+
import org.slf4j.LoggerFactory;
27+
28+
import org.apache.hadoop.conf.Configuration;
29+
import org.apache.hadoop.ozone.lock.LockManager;
30+
31+
/**
32+
* Provides different locks to handle concurrency in OzoneMaster.
33+
* We also maintain lock hierarchy, based on the weight.
34+
*
35+
* <table>
36+
* <caption></caption>
37+
* <tr>
38+
* <td><b> WEIGHT </b></td> <td><b> LOCK </b></td>
39+
* </tr>
40+
* <tr>
41+
* <td> 0 </td> <td> S3 Bucket Lock </td>
42+
* </tr>
43+
* <tr>
44+
* <td> 1 </td> <td> Volume Lock </td>
45+
* </tr>
46+
* <tr>
47+
* <td> 2 </td> <td> Bucket Lock </td>
48+
* </tr>
49+
* <tr>
50+
* <td> 3 </td> <td> User Lock </td>
51+
* </tr>
52+
* <tr>
53+
* <td> 4 </td> <td> S3 Secret Lock</td>
54+
* </tr>
55+
* <tr>
56+
* <td> 5 </td> <td> Prefix Lock </td>
57+
* </tr>
58+
* </table>
59+
*
60+
* One cannot obtain a lower weight lock while holding a lock with higher
61+
* weight. The other way around is possible. <br>
62+
* <br>
63+
* <p>
64+
* For example:
65+
* <br>
66+
* {@literal ->} acquire volume lock (will work)<br>
67+
* {@literal +->} acquire bucket lock (will work)<br>
68+
* {@literal +-->} acquire s3 bucket lock (will throw Exception)<br>
69+
* </p>
70+
* <br>
71+
*/
72+
73+
public class OzoneManagerLock {
74+
75+
private static final Logger LOG =
76+
LoggerFactory.getLogger(OzoneManagerLock.class);
77+
78+
private final LockManager<String> manager;
79+
private final ThreadLocal<Short> lockSet = ThreadLocal.withInitial(
80+
() -> Short.valueOf((short)0));
81+
82+
83+
/**
84+
* Creates new OzoneManagerLock instance.
85+
* @param conf Configuration object
86+
*/
87+
public OzoneManagerLock(Configuration conf) {
88+
manager = new LockManager<>(conf);
89+
}
90+
91+
/**
92+
* Acquire lock on resource.
93+
*
94+
* For S3_Bucket, VOLUME, BUCKET type resource, same thread acquiring lock
95+
* again is allowed.
96+
*
97+
* For USER, PREFIX, S3_SECRET type resource, same thread acquiring lock
98+
* again is not allowed.
99+
*
100+
* Special Note for UserLock: Single thread can acquire single user lock/
101+
* multi user lock. But not both at the same time.
102+
* @param resourceName - Resource name on which user want to acquire lock.
103+
* @param resource - Type of the resource.
104+
*/
105+
public void acquireLock(String resourceName, Resource resource) {
106+
if (!resource.canLock(lockSet.get())) {
107+
String errorMessage = getErrorMessage(resource);
108+
LOG.error(errorMessage);
109+
throw new RuntimeException(errorMessage);
110+
} else {
111+
manager.lock(resourceName);
112+
LOG.debug("Acquired {} lock on resource {}", resource.name,
113+
resourceName);
114+
lockSet.set(resource.setLock(lockSet.get()));
115+
}
116+
}
117+
118+
private String getErrorMessage(Resource resource) {
119+
return "Thread '" + Thread.currentThread().getName() + "' cannot " +
120+
"acquire " + resource.name + " lock while holding " +
121+
getCurrentLocks().toString() + " lock(s).";
122+
123+
}
124+
125+
private List<String> getCurrentLocks() {
126+
List<String> currentLocks = new ArrayList<>();
127+
int i=0;
128+
short lockSetVal = lockSet.get();
129+
for (Resource value : Resource.values()) {
130+
if (value.isLevelLocked(lockSetVal)) {
131+
currentLocks.add(value.getName());
132+
}
133+
}
134+
return currentLocks;
135+
}
136+
137+
/**
138+
* Acquire lock on multiple users.
139+
* @param firstUser
140+
* @param secondUser
141+
*/
142+
public void acquireMultiUserLock(String firstUser, String secondUser) {
143+
Resource resource = Resource.USER;
144+
if (!resource.canLock(lockSet.get())) {
145+
String errorMessage = getErrorMessage(resource);
146+
LOG.error(errorMessage);
147+
throw new RuntimeException(errorMessage);
148+
} else {
149+
// When acquiring multiple user locks, the reason for doing lexical
150+
// order comparision is to avoid deadlock scenario.
151+
152+
// Example: 1st thread acquire lock(ozone, hdfs)
153+
// 2nd thread acquire lock(hdfs, ozone).
154+
// If we don't acquire user locks in an order, there can be a deadlock.
155+
// 1st thread acquired lock on ozone, waiting for lock on hdfs, 2nd
156+
// thread acquired lock on hdfs, waiting for lock on ozone.
157+
// To avoid this when we acquire lock on multiple users, we acquire
158+
// locks in lexical order, which can help us to avoid dead locks.
159+
// Now if first thread acquires lock on hdfs, 2nd thread wait for lock
160+
// on hdfs, and first thread acquires lock on ozone. Once after first
161+
// thread releases user locks, 2nd thread acquires them.
162+
163+
int compare = firstUser.compareTo(secondUser);
164+
String temp;
165+
166+
// Order the user names in sorted order. Swap them.
167+
if (compare > 0) {
168+
temp = secondUser;
169+
secondUser = firstUser;
170+
firstUser = temp;
171+
}
172+
173+
if (compare == 0) {
174+
// both users are equal.
175+
manager.lock(firstUser);
176+
} else {
177+
manager.lock(firstUser);
178+
try {
179+
manager.lock(secondUser);
180+
} catch (Exception ex) {
181+
// We got an exception acquiring 2nd user lock. Release already
182+
// acquired user lock, and throw exception to the user.
183+
manager.unlock(firstUser);
184+
throw ex;
185+
}
186+
}
187+
LOG.debug("Acquired {} lock on resource {} and {}", resource.name,
188+
firstUser, secondUser);
189+
lockSet.set(resource.setLock(lockSet.get()));
190+
}
191+
}
192+
193+
194+
195+
/**
196+
* Release lock on multiple users.
197+
* @param firstUser
198+
* @param secondUser
199+
*/
200+
public void releaseMultiUserLock(String firstUser, String secondUser) {
201+
Resource resource = Resource.USER;
202+
int compare = firstUser.compareTo(secondUser);
203+
204+
String temp;
205+
206+
// Order the user names in sorted order. Swap them.
207+
if (compare > 0) {
208+
temp = secondUser;
209+
secondUser = firstUser;
210+
firstUser = temp;
211+
}
212+
213+
if (compare == 0) {
214+
// both users are equal.
215+
manager.unlock(firstUser);
216+
} else {
217+
manager.unlock(firstUser);
218+
manager.unlock(secondUser);
219+
}
220+
LOG.debug("Release {} lock on resource {} and {}", resource.name,
221+
firstUser, secondUser);
222+
lockSet.set(resource.clearLock(lockSet.get()));
223+
}
224+
225+
226+
public void releaseLock(String resourceName, Resource resource) {
227+
228+
// TODO: Not checking release of higher order level lock happened while
229+
// releasing lower order level lock, as for that we need counter for
230+
// locks, as some locks support acquiring lock again.
231+
manager.unlock(resourceName);
232+
// clear lock
233+
LOG.debug("Release {}, lock on resource {}", resource.name,
234+
resource.name, resourceName);
235+
lockSet.set(resource.clearLock(lockSet.get()));
236+
237+
}
238+
239+
/**
240+
* Resource defined in Ozone.
241+
*/
242+
public enum Resource {
243+
// For S3 Bucket need to allow only for S3, that should be means only 1.
244+
S3_BUCKET((byte) 0, "S3_BUCKET"), // = 1
245+
246+
// For volume need to allow both s3 bucket and volume. 01 + 10 = 11 (3)
247+
VOLUME((byte) 1, "VOLUME"), // = 2
248+
249+
// For bucket we need to allow both s3 bucket, volume and bucket. Which
250+
// is equal to 100 + 010 + 001 = 111 = 4 + 2 + 1 = 7
251+
BUCKET((byte) 2, "BUCKET"), // = 4
252+
253+
// For user we need to allow s3 bucket, volume, bucket and user lock.
254+
// Which is 8 4 + 2 + 1 = 15
255+
USER((byte) 3, "USER"), // 15
256+
257+
S3_SECRET((byte) 4, "S3_SECRET"), // 31
258+
PREFIX((byte) 5, "PREFIX"); //63
259+
260+
// level of the resource
261+
private byte lockLevel;
262+
263+
// This will tell the value, till which we can allow locking.
264+
private short mask;
265+
266+
// This value will help during setLock, and also will tell whether we can
267+
// re-acquire lock or not.
268+
private short setMask;
269+
270+
// Name of the resource.
271+
private String name;
272+
273+
Resource(byte pos, String name) {
274+
this.lockLevel = pos;
275+
this.mask = (short) (Math.pow(2, lockLevel + 1) - 1);
276+
this.setMask = (short) Math.pow(2, lockLevel);
277+
this.name = name;
278+
}
279+
280+
boolean canLock(short lockSetVal) {
281+
282+
// For USER, S3_SECRET and PREFIX we shall not allow re-acquire locks at
283+
// from single thread. 2nd condition is we have acquired one of these
284+
// locks, but after that trying to acquire a lock with less than equal of
285+
// lockLevel, we should disallow.
286+
if (((USER.setMask & lockSetVal) == USER.setMask ||
287+
(S3_SECRET.setMask & lockSetVal) == S3_SECRET.setMask ||
288+
(PREFIX.setMask & lockSetVal) == PREFIX.setMask)
289+
&& setMask <= lockSetVal) {
290+
return false;
291+
}
292+
293+
294+
// Our mask is the summation of bits of all previous possible locks. In
295+
// other words it is the largest possible value for that bit position.
296+
297+
// For example for Volume lock, bit position is 1, and mask is 3. Which
298+
// is the largest value that can be represented with 2 bits is 3.
299+
// Therefore if lockSet is larger than mask we have to return false i.e
300+
// some other higher order lock has been acquired.
301+
302+
return lockSetVal <= mask;
303+
}
304+
305+
/**
306+
* Set Lock bits in lockSetVal.
307+
*
308+
* @param lockSetVal
309+
* @return Updated value which has set lock bits.
310+
*/
311+
short setLock(short lockSetVal) {
312+
return (short) (lockSetVal | setMask);
313+
}
314+
315+
/**
316+
* Clear lock from lockSetVal.
317+
*
318+
* @param lockSetVal
319+
* @return Updated value which has cleared lock bits.
320+
*/
321+
short clearLock(short lockSetVal) {
322+
return (short) (lockSetVal & ~setMask);
323+
}
324+
325+
/**
326+
* Return true, if this level is locked, else false.
327+
* @param lockSetVal
328+
*/
329+
boolean isLevelLocked(short lockSetVal) {
330+
return (lockSetVal & setMask) == setMask;
331+
}
332+
333+
String getName() {
334+
return name;
335+
}
336+
337+
short getMask() {
338+
return mask;
339+
}
340+
}
341+
342+
}
343+

0 commit comments

Comments
 (0)