Skip to content

Commit 248ac3b

Browse files
committed
[feature] Implement try lock with timeout methods for MultiLock
1 parent 135ecba commit 248ac3b

8 files changed

Lines changed: 285 additions & 22 deletions

File tree

src/main/java/uk/ac/ic/doc/slurp/multilock/HierarchicalMultiLock.java

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package uk.ac.ic.doc.slurp.multilock;
22

33
import javax.annotation.Nullable;
4+
import java.util.concurrent.TimeUnit;
45

56
public class HierarchicalMultiLock extends MultiLock {
67

@@ -45,6 +46,35 @@ public boolean tryReadLock() {
4546
return locked;
4647
}
4748

49+
/**
50+
* @param timeout the time to wait for acquiring the locks. The actual time can be 2* this, as the
51+
* timeout is used twice, once for the parent and once for the node.
52+
* @param unit the time unit of the timeout argument
53+
*/
54+
@Override
55+
public boolean tryReadLock(final long timeout, final TimeUnit unit) throws InterruptedException {
56+
if (parent != null) {
57+
if(!parent.tryIntentionReadLock(timeout, unit)) {
58+
return false;
59+
}
60+
}
61+
62+
boolean locked = false;
63+
try {
64+
locked = super.tryReadLock(timeout, unit);
65+
} catch (final InterruptedException e) {
66+
if (parent != null) {
67+
parent.unlockIntentionRead();
68+
}
69+
throw e;
70+
} finally {
71+
if (!locked && parent != null) {
72+
parent.unlockIntentionRead();
73+
}
74+
}
75+
return locked;
76+
}
77+
4878
@Override
4979
public void readLockInterruptibly() throws InterruptedException {
5080
if (parent != null) {
@@ -88,6 +118,35 @@ public boolean tryWriteLock() {
88118
return locked;
89119
}
90120

121+
/**
122+
* @param timeout the time to wait for acquiring the locks. The actual time can be 2* this, as the
123+
* timeout is used twice, once for the parent and once for the node.
124+
* @param unit the time unit of the timeout argument
125+
*/
126+
@Override
127+
public boolean tryWriteLock(final long timeout, final TimeUnit unit) throws InterruptedException {
128+
if (parent != null) {
129+
if(!parent.tryIntentionWriteLock(timeout, unit)) {
130+
return false;
131+
}
132+
}
133+
134+
boolean locked = false;
135+
try {
136+
locked = super.tryWriteLock(timeout, unit);
137+
} catch (final InterruptedException e) {
138+
if (parent != null) {
139+
parent.unlockIntentionWrite();
140+
}
141+
throw e;
142+
} finally {
143+
if (!locked && parent != null) {
144+
parent.unlockIntentionWrite();
145+
}
146+
}
147+
return locked;
148+
}
149+
91150
@Override
92151
public void writeLockInterruptibly() throws InterruptedException {
93152
if (parent != null) {
@@ -131,6 +190,35 @@ public boolean tryIntentionReadLock() {
131190
return locked;
132191
}
133192

193+
/**
194+
* @param timeout the time to wait for acquiring the locks. The actual time can be 2* this, as the
195+
* timeout is used twice, once for the parent and once for the node.
196+
* @param unit the time unit of the timeout argument
197+
*/
198+
@Override
199+
public boolean tryIntentionReadLock(final long timeout, final TimeUnit unit) throws InterruptedException {
200+
if (parent != null) {
201+
if(!parent.tryIntentionReadLock(timeout, unit)) {
202+
return false;
203+
}
204+
}
205+
206+
boolean locked = false;
207+
try {
208+
locked = super.tryIntentionReadLock(timeout, unit);
209+
} catch (final InterruptedException e) {
210+
if (parent != null) {
211+
parent.unlockIntentionRead();
212+
}
213+
throw e;
214+
} finally {
215+
if (!locked && parent != null) {
216+
parent.unlockIntentionRead();
217+
}
218+
}
219+
return locked;
220+
}
221+
134222
@Override
135223
public void intentionReadLockInterruptibly() throws InterruptedException {
136224
if (parent != null) {
@@ -174,6 +262,35 @@ public boolean tryIntentionWriteLock() {
174262
return locked;
175263
}
176264

265+
/**
266+
* @param timeout the time to wait for acquiring the locks. The actual time can be 2* this, as the
267+
* timeout is used twice, once for the parent and once for the node.
268+
* @param unit the time unit of the timeout argument
269+
*/
270+
@Override
271+
public boolean tryIntentionWriteLock(final long timeout, final TimeUnit unit) throws InterruptedException {
272+
if (parent != null) {
273+
if(!parent.tryIntentionWriteLock(timeout, unit)) {
274+
return false;
275+
}
276+
}
277+
278+
boolean locked = false;
279+
try {
280+
locked = super.tryIntentionWriteLock(timeout, unit);
281+
} catch (final InterruptedException e) {
282+
if (parent != null) {
283+
parent.unlockIntentionWrite();
284+
}
285+
throw e;
286+
} finally {
287+
if (!locked && parent != null) {
288+
parent.unlockIntentionWrite();
289+
}
290+
}
291+
return locked;
292+
}
293+
177294
@Override
178295
public void intentionWriteLockInterruptibly() throws InterruptedException {
179296
if (parent != null) {

src/main/java/uk/ac/ic/doc/slurp/multilock/LockMode.java

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package uk.ac.ic.doc.slurp.multilock;
22

3+
import java.util.concurrent.TimeUnit;
4+
35
/**
46
* An Enumeration of the MultiLock modes.
57
*/
@@ -49,6 +51,22 @@ public boolean tryLock(final MultiLock multiLock) {
4951
return tryLock(multiLock, this);
5052
}
5153

54+
/**
55+
* Attempts to lock the MultiLock with this mode.
56+
*
57+
* @param multiLock the MultiLock object.
58+
* @param timeout the time to wait for acquiring the lock.
59+
* @param unit the time unit of the timeout argument.
60+
*
61+
* @return true if the lock was acquired, false otherwise.
62+
*
63+
* @throws InterruptedException if the thread was interrupted
64+
*/
65+
public boolean tryLock(final MultiLock multiLock, final long timeout, final TimeUnit unit)
66+
throws InterruptedException {
67+
return tryLock(multiLock, this, timeout, unit);
68+
}
69+
5270
/**
5371
* Locks the MultiLock with this mode, aborting if interrupted.
5472
*
@@ -147,6 +165,54 @@ public static boolean tryLock(final MultiLock multiLock, final LockMode lockMode
147165
}
148166
}
149167

168+
/**
169+
* Attempts to lock the MultiLock with the provided mode.
170+
*
171+
* @param multiLock the MultiLock object.
172+
* @param lockMode the mode to lock the MultiLock.
173+
* @param timeout the time to wait for acquiring the lock.
174+
* @param unit the time unit of the timeout argument.
175+
*
176+
* @return true if the lock was acquired.
177+
*
178+
* @throws IllegalArgumentException if an unknown mode is provided.
179+
* @throws InterruptedException if the thread was interrupted
180+
*/
181+
public static boolean tryLock(final MultiLock multiLock, final LockMode lockMode, final long timeout,
182+
final TimeUnit unit) throws InterruptedException {
183+
switch (lockMode) {
184+
case IS:
185+
return multiLock.tryIntentionReadLock(timeout, unit);
186+
187+
case IX:
188+
return multiLock.tryIntentionWriteLock(timeout, unit);
189+
190+
case S:
191+
return multiLock.tryReadLock(timeout, unit);
192+
193+
case SIX:
194+
if (!multiLock.tryReadLock(timeout, unit)) {
195+
return false;
196+
}
197+
try {
198+
if (!multiLock.tryIntentionWriteLock(timeout, unit)) {
199+
multiLock.unlockRead();
200+
return false;
201+
}
202+
} catch (final InterruptedException e) {
203+
multiLock.unlockRead();
204+
throw e;
205+
}
206+
return true;
207+
208+
case X:
209+
return multiLock.tryWriteLock(timeout, unit);
210+
211+
default:
212+
throw new IllegalArgumentException("Unknown lock mode: " + lockMode);
213+
}
214+
}
215+
150216
/**
151217
* Locks the MultiLock with the provided mode, aborting if interrupted.
152218
*

src/main/java/uk/ac/ic/doc/slurp/multilock/MultiLock.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ public boolean tryLock() {
9292
@Override
9393
public boolean tryLock(final long time, final TimeUnit unit)
9494
throws InterruptedException {
95-
throw new UnsupportedOperationException();
95+
return tryReadLock(time, unit);
9696
}
9797
}
9898

@@ -126,7 +126,7 @@ public boolean tryLock() {
126126
@Override
127127
public boolean tryLock(final long time, final TimeUnit unit)
128128
throws InterruptedException {
129-
throw new UnsupportedOperationException();
129+
return tryWriteLock(time, unit);
130130
}
131131
}
132132

@@ -464,6 +464,10 @@ public boolean tryReadLock() {
464464
return sync.tryAcquireShared(S_UNIT) >= 0;
465465
}
466466

467+
public boolean tryReadLock(final long timeout, final TimeUnit unit) throws InterruptedException {
468+
return sync.tryAcquireSharedNanos(S_UNIT, unit.toNanos(timeout));
469+
}
470+
467471
public void readLockInterruptibly() throws InterruptedException {
468472
sync.acquireSharedInterruptibly(S_UNIT);
469473
}
@@ -476,6 +480,10 @@ public boolean tryWriteLock() {
476480
return sync.tryAcquire(X_UNIT);
477481
}
478482

483+
public boolean tryWriteLock(final long timeout, final TimeUnit unit) throws InterruptedException {
484+
return sync.tryAcquireNanos(X_UNIT, unit.toNanos(timeout));
485+
}
486+
479487
public void writeLockInterruptibly() throws InterruptedException {
480488
sync.acquireInterruptibly(X_UNIT);
481489
}
@@ -488,6 +496,10 @@ public boolean tryIntentionReadLock() {
488496
return sync.tryAcquireShared(IS_UNIT) >= 0;
489497
}
490498

499+
public boolean tryIntentionReadLock(final long timeout, final TimeUnit unit) throws InterruptedException {
500+
return sync.tryAcquireSharedNanos(IS_UNIT, unit.toNanos(timeout));
501+
}
502+
491503
public void intentionReadLockInterruptibly() throws InterruptedException {
492504
sync.acquireSharedInterruptibly(IS_UNIT);
493505
}
@@ -500,6 +512,10 @@ public boolean tryIntentionWriteLock() {
500512
return sync.tryAcquireShared(IX_UNIT) >= 0;
501513
}
502514

515+
public boolean tryIntentionWriteLock(final long timeout, final TimeUnit unit) throws InterruptedException {
516+
return sync.tryAcquireSharedNanos(IX_UNIT, unit.toNanos(timeout));
517+
}
518+
503519
public void intentionWriteLockInterruptibly() throws InterruptedException {
504520
sync.acquireSharedInterruptibly(IX_UNIT);
505521
}

src/test/java/uk/ac/ic/doc/slurp/multilock/CompatibilityTest.java

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
import static org.junit.jupiter.api.Assertions.assertFalse;
1313
import static org.junit.jupiter.api.Assertions.assertTrue;
14+
import static uk.ac.ic.doc.slurp.multilock.Constants.LOCK_ACQUISITION_TIMEOUT;
1415
import static uk.ac.ic.doc.slurp.multilock.LockMode.*;
1516

1617
/**
@@ -40,9 +41,6 @@
4041
*/
4142
public class CompatibilityTest {
4243

43-
// TODO(AR) this might need to be longer on slower machines...
44-
private static final long LOCK_ACQUISITION_TIMEOUT = 20;
45-
4644
static List<Arguments> compatibleModesProvider() {
4745
return Arrays.asList(
4846
Arguments.of(IS, IS, true),
@@ -113,6 +111,31 @@ public void compatibleTry(final LockMode mode1, final LockMode mode2, final bool
113111
}
114112
}
115113

114+
@ParameterizedTest(name = "{0} and {1}")
115+
@DisplayName("Compatible Modes Try (short timeout)")
116+
@MethodSource("compatibleModesProvider")
117+
public void compatibleTryShortTimeout(final LockMode mode1, final LockMode mode2, final boolean compatible)
118+
throws InterruptedException, ExecutionException {
119+
if (compatible) {
120+
assertCompatible(mode1, mode2, (mode, multiLock) -> mode.tryLock(multiLock, LOCK_ACQUISITION_TIMEOUT / 2, TimeUnit.MILLISECONDS));
121+
} else {
122+
assertNotCompatible(mode1, mode2, (mode, multiLock) -> mode.tryLock(multiLock, LOCK_ACQUISITION_TIMEOUT / 2, TimeUnit.MILLISECONDS), false);
123+
}
124+
}
125+
126+
@ParameterizedTest(name = "{0} and {1}")
127+
@DisplayName("Compatible Modes Try (long timeout)")
128+
@MethodSource("compatibleModesProvider")
129+
public void compatibleTryLongTimeout(final LockMode mode1, final LockMode mode2, final boolean compatible)
130+
throws InterruptedException, ExecutionException {
131+
if (compatible) {
132+
assertCompatible(mode1, mode2, (mode, multiLock) -> mode.tryLock(multiLock, LOCK_ACQUISITION_TIMEOUT * 2, TimeUnit.MILLISECONDS));
133+
} else {
134+
// NOTE: we set the blockingAcquisition=true parameter, as the timeout is greater than the ExecutorService's LOCK_ACQUISITION_TIMEOUT
135+
assertNotCompatible(mode1, mode2, (mode, multiLock) -> mode.tryLock(multiLock, LOCK_ACQUISITION_TIMEOUT * 2, TimeUnit.MILLISECONDS), true);
136+
}
137+
}
138+
116139
/**
117140
* Assert that two lock modes are compatible.
118141
*
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package uk.ac.ic.doc.slurp.multilock;
2+
3+
public interface Constants {
4+
5+
// TODO(AR) this might need to be longer on slower machines...
6+
long LOCK_ACQUISITION_TIMEOUT = 40;
7+
}

0 commit comments

Comments
 (0)