--- jsr166/src/test/tck/StampedLockTest.java 2016/06/07 07:20:09 1.20 +++ jsr166/src/test/tck/StampedLockTest.java 2020/08/12 16:15:28 1.46 @@ -5,11 +5,28 @@ * http://creativecommons.org/publicdomain/zero/1.0/ */ +import static java.util.concurrent.TimeUnit.DAYS; import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.locks.StampedLock.isLockStamp; +import static java.util.concurrent.locks.StampedLock.isOptimisticReadStamp; +import static java.util.concurrent.locks.StampedLock.isReadLockStamp; +import static java.util.concurrent.locks.StampedLock.isWriteLockStamp; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.StampedLock; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; import junit.framework.Test; import junit.framework.TestSuite; @@ -23,207 +40,183 @@ public class StampedLockTest extends JSR } /** - * A runnable calling writeLockInterruptibly - */ - class InterruptibleLockRunnable extends CheckedRunnable { - final StampedLock lock; - InterruptibleLockRunnable(StampedLock l) { lock = l; } - public void realRun() throws InterruptedException { - lock.writeLockInterruptibly(); - } - } - - /** - * A runnable calling writeLockInterruptibly that expects to be - * interrupted - */ - class InterruptedLockRunnable extends CheckedInterruptedRunnable { - final StampedLock lock; - InterruptedLockRunnable(StampedLock l) { lock = l; } - public void realRun() throws InterruptedException { - lock.writeLockInterruptibly(); - } - } - - /** * Releases write lock, checking isWriteLocked before and after */ - void releaseWriteLock(StampedLock lock, long s) { + void releaseWriteLock(StampedLock lock, long stamp) { assertTrue(lock.isWriteLocked()); - lock.unlockWrite(s); + assertValid(lock, stamp); + lock.unlockWrite(stamp); assertFalse(lock.isWriteLocked()); + assertFalse(lock.validate(stamp)); } /** - * Constructed StampedLock is in unlocked state + * Releases read lock, checking isReadLocked before and after */ - public void testConstructor() { - StampedLock lock; - lock = new StampedLock(); - assertFalse(lock.isWriteLocked()); + void releaseReadLock(StampedLock lock, long stamp) { + assertTrue(lock.isReadLocked()); + assertValid(lock, stamp); + lock.unlockRead(stamp); assertFalse(lock.isReadLocked()); - assertEquals(lock.getReadLockCount(), 0); + assertTrue(lock.validate(stamp)); } - /** - * write-locking and read-locking an unlocked lock succeed - */ - public void testLock() { - StampedLock lock = new StampedLock(); - assertFalse(lock.isWriteLocked()); - assertFalse(lock.isReadLocked()); - assertEquals(lock.getReadLockCount(), 0); - long s = lock.writeLock(); - assertTrue(lock.isWriteLocked()); - assertFalse(lock.isReadLocked()); - assertEquals(lock.getReadLockCount(), 0); - lock.unlockWrite(s); - assertFalse(lock.isWriteLocked()); - assertFalse(lock.isReadLocked()); - assertEquals(lock.getReadLockCount(), 0); - long rs = lock.readLock(); - assertFalse(lock.isWriteLocked()); - assertTrue(lock.isReadLocked()); - assertEquals(lock.getReadLockCount(), 1); - lock.unlockRead(rs); - assertFalse(lock.isWriteLocked()); - assertFalse(lock.isReadLocked()); - assertEquals(lock.getReadLockCount(), 0); + long assertNonZero(long v) { + assertTrue(v != 0L); + return v; } - /** - * unlock releases either a read or write lock - */ - public void testUnlock() { - StampedLock lock = new StampedLock(); - assertFalse(lock.isWriteLocked()); - assertFalse(lock.isReadLocked()); - assertEquals(lock.getReadLockCount(), 0); - long s = lock.writeLock(); - assertTrue(lock.isWriteLocked()); - assertFalse(lock.isReadLocked()); - assertEquals(lock.getReadLockCount(), 0); - lock.unlock(s); - assertFalse(lock.isWriteLocked()); - assertFalse(lock.isReadLocked()); - assertEquals(lock.getReadLockCount(), 0); - long rs = lock.readLock(); - assertFalse(lock.isWriteLocked()); - assertTrue(lock.isReadLocked()); - assertEquals(lock.getReadLockCount(), 1); - lock.unlock(rs); - assertFalse(lock.isWriteLocked()); - assertFalse(lock.isReadLocked()); - assertEquals(lock.getReadLockCount(), 0); + long assertValid(StampedLock lock, long stamp) { + assertTrue(stamp != 0L); + assertTrue(lock.validate(stamp)); + return stamp; } - /** - * tryUnlockRead/Write succeeds if locked in associated mode else - * returns false - */ - public void testTryUnlock() { - StampedLock lock = new StampedLock(); - assertFalse(lock.isWriteLocked()); - assertFalse(lock.isReadLocked()); - assertEquals(lock.getReadLockCount(), 0); - long s = lock.writeLock(); - assertTrue(lock.isWriteLocked()); + void assertUnlocked(StampedLock lock) { assertFalse(lock.isReadLocked()); - assertEquals(lock.getReadLockCount(), 0); - assertFalse(lock.tryUnlockRead()); - assertTrue(lock.tryUnlockWrite()); - assertFalse(lock.tryUnlockWrite()); - assertFalse(lock.tryUnlockRead()); assertFalse(lock.isWriteLocked()); - assertFalse(lock.isReadLocked()); - assertEquals(lock.getReadLockCount(), 0); - long rs = lock.readLock(); - assertFalse(lock.isWriteLocked()); - assertTrue(lock.isReadLocked()); - assertEquals(lock.getReadLockCount(), 1); - assertFalse(lock.tryUnlockWrite()); - assertTrue(lock.tryUnlockRead()); - assertFalse(lock.tryUnlockRead()); - assertFalse(lock.tryUnlockWrite()); - assertFalse(lock.isWriteLocked()); - assertFalse(lock.isReadLocked()); - assertEquals(lock.getReadLockCount(), 0); + assertEquals(0, lock.getReadLockCount()); + assertValid(lock, lock.tryOptimisticRead()); } - /** - * write-unlocking an unlocked lock throws IllegalMonitorStateException - */ - public void testWriteUnlock_IMSE() { - StampedLock lock = new StampedLock(); - try { - lock.unlockWrite(0L); - shouldThrow(); - } catch (IllegalMonitorStateException success) {} + List lockLockers(Lock lock) { + return List.of( + () -> lock.lock(), + () -> lock.lockInterruptibly(), + () -> lock.tryLock(), + () -> lock.tryLock(Long.MIN_VALUE, DAYS), + () -> lock.tryLock(0L, DAYS), + () -> lock.tryLock(Long.MAX_VALUE, DAYS)); + } + + List> readLockers() { + return List.of( + sl -> sl.readLock(), + sl -> sl.tryReadLock(), + sl -> readLockInterruptiblyUninterrupted(sl), + sl -> tryReadLockUninterrupted(sl, Long.MIN_VALUE, DAYS), + sl -> tryReadLockUninterrupted(sl, 0L, DAYS), + sl -> sl.tryConvertToReadLock(sl.tryOptimisticRead())); + } + + List> readUnlockers() { + return List.of( + (sl, stamp) -> sl.unlockRead(stamp), + (sl, stamp) -> assertTrue(sl.tryUnlockRead()), + (sl, stamp) -> sl.asReadLock().unlock(), + (sl, stamp) -> sl.unlock(stamp), + (sl, stamp) -> assertValid(sl, sl.tryConvertToOptimisticRead(stamp))); + } + + List> writeLockers() { + return List.of( + sl -> sl.writeLock(), + sl -> sl.tryWriteLock(), + sl -> writeLockInterruptiblyUninterrupted(sl), + sl -> tryWriteLockUninterrupted(sl, Long.MIN_VALUE, DAYS), + sl -> tryWriteLockUninterrupted(sl, 0L, DAYS), + sl -> sl.tryConvertToWriteLock(sl.tryOptimisticRead())); + } + + List> writeUnlockers() { + return List.of( + (sl, stamp) -> sl.unlockWrite(stamp), + (sl, stamp) -> assertTrue(sl.tryUnlockWrite()), + (sl, stamp) -> sl.asWriteLock().unlock(), + (sl, stamp) -> sl.unlock(stamp), + (sl, stamp) -> assertValid(sl, sl.tryConvertToOptimisticRead(stamp))); } /** - * write-unlocking an unlocked lock throws IllegalMonitorStateException + * Constructed StampedLock is in unlocked state */ - public void testWriteUnlock_IMSE2() { - StampedLock lock = new StampedLock(); - long s = lock.writeLock(); - lock.unlockWrite(s); - try { - lock.unlockWrite(s); - shouldThrow(); - } catch (IllegalMonitorStateException success) {} + public void testConstructor() { + assertUnlocked(new StampedLock()); } /** - * write-unlocking after readlock throws IllegalMonitorStateException + * write-locking, then unlocking, an unlocked lock succeed */ - public void testWriteUnlock_IMSE3() { + public void testWriteLock_lockUnlock() { StampedLock lock = new StampedLock(); - long s = lock.readLock(); - try { - lock.unlockWrite(s); - shouldThrow(); - } catch (IllegalMonitorStateException success) {} + + for (Function writeLocker : writeLockers()) + for (BiConsumer writeUnlocker : writeUnlockers()) { + assertFalse(lock.isWriteLocked()); + assertFalse(lock.isReadLocked()); + assertEquals(0, lock.getReadLockCount()); + + long s = writeLocker.apply(lock); + assertValid(lock, s); + assertTrue(lock.isWriteLocked()); + assertFalse(lock.isReadLocked()); + assertEquals(0, lock.getReadLockCount()); + writeUnlocker.accept(lock, s); + assertUnlocked(lock); + } } /** - * read-unlocking an unlocked lock throws IllegalMonitorStateException + * read-locking, then unlocking, an unlocked lock succeed */ - public void testReadUnlock_IMSE() { + public void testReadLock_lockUnlock() { StampedLock lock = new StampedLock(); - long s = lock.readLock(); - lock.unlockRead(s); - try { - lock.unlockRead(s); - shouldThrow(); - } catch (IllegalMonitorStateException success) {} + + for (Function readLocker : readLockers()) + for (BiConsumer readUnlocker : readUnlockers()) { + long s = 42; + for (int i = 0; i < 2; i++) { + s = assertValid(lock, readLocker.apply(lock)); + assertFalse(lock.isWriteLocked()); + assertTrue(lock.isReadLocked()); + assertEquals(i + 1, lock.getReadLockCount()); + } + for (int i = 0; i < 2; i++) { + assertFalse(lock.isWriteLocked()); + assertTrue(lock.isReadLocked()); + assertEquals(2 - i, lock.getReadLockCount()); + readUnlocker.accept(lock, s); + } + assertUnlocked(lock); + } } /** - * read-unlocking an unlocked lock throws IllegalMonitorStateException + * tryUnlockWrite fails if not write locked */ - public void testReadUnlock_IMSE2() { + public void testTryUnlockWrite_failure() { StampedLock lock = new StampedLock(); - try { - lock.unlockRead(0L); - shouldThrow(); - } catch (IllegalMonitorStateException success) {} + assertFalse(lock.tryUnlockWrite()); + + for (Function readLocker : readLockers()) + for (BiConsumer readUnlocker : readUnlockers()) { + long s = assertValid(lock, readLocker.apply(lock)); + assertFalse(lock.tryUnlockWrite()); + assertTrue(lock.isReadLocked()); + readUnlocker.accept(lock, s); + assertUnlocked(lock); + } } /** - * read-unlocking after writeLock throws IllegalMonitorStateException + * tryUnlockRead fails if not read locked */ - public void testReadUnlock_IMSE3() { + public void testTryUnlockRead_failure() { StampedLock lock = new StampedLock(); - long s = lock.writeLock(); - try { - lock.unlockRead(s); - shouldThrow(); - } catch (IllegalMonitorStateException success) {} + assertFalse(lock.tryUnlockRead()); + + for (Function writeLocker : writeLockers()) + for (BiConsumer writeUnlocker : writeUnlockers()) { + long s = writeLocker.apply(lock); + assertFalse(lock.tryUnlockRead()); + assertTrue(lock.isWriteLocked()); + writeUnlocker.accept(lock, s); + assertUnlocked(lock); + } } /** - * validate(0) fails + * validate(0L) fails */ public void testValidate0() { StampedLock lock = new StampedLock(); @@ -231,29 +224,24 @@ public class StampedLockTest extends JSR } /** - * A stamp obtained from a successful lock operation validates + * A stamp obtained from a successful lock operation validates while the lock is held */ public void testValidate() throws InterruptedException { StampedLock lock = new StampedLock(); - long s = lock.writeLock(); - assertTrue(lock.validate(s)); - lock.unlockWrite(s); - s = lock.readLock(); - assertTrue(lock.validate(s)); - lock.unlockRead(s); - assertTrue((s = lock.tryWriteLock()) != 0L); - assertTrue(lock.validate(s)); - lock.unlockWrite(s); - assertTrue((s = lock.tryReadLock()) != 0L); - assertTrue(lock.validate(s)); - lock.unlockRead(s); - assertTrue((s = lock.tryWriteLock(100L, MILLISECONDS)) != 0L); - assertTrue(lock.validate(s)); - lock.unlockWrite(s); - assertTrue((s = lock.tryReadLock(100L, MILLISECONDS)) != 0L); - assertTrue(lock.validate(s)); - lock.unlockRead(s); - assertTrue((s = lock.tryOptimisticRead()) != 0L); + + for (Function readLocker : readLockers()) + for (BiConsumer readUnlocker : readUnlockers()) { + long s = assertNonZero(readLocker.apply(lock)); + assertTrue(lock.validate(s)); + readUnlocker.accept(lock, s); + } + + for (Function writeLocker : writeLockers()) + for (BiConsumer writeUnlocker : writeUnlockers()) { + long s = assertNonZero(writeLocker.apply(lock)); + assertTrue(lock.validate(s)); + writeUnlocker.accept(lock, s); + } } /** @@ -261,124 +249,193 @@ public class StampedLockTest extends JSR */ public void testValidate2() throws InterruptedException { StampedLock lock = new StampedLock(); - long s; - assertTrue((s = lock.writeLock()) != 0L); + long s = assertNonZero(lock.writeLock()); assertTrue(lock.validate(s)); assertFalse(lock.validate(lock.tryWriteLock())); - assertFalse(lock.validate(lock.tryWriteLock(10L, MILLISECONDS))); + assertFalse(lock.validate(lock.tryWriteLock(randomExpiredTimeout(), + randomTimeUnit()))); assertFalse(lock.validate(lock.tryReadLock())); - assertFalse(lock.validate(lock.tryReadLock(10L, MILLISECONDS))); + assertFalse(lock.validate(lock.tryWriteLock(randomExpiredTimeout(), + randomTimeUnit()))); assertFalse(lock.validate(lock.tryOptimisticRead())); lock.unlockWrite(s); } + void assertThrowInterruptedExceptionWhenPreInterrupted(Action[] actions) { + for (Action action : actions) { + Thread.currentThread().interrupt(); + try { + action.run(); + shouldThrow(); + } + catch (InterruptedException success) {} + catch (Throwable fail) { threadUnexpectedException(fail); } + assertFalse(Thread.interrupted()); + } + } + /** - * writeLockInterruptibly is interruptible + * interruptible operations throw InterruptedException when pre-interrupted */ - public void testWriteLockInterruptibly_Interruptible() - throws InterruptedException { - final CountDownLatch running = new CountDownLatch(1); + public void testInterruptibleOperationsThrowInterruptedExceptionWhenPreInterrupted() { final StampedLock lock = new StampedLock(); - long s = lock.writeLock(); - Thread t = newStartedThread(new CheckedInterruptedRunnable() { - public void realRun() throws InterruptedException { - running.countDown(); - lock.writeLockInterruptibly(); - }}); - running.await(); - waitForThreadToEnterWaitState(t, 100); - t.interrupt(); - awaitTermination(t); - releaseWriteLock(lock, s); + Action[] interruptibleLockActions = { + () -> lock.writeLockInterruptibly(), + () -> lock.tryWriteLock(Long.MIN_VALUE, DAYS), + () -> lock.tryWriteLock(Long.MAX_VALUE, DAYS), + () -> lock.readLockInterruptibly(), + () -> lock.tryReadLock(Long.MIN_VALUE, DAYS), + () -> lock.tryReadLock(Long.MAX_VALUE, DAYS), + () -> lock.asWriteLock().lockInterruptibly(), + () -> lock.asWriteLock().tryLock(0L, DAYS), + () -> lock.asWriteLock().tryLock(Long.MAX_VALUE, DAYS), + () -> lock.asReadLock().lockInterruptibly(), + () -> lock.asReadLock().tryLock(0L, DAYS), + () -> lock.asReadLock().tryLock(Long.MAX_VALUE, DAYS), + }; + shuffle(interruptibleLockActions); + + assertThrowInterruptedExceptionWhenPreInterrupted(interruptibleLockActions); + { + long s = lock.writeLock(); + assertThrowInterruptedExceptionWhenPreInterrupted(interruptibleLockActions); + lock.unlockWrite(s); + } + { + long s = lock.readLock(); + assertThrowInterruptedExceptionWhenPreInterrupted(interruptibleLockActions); + lock.unlockRead(s); + } + } + + void assertThrowInterruptedExceptionWhenInterrupted(Action[] actions) { + int n = actions.length; + Future[] futures = new Future[n]; + CountDownLatch threadsStarted = new CountDownLatch(n); + CountDownLatch done = new CountDownLatch(n); + + for (int i = 0; i < n; i++) { + Action action = actions[i]; + futures[i] = cachedThreadPool.submit(new CheckedRunnable() { + public void realRun() throws Throwable { + threadsStarted.countDown(); + try { + action.run(); + shouldThrow(); + } + catch (InterruptedException success) {} + catch (Throwable fail) { threadUnexpectedException(fail); } + assertFalse(Thread.interrupted()); + done.countDown(); + }}); + } + + await(threadsStarted); + assertEquals(n, done.getCount()); + for (Future future : futures) // Interrupt all the tasks + future.cancel(true); + await(done); } /** - * timed tryWriteLock is interruptible + * interruptible operations throw InterruptedException when write locked and interrupted */ - public void testWriteTryLock_Interruptible() throws InterruptedException { - final CountDownLatch running = new CountDownLatch(1); + public void testInterruptibleOperationsThrowInterruptedExceptionWriteLockedInterrupted() { final StampedLock lock = new StampedLock(); - long s = lock.writeLock(); - Thread t = newStartedThread(new CheckedInterruptedRunnable() { - public void realRun() throws InterruptedException { - running.countDown(); - lock.tryWriteLock(2 * LONG_DELAY_MS, MILLISECONDS); - }}); + long stamp = lock.writeLock(); - running.await(); - waitForThreadToEnterWaitState(t, 100); - t.interrupt(); - awaitTermination(t); - releaseWriteLock(lock, s); + Action[] interruptibleLockBlockingActions = { + () -> lock.writeLockInterruptibly(), + () -> lock.tryWriteLock(Long.MAX_VALUE, DAYS), + () -> lock.readLockInterruptibly(), + () -> lock.tryReadLock(Long.MAX_VALUE, DAYS), + () -> lock.asWriteLock().lockInterruptibly(), + () -> lock.asWriteLock().tryLock(Long.MAX_VALUE, DAYS), + () -> lock.asReadLock().lockInterruptibly(), + () -> lock.asReadLock().tryLock(Long.MAX_VALUE, DAYS), + }; + shuffle(interruptibleLockBlockingActions); + + assertThrowInterruptedExceptionWhenInterrupted(interruptibleLockBlockingActions); + + releaseWriteLock(lock, stamp); } /** - * readLockInterruptibly is interruptible + * interruptible operations throw InterruptedException when read locked and interrupted */ - public void testReadLockInterruptibly_Interruptible() - throws InterruptedException { - final CountDownLatch running = new CountDownLatch(1); + public void testInterruptibleOperationsThrowInterruptedExceptionReadLockedInterrupted() { final StampedLock lock = new StampedLock(); - long s = lock.writeLock(); - Thread t = newStartedThread(new CheckedInterruptedRunnable() { - public void realRun() throws InterruptedException { - running.countDown(); - lock.readLockInterruptibly(); - }}); + long stamp = lock.readLock(); - running.await(); - waitForThreadToEnterWaitState(t, 100); - t.interrupt(); - awaitTermination(t); - releaseWriteLock(lock, s); + Action[] interruptibleLockBlockingActions = { + () -> lock.writeLockInterruptibly(), + () -> lock.tryWriteLock(Long.MAX_VALUE, DAYS), + () -> lock.asWriteLock().lockInterruptibly(), + () -> lock.asWriteLock().tryLock(Long.MAX_VALUE, DAYS), + }; + shuffle(interruptibleLockBlockingActions); + + assertThrowInterruptedExceptionWhenInterrupted(interruptibleLockBlockingActions); + + releaseReadLock(lock, stamp); } /** - * timed tryReadLock is interruptible + * Non-interruptible operations ignore and preserve interrupt status */ - public void testReadTryLock_Interruptible() throws InterruptedException { - final CountDownLatch running = new CountDownLatch(1); + public void testNonInterruptibleOperationsIgnoreInterrupts() { final StampedLock lock = new StampedLock(); - long s = lock.writeLock(); - Thread t = newStartedThread(new CheckedInterruptedRunnable() { - public void realRun() throws InterruptedException { - running.countDown(); - lock.tryReadLock(2 * LONG_DELAY_MS, MILLISECONDS); - }}); + Thread.currentThread().interrupt(); - running.await(); - waitForThreadToEnterWaitState(t, 100); - t.interrupt(); - awaitTermination(t); - releaseWriteLock(lock, s); + for (BiConsumer readUnlocker : readUnlockers()) { + long s = assertValid(lock, lock.readLock()); + readUnlocker.accept(lock, s); + s = assertValid(lock, lock.tryReadLock()); + readUnlocker.accept(lock, s); + } + + lock.asReadLock().lock(); + lock.asReadLock().unlock(); + + for (BiConsumer writeUnlocker : writeUnlockers()) { + long s = assertValid(lock, lock.writeLock()); + writeUnlocker.accept(lock, s); + s = assertValid(lock, lock.tryWriteLock()); + writeUnlocker.accept(lock, s); + } + + lock.asWriteLock().lock(); + lock.asWriteLock().unlock(); + + assertTrue(Thread.interrupted()); } /** * tryWriteLock on an unlocked lock succeeds */ - public void testWriteTryLock() { + public void testTryWriteLock() { final StampedLock lock = new StampedLock(); long s = lock.tryWriteLock(); assertTrue(s != 0L); assertTrue(lock.isWriteLocked()); - long s2 = lock.tryWriteLock(); - assertEquals(s2, 0L); + assertEquals(0L, lock.tryWriteLock()); releaseWriteLock(lock, s); } /** * tryWriteLock fails if locked */ - public void testWriteTryLockWhenLocked() { + public void testTryWriteLockWhenLocked() { final StampedLock lock = new StampedLock(); long s = lock.writeLock(); Thread t = newStartedThread(new CheckedRunnable() { public void realRun() { - long ws = lock.tryWriteLock(); - assertTrue(ws == 0L); + assertEquals(0L, lock.tryWriteLock()); }}); + assertEquals(0L, lock.tryWriteLock()); awaitTermination(t); releaseWriteLock(lock, s); } @@ -386,15 +443,15 @@ public class StampedLockTest extends JSR /** * tryReadLock fails if write-locked */ - public void testReadTryLockWhenLocked() { + public void testTryReadLockWhenLocked() { final StampedLock lock = new StampedLock(); long s = lock.writeLock(); Thread t = newStartedThread(new CheckedRunnable() { public void realRun() { - long rs = lock.tryReadLock(); - assertEquals(rs, 0L); + assertEquals(0L, lock.tryReadLock()); }}); + assertEquals(0L, lock.tryReadLock()); awaitTermination(t); releaseWriteLock(lock, s); } @@ -408,13 +465,20 @@ public class StampedLockTest extends JSR Thread t = newStartedThread(new CheckedRunnable() { public void realRun() throws InterruptedException { long s2 = lock.tryReadLock(); - assertTrue(s2 != 0L); + assertValid(lock, s2); lock.unlockRead(s2); long s3 = lock.tryReadLock(LONG_DELAY_MS, MILLISECONDS); - assertTrue(s3 != 0L); + assertValid(lock, s3); lock.unlockRead(s3); long s4 = lock.readLock(); + assertValid(lock, s4); lock.unlockRead(s4); + lock.asReadLock().lock(); + lock.asReadLock().unlock(); + lock.asReadLock().lockInterruptibly(); + lock.asReadLock().unlock(); + lock.asReadLock().tryLock(Long.MIN_VALUE, DAYS); + lock.asReadLock().unlock(); }}); awaitTermination(t); @@ -422,29 +486,32 @@ public class StampedLockTest extends JSR } /** - * A writelock succeeds only after a reading thread unlocks + * writeLock() succeeds only after a reading thread unlocks */ public void testWriteAfterReadLock() throws InterruptedException { - final CountDownLatch running = new CountDownLatch(1); + final CountDownLatch aboutToLock = new CountDownLatch(1); final StampedLock lock = new StampedLock(); long rs = lock.readLock(); Thread t = newStartedThread(new CheckedRunnable() { public void realRun() { - running.countDown(); + aboutToLock.countDown(); long s = lock.writeLock(); + assertTrue(lock.isWriteLocked()); + assertFalse(lock.isReadLocked()); lock.unlockWrite(s); }}); - running.await(); - waitForThreadToEnterWaitState(t, 100); + await(aboutToLock); + assertThreadBlocks(t, Thread.State.WAITING); assertFalse(lock.isWriteLocked()); + assertTrue(lock.isReadLocked()); lock.unlockRead(rs); awaitTermination(t); - assertFalse(lock.isWriteLocked()); + assertUnlocked(lock); } /** - * A writelock succeeds only after reading threads unlock + * writeLock() succeeds only after reading threads unlock */ public void testWriteAfterMultipleReadLocks() { final StampedLock lock = new StampedLock(); @@ -463,36 +530,44 @@ public class StampedLockTest extends JSR lock.unlockWrite(ws); }}); + assertTrue(lock.isReadLocked()); assertFalse(lock.isWriteLocked()); lock.unlockRead(s); awaitTermination(t2); - assertFalse(lock.isWriteLocked()); + assertUnlocked(lock); } /** - * Readlocks succeed only after a writing thread unlocks + * readLock() succeed only after a writing thread unlocks */ public void testReadAfterWriteLock() { final StampedLock lock = new StampedLock(); + final CountDownLatch threadsStarted = new CountDownLatch(2); final long s = lock.writeLock(); - Thread t1 = newStartedThread(new CheckedRunnable() { - public void realRun() { - long rs = lock.readLock(); - lock.unlockRead(rs); - }}); - Thread t2 = newStartedThread(new CheckedRunnable() { + final Runnable acquireReleaseReadLock = new CheckedRunnable() { public void realRun() { + threadsStarted.countDown(); long rs = lock.readLock(); + assertTrue(lock.isReadLocked()); + assertFalse(lock.isWriteLocked()); lock.unlockRead(rs); - }}); - + }}; + Thread t1 = newStartedThread(acquireReleaseReadLock); + Thread t2 = newStartedThread(acquireReleaseReadLock); + + await(threadsStarted); + assertThreadBlocks(t1, Thread.State.WAITING); + assertThreadBlocks(t2, Thread.State.WAITING); + assertTrue(lock.isWriteLocked()); + assertFalse(lock.isReadLocked()); releaseWriteLock(lock, s); awaitTermination(t1); awaitTermination(t2); + assertUnlocked(lock); } /** - * tryReadLock succeeds if readlocked but not writelocked + * tryReadLock succeeds if read locked but not write locked */ public void testTryLockWhenReadLocked() { final StampedLock lock = new StampedLock(); @@ -500,7 +575,7 @@ public class StampedLockTest extends JSR Thread t = newStartedThread(new CheckedRunnable() { public void realRun() { long rs = lock.tryReadLock(); - threadAssertTrue(rs != 0L); + assertValid(lock, rs); lock.unlockRead(rs); }}); @@ -509,15 +584,14 @@ public class StampedLockTest extends JSR } /** - * tryWriteLock fails when readlocked + * tryWriteLock fails when read locked */ - public void testWriteTryLockWhenReadLocked() { + public void testTryWriteLockWhenReadLocked() { final StampedLock lock = new StampedLock(); long s = lock.readLock(); Thread t = newStartedThread(new CheckedRunnable() { public void realRun() { - long ws = lock.tryWriteLock(); - threadAssertEquals(ws, 0L); + assertEquals(0L, lock.tryWriteLock()); }}); awaitTermination(t); @@ -525,86 +599,82 @@ public class StampedLockTest extends JSR } /** - * timed tryWriteLock times out if locked + * timed lock operations time out if lock not available */ - public void testWriteTryLock_Timeout() { + public void testTimedLock_Timeout() throws Exception { + ArrayList> futures = new ArrayList<>(); + + // Write locked final StampedLock lock = new StampedLock(); - long s = lock.writeLock(); - Thread t = newStartedThread(new CheckedRunnable() { + long stamp = lock.writeLock(); + assertEquals(0L, lock.tryReadLock(0L, DAYS)); + assertEquals(0L, lock.tryReadLock(Long.MIN_VALUE, DAYS)); + assertFalse(lock.asReadLock().tryLock(0L, DAYS)); + assertFalse(lock.asReadLock().tryLock(Long.MIN_VALUE, DAYS)); + assertEquals(0L, lock.tryWriteLock(0L, DAYS)); + assertEquals(0L, lock.tryWriteLock(Long.MIN_VALUE, DAYS)); + assertFalse(lock.asWriteLock().tryLock(0L, DAYS)); + assertFalse(lock.asWriteLock().tryLock(Long.MIN_VALUE, DAYS)); + + futures.add(cachedThreadPool.submit(new CheckedRunnable() { public void realRun() throws InterruptedException { long startTime = System.nanoTime(); - long timeoutMillis = 10; - long ws = lock.tryWriteLock(timeoutMillis, MILLISECONDS); - assertEquals(ws, 0L); - assertTrue(millisElapsedSince(startTime) >= timeoutMillis); - }}); + assertEquals(0L, lock.tryWriteLock(timeoutMillis(), MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + }})); - awaitTermination(t); - releaseWriteLock(lock, s); - } + futures.add(cachedThreadPool.submit(new CheckedRunnable() { + public void realRun() throws InterruptedException { + long startTime = System.nanoTime(); + assertEquals(0L, lock.tryReadLock(timeoutMillis(), MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + }})); + + // Read locked + final StampedLock lock2 = new StampedLock(); + long stamp2 = lock2.readLock(); + assertEquals(0L, lock2.tryWriteLock(0L, DAYS)); + assertEquals(0L, lock2.tryWriteLock(Long.MIN_VALUE, DAYS)); + assertFalse(lock2.asWriteLock().tryLock(0L, DAYS)); + assertFalse(lock2.asWriteLock().tryLock(Long.MIN_VALUE, DAYS)); - /** - * timed tryReadLock times out if write-locked - */ - public void testReadTryLock_Timeout() { - final StampedLock lock = new StampedLock(); - long s = lock.writeLock(); - Thread t = newStartedThread(new CheckedRunnable() { + futures.add(cachedThreadPool.submit(new CheckedRunnable() { public void realRun() throws InterruptedException { long startTime = System.nanoTime(); - long timeoutMillis = 10; - long rs = lock.tryReadLock(timeoutMillis, MILLISECONDS); - assertEquals(rs, 0L); - assertTrue(millisElapsedSince(startTime) >= timeoutMillis); - }}); + assertEquals(0L, lock2.tryWriteLock(timeoutMillis(), MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis()); + }})); - awaitTermination(t); - assertTrue(lock.isWriteLocked()); - lock.unlockWrite(s); + for (Future future : futures) + assertNull(future.get()); + + releaseWriteLock(lock, stamp); + releaseReadLock(lock2, stamp2); } /** - * writeLockInterruptibly succeeds if unlocked, else is interruptible + * writeLockInterruptibly succeeds if unlocked */ public void testWriteLockInterruptibly() throws InterruptedException { - final CountDownLatch running = new CountDownLatch(1); final StampedLock lock = new StampedLock(); long s = lock.writeLockInterruptibly(); - Thread t = newStartedThread(new CheckedInterruptedRunnable() { - public void realRun() throws InterruptedException { - running.countDown(); - lock.writeLockInterruptibly(); - }}); - - running.await(); - waitForThreadToEnterWaitState(t, 100); - t.interrupt(); assertTrue(lock.isWriteLocked()); - awaitTermination(t); releaseWriteLock(lock, s); } /** - * readLockInterruptibly succeeds if lock free else is interruptible + * readLockInterruptibly succeeds if lock free */ public void testReadLockInterruptibly() throws InterruptedException { - final CountDownLatch running = new CountDownLatch(1); final StampedLock lock = new StampedLock(); - long s; - s = lock.readLockInterruptibly(); + + long s = assertValid(lock, lock.readLockInterruptibly()); + assertTrue(lock.isReadLocked()); lock.unlockRead(s); - s = lock.writeLockInterruptibly(); - Thread t = newStartedThread(new CheckedInterruptedRunnable() { - public void realRun() throws InterruptedException { - running.countDown(); - lock.readLockInterruptibly(); - }}); - running.await(); - waitForThreadToEnterWaitState(t, 100); - t.interrupt(); - awaitTermination(t); - releaseWriteLock(lock, s); + lock.asReadLock().lockInterruptibly(); + assertTrue(lock.isReadLocked()); + lock.asReadLock().unlock(); } /** @@ -633,57 +703,43 @@ public class StampedLockTest extends JSR lock.unlockWrite(s); s = lock.readLock(); assertTrue(lock.toString().contains("Read-locks")); + releaseReadLock(lock, s); } /** - * tryOptimisticRead succeeds and validates if unlocked, fails if locked + * tryOptimisticRead succeeds and validates if unlocked, fails if + * exclusively locked */ public void testValidateOptimistic() throws InterruptedException { StampedLock lock = new StampedLock(); - long s, p; - assertTrue((p = lock.tryOptimisticRead()) != 0L); - assertTrue(lock.validate(p)); - assertTrue((s = lock.writeLock()) != 0L); - assertFalse((p = lock.tryOptimisticRead()) != 0L); - assertTrue(lock.validate(s)); - lock.unlockWrite(s); - assertTrue((p = lock.tryOptimisticRead()) != 0L); - assertTrue(lock.validate(p)); - assertTrue((s = lock.readLock()) != 0L); - assertTrue(lock.validate(s)); - assertTrue((p = lock.tryOptimisticRead()) != 0L); - assertTrue(lock.validate(p)); - lock.unlockRead(s); - assertTrue((s = lock.tryWriteLock()) != 0L); - assertTrue(lock.validate(s)); - assertFalse((p = lock.tryOptimisticRead()) != 0L); - lock.unlockWrite(s); - assertTrue((s = lock.tryReadLock()) != 0L); - assertTrue(lock.validate(s)); - assertTrue((p = lock.tryOptimisticRead()) != 0L); - lock.unlockRead(s); - assertTrue(lock.validate(p)); - assertTrue((s = lock.tryWriteLock(100L, MILLISECONDS)) != 0L); - assertFalse((p = lock.tryOptimisticRead()) != 0L); - assertTrue(lock.validate(s)); - lock.unlockWrite(s); - assertTrue((s = lock.tryReadLock(100L, MILLISECONDS)) != 0L); - assertTrue(lock.validate(s)); - assertTrue((p = lock.tryOptimisticRead()) != 0L); - lock.unlockRead(s); - assertTrue((p = lock.tryOptimisticRead()) != 0L); + + assertValid(lock, lock.tryOptimisticRead()); + + for (Function writeLocker : writeLockers()) { + long s = assertValid(lock, writeLocker.apply(lock)); + assertEquals(0L, lock.tryOptimisticRead()); + releaseWriteLock(lock, s); + } + + for (Function readLocker : readLockers()) { + long s = assertValid(lock, readLocker.apply(lock)); + long p = assertValid(lock, lock.tryOptimisticRead()); + releaseReadLock(lock, s); + assertTrue(lock.validate(p)); + } + + assertValid(lock, lock.tryOptimisticRead()); } /** * tryOptimisticRead stamp does not validate if a write lock intervenes */ public void testValidateOptimisticWriteLocked() { - StampedLock lock = new StampedLock(); - long s, p; - assertTrue((p = lock.tryOptimisticRead()) != 0L); - assertTrue((s = lock.writeLock()) != 0L); + final StampedLock lock = new StampedLock(); + final long p = assertValid(lock, lock.tryOptimisticRead()); + final long s = assertValid(lock, lock.writeLock()); assertFalse(lock.validate(p)); - assertFalse((p = lock.tryOptimisticRead()) != 0L); + assertEquals(0L, lock.tryOptimisticRead()); assertTrue(lock.validate(s)); lock.unlockWrite(s); } @@ -694,199 +750,224 @@ public class StampedLockTest extends JSR */ public void testValidateOptimisticWriteLocked2() throws InterruptedException { - final CountDownLatch running = new CountDownLatch(1); + final CountDownLatch locked = new CountDownLatch(1); final StampedLock lock = new StampedLock(); - long s, p; - assertTrue((p = lock.tryOptimisticRead()) != 0L); + final long p = assertValid(lock, lock.tryOptimisticRead()); + Thread t = newStartedThread(new CheckedInterruptedRunnable() { public void realRun() throws InterruptedException { lock.writeLockInterruptibly(); - running.countDown(); + locked.countDown(); lock.writeLockInterruptibly(); }}); - running.await(); + await(locked); assertFalse(lock.validate(p)); - assertFalse((p = lock.tryOptimisticRead()) != 0L); + assertEquals(0L, lock.tryOptimisticRead()); + assertThreadBlocks(t, Thread.State.WAITING); t.interrupt(); awaitTermination(t); + assertTrue(lock.isWriteLocked()); } /** - * tryConvertToOptimisticRead succeeds and validates if successfully locked, + * tryConvertToOptimisticRead succeeds and validates if successfully locked */ public void testTryConvertToOptimisticRead() throws InterruptedException { StampedLock lock = new StampedLock(); - long s, p; + long s, p, q; assertEquals(0L, lock.tryConvertToOptimisticRead(0L)); - assertTrue((s = lock.tryOptimisticRead()) != 0L); - assertEquals(s, lock.tryConvertToOptimisticRead(s)); - assertTrue(lock.validate(s)); - - assertTrue((p = lock.readLock()) != 0L); - assertTrue((s = lock.tryOptimisticRead()) != 0L); + s = assertValid(lock, lock.tryOptimisticRead()); assertEquals(s, lock.tryConvertToOptimisticRead(s)); assertTrue(lock.validate(s)); - lock.unlockRead(p); - - assertTrue((s = lock.writeLock()) != 0L); - assertTrue((p = lock.tryConvertToOptimisticRead(s)) != 0L); - assertTrue(lock.validate(p)); - - assertTrue((s = lock.readLock()) != 0L); - assertTrue(lock.validate(s)); - assertTrue((p = lock.tryConvertToOptimisticRead(s)) != 0L); - assertTrue(lock.validate(p)); - - assertTrue((s = lock.tryWriteLock()) != 0L); - assertTrue(lock.validate(s)); - assertTrue((p = lock.tryConvertToOptimisticRead(s)) != 0L); - assertTrue(lock.validate(p)); - - assertTrue((s = lock.tryReadLock()) != 0L); - assertTrue(lock.validate(s)); - assertTrue((p = lock.tryConvertToOptimisticRead(s)) != 0L); - assertTrue(lock.validate(p)); - assertTrue((s = lock.tryWriteLock(100L, MILLISECONDS)) != 0L); - assertTrue((p = lock.tryConvertToOptimisticRead(s)) != 0L); - assertTrue(lock.validate(p)); + for (Function writeLocker : writeLockers()) { + s = assertValid(lock, writeLocker.apply(lock)); + p = assertValid(lock, lock.tryConvertToOptimisticRead(s)); + assertFalse(lock.validate(s)); + assertTrue(lock.validate(p)); + assertUnlocked(lock); + } - assertTrue((s = lock.tryReadLock(100L, MILLISECONDS)) != 0L); - assertTrue(lock.validate(s)); - assertTrue((p = lock.tryConvertToOptimisticRead(s)) != 0L); - assertTrue(lock.validate(p)); + for (Function readLocker : readLockers()) { + s = assertValid(lock, readLocker.apply(lock)); + q = assertValid(lock, lock.tryOptimisticRead()); + assertEquals(q, lock.tryConvertToOptimisticRead(q)); + assertTrue(lock.validate(q)); + assertTrue(lock.isReadLocked()); + p = assertValid(lock, lock.tryConvertToOptimisticRead(s)); + assertTrue(lock.validate(p)); + assertTrue(lock.validate(s)); + assertUnlocked(lock); + assertEquals(q, lock.tryConvertToOptimisticRead(q)); + assertTrue(lock.validate(q)); + } } /** - * tryConvertToReadLock succeeds and validates if successfully locked - * or lock free; + * tryConvertToReadLock succeeds for valid stamps */ public void testTryConvertToReadLock() throws InterruptedException { StampedLock lock = new StampedLock(); long s, p; - s = 0L; - assertFalse((p = lock.tryConvertToReadLock(s)) != 0L); - assertTrue((s = lock.tryOptimisticRead()) != 0L); - assertTrue((p = lock.tryConvertToReadLock(s)) != 0L); - lock.unlockRead(p); - assertTrue((s = lock.writeLock()) != 0L); - assertTrue((p = lock.tryConvertToReadLock(s)) != 0L); - assertTrue(lock.validate(p)); - lock.unlockRead(p); - assertTrue((s = lock.readLock()) != 0L); - assertTrue(lock.validate(s)); - assertTrue((p = lock.tryConvertToReadLock(s)) != 0L); - assertTrue(lock.validate(p)); - lock.unlockRead(p); - assertTrue((s = lock.tryWriteLock()) != 0L); - assertTrue(lock.validate(s)); - assertTrue((p = lock.tryConvertToReadLock(s)) != 0L); - assertTrue(lock.validate(p)); - lock.unlockRead(p); - assertTrue((s = lock.tryReadLock()) != 0L); + + assertEquals(0L, lock.tryConvertToReadLock(0L)); + + s = assertValid(lock, lock.tryOptimisticRead()); + p = assertValid(lock, lock.tryConvertToReadLock(s)); + assertTrue(lock.isReadLocked()); + assertEquals(1, lock.getReadLockCount()); assertTrue(lock.validate(s)); - assertTrue((p = lock.tryConvertToReadLock(s)) != 0L); - assertTrue(lock.validate(p)); lock.unlockRead(p); - assertTrue((s = lock.tryWriteLock(100L, MILLISECONDS)) != 0L); - assertTrue((p = lock.tryConvertToReadLock(s)) != 0L); - assertTrue(lock.validate(p)); + + s = assertValid(lock, lock.tryOptimisticRead()); + lock.readLock(); + p = assertValid(lock, lock.tryConvertToReadLock(s)); + assertTrue(lock.isReadLocked()); + assertEquals(2, lock.getReadLockCount()); lock.unlockRead(p); - assertTrue((s = lock.tryReadLock(100L, MILLISECONDS)) != 0L); - assertTrue(lock.validate(s)); - assertTrue((p = lock.tryConvertToReadLock(s)) != 0L); - assertTrue(lock.validate(p)); lock.unlockRead(p); + assertUnlocked(lock); + + for (BiConsumer readUnlocker : readUnlockers()) { + for (Function writeLocker : writeLockers()) { + s = assertValid(lock, writeLocker.apply(lock)); + p = assertValid(lock, lock.tryConvertToReadLock(s)); + assertFalse(lock.validate(s)); + assertTrue(lock.isReadLocked()); + assertEquals(1, lock.getReadLockCount()); + readUnlocker.accept(lock, p); + } + + for (Function readLocker : readLockers()) { + s = assertValid(lock, readLocker.apply(lock)); + assertEquals(s, lock.tryConvertToReadLock(s)); + assertTrue(lock.validate(s)); + assertTrue(lock.isReadLocked()); + assertEquals(1, lock.getReadLockCount()); + readUnlocker.accept(lock, s); + } + } } /** - * tryConvertToWriteLock succeeds and validates if successfully locked - * or lock free; + * tryConvertToWriteLock succeeds if lock available; fails if multiply read locked */ public void testTryConvertToWriteLock() throws InterruptedException { StampedLock lock = new StampedLock(); long s, p; - s = 0L; - assertFalse((p = lock.tryConvertToWriteLock(s)) != 0L); + + assertEquals(0L, lock.tryConvertToWriteLock(0L)); + assertTrue((s = lock.tryOptimisticRead()) != 0L); assertTrue((p = lock.tryConvertToWriteLock(s)) != 0L); + assertTrue(lock.isWriteLocked()); lock.unlockWrite(p); - assertTrue((s = lock.writeLock()) != 0L); - assertTrue((p = lock.tryConvertToWriteLock(s)) != 0L); - assertTrue(lock.validate(p)); - lock.unlockWrite(p); - assertTrue((s = lock.readLock()) != 0L); - assertTrue(lock.validate(s)); - assertTrue((p = lock.tryConvertToWriteLock(s)) != 0L); - assertTrue(lock.validate(p)); - lock.unlockWrite(p); - assertTrue((s = lock.tryWriteLock()) != 0L); - assertTrue(lock.validate(s)); - assertTrue((p = lock.tryConvertToWriteLock(s)) != 0L); - assertTrue(lock.validate(p)); - lock.unlockWrite(p); - assertTrue((s = lock.tryReadLock()) != 0L); - assertTrue(lock.validate(s)); - assertTrue((p = lock.tryConvertToWriteLock(s)) != 0L); - assertTrue(lock.validate(p)); - lock.unlockWrite(p); - assertTrue((s = lock.tryWriteLock(100L, MILLISECONDS)) != 0L); - assertTrue((p = lock.tryConvertToWriteLock(s)) != 0L); - assertTrue(lock.validate(p)); - lock.unlockWrite(p); - assertTrue((s = lock.tryReadLock(100L, MILLISECONDS)) != 0L); - assertTrue(lock.validate(s)); - assertTrue((p = lock.tryConvertToWriteLock(s)) != 0L); - assertTrue(lock.validate(p)); - lock.unlockWrite(p); + + for (BiConsumer writeUnlocker : writeUnlockers()) { + for (Function writeLocker : writeLockers()) { + s = assertValid(lock, writeLocker.apply(lock)); + assertEquals(s, lock.tryConvertToWriteLock(s)); + assertTrue(lock.validate(s)); + assertTrue(lock.isWriteLocked()); + writeUnlocker.accept(lock, s); + } + + for (Function readLocker : readLockers()) { + s = assertValid(lock, readLocker.apply(lock)); + p = assertValid(lock, lock.tryConvertToWriteLock(s)); + assertFalse(lock.validate(s)); + assertTrue(lock.validate(p)); + assertTrue(lock.isWriteLocked()); + writeUnlocker.accept(lock, p); + } + } + + // failure if multiply read locked + for (Function readLocker : readLockers()) { + s = assertValid(lock, readLocker.apply(lock)); + p = assertValid(lock, readLocker.apply(lock)); + assertEquals(0L, lock.tryConvertToWriteLock(s)); + assertTrue(lock.validate(s)); + assertTrue(lock.validate(p)); + assertEquals(2, lock.getReadLockCount()); + lock.unlock(p); + lock.unlock(s); + assertUnlocked(lock); + } } /** * asWriteLock can be locked and unlocked */ - public void testAsWriteLock() { + public void testAsWriteLock() throws Throwable { StampedLock sl = new StampedLock(); Lock lock = sl.asWriteLock(); - lock.lock(); - assertFalse(lock.tryLock()); - lock.unlock(); - assertTrue(lock.tryLock()); + for (Action locker : lockLockers(lock)) { + locker.run(); + assertTrue(sl.isWriteLocked()); + assertFalse(sl.isReadLocked()); + assertFalse(lock.tryLock()); + lock.unlock(); + assertUnlocked(sl); + } } /** * asReadLock can be locked and unlocked */ - public void testAsReadLock() { + public void testAsReadLock() throws Throwable { StampedLock sl = new StampedLock(); Lock lock = sl.asReadLock(); - lock.lock(); - lock.unlock(); - assertTrue(lock.tryLock()); + for (Action locker : lockLockers(lock)) { + locker.run(); + assertTrue(sl.isReadLocked()); + assertFalse(sl.isWriteLocked()); + assertEquals(1, sl.getReadLockCount()); + locker.run(); + assertTrue(sl.isReadLocked()); + assertEquals(2, sl.getReadLockCount()); + lock.unlock(); + lock.unlock(); + assertUnlocked(sl); + } } /** * asReadWriteLock.writeLock can be locked and unlocked */ - public void testAsReadWriteLockWriteLock() { + public void testAsReadWriteLockWriteLock() throws Throwable { StampedLock sl = new StampedLock(); Lock lock = sl.asReadWriteLock().writeLock(); - lock.lock(); - assertFalse(lock.tryLock()); - lock.unlock(); - assertTrue(lock.tryLock()); + for (Action locker : lockLockers(lock)) { + locker.run(); + assertTrue(sl.isWriteLocked()); + assertFalse(sl.isReadLocked()); + assertFalse(lock.tryLock()); + lock.unlock(); + assertUnlocked(sl); + } } /** * asReadWriteLock.readLock can be locked and unlocked */ - public void testAsReadWriteLockReadLock() { + public void testAsReadWriteLockReadLock() throws Throwable { StampedLock sl = new StampedLock(); Lock lock = sl.asReadWriteLock().readLock(); - lock.lock(); - lock.unlock(); - assertTrue(lock.tryLock()); + for (Action locker : lockLockers(lock)) { + locker.run(); + assertTrue(sl.isReadLocked()); + assertFalse(sl.isWriteLocked()); + assertEquals(1, sl.getReadLockCount()); + locker.run(); + assertTrue(sl.isReadLocked()); + assertEquals(2, sl.getReadLockCount()); + lock.unlock(); + lock.unlock(); + assertUnlocked(sl); + } } /** @@ -906,107 +987,517 @@ public class StampedLockTest extends JSR * IllegalMonitorStateException */ public void testCannotUnlockOptimisticReadStamps() { - Runnable[] actions = { - () -> { - StampedLock sl = new StampedLock(); - long stamp = sl.tryOptimisticRead(); - assertTrue(stamp != 0); - sl.unlockRead(stamp); - }, - () -> { - StampedLock sl = new StampedLock(); - long stamp = sl.tryOptimisticRead(); - sl.unlock(stamp); - }, + { + StampedLock sl = new StampedLock(); + long stamp = assertValid(sl, sl.tryOptimisticRead()); + assertThrows(IllegalMonitorStateException.class, + () -> sl.unlockRead(stamp)); + } + { + StampedLock sl = new StampedLock(); + long stamp = sl.tryOptimisticRead(); + assertThrows(IllegalMonitorStateException.class, + () -> sl.unlock(stamp)); + } - () -> { - StampedLock sl = new StampedLock(); - long stamp = sl.tryOptimisticRead(); - sl.writeLock(); - sl.unlock(stamp); - }, - () -> { - StampedLock sl = new StampedLock(); + { + StampedLock sl = new StampedLock(); + long stamp = sl.tryOptimisticRead(); + sl.writeLock(); + assertThrows(IllegalMonitorStateException.class, + () -> sl.unlock(stamp)); + } + { + StampedLock sl = new StampedLock(); + sl.readLock(); + long stamp = assertValid(sl, sl.tryOptimisticRead()); + assertThrows(IllegalMonitorStateException.class, + () -> sl.unlockRead(stamp)); + } + { + StampedLock sl = new StampedLock(); + sl.readLock(); + long stamp = assertValid(sl, sl.tryOptimisticRead()); + assertThrows(IllegalMonitorStateException.class, + () -> sl.unlock(stamp)); + } + + { + StampedLock sl = new StampedLock(); + long stamp = sl.tryConvertToOptimisticRead(sl.writeLock()); + assertValid(sl, stamp); + sl.writeLock(); + assertThrows(IllegalMonitorStateException.class, + () -> sl.unlockWrite(stamp)); + } + { + StampedLock sl = new StampedLock(); + long stamp = sl.tryConvertToOptimisticRead(sl.writeLock()); + sl.writeLock(); + assertThrows(IllegalMonitorStateException.class, + () -> sl.unlock(stamp)); + } + { + StampedLock sl = new StampedLock(); + long stamp = sl.tryConvertToOptimisticRead(sl.writeLock()); + sl.readLock(); + assertThrows(IllegalMonitorStateException.class, + () -> sl.unlockRead(stamp)); + } + { + StampedLock sl = new StampedLock(); + long stamp = sl.tryConvertToOptimisticRead(sl.writeLock()); + sl.readLock(); + assertThrows(IllegalMonitorStateException.class, + () -> sl.unlock(stamp)); + } + + { + StampedLock sl = new StampedLock(); + long stamp = sl.tryConvertToOptimisticRead(sl.readLock()); + assertValid(sl, stamp); + sl.writeLock(); + assertThrows(IllegalMonitorStateException.class, + () -> sl.unlockWrite(stamp)); + } + { + StampedLock sl = new StampedLock(); + long stamp = sl.tryConvertToOptimisticRead(sl.readLock()); + sl.writeLock(); + assertThrows(IllegalMonitorStateException.class, + () -> sl.unlock(stamp)); + } + { + StampedLock sl = new StampedLock(); + long stamp = sl.tryConvertToOptimisticRead(sl.readLock()); + sl.readLock(); + assertThrows(IllegalMonitorStateException.class, + () -> sl.unlockRead(stamp)); + } + { + StampedLock sl = new StampedLock(); + sl.readLock(); + long stamp = sl.tryConvertToOptimisticRead(sl.readLock()); + assertValid(sl, stamp); + sl.readLock(); + assertThrows(IllegalMonitorStateException.class, + () -> sl.unlockRead(stamp)); + } + { + StampedLock sl = new StampedLock(); + long stamp = sl.tryConvertToOptimisticRead(sl.readLock()); + sl.readLock(); + assertThrows(IllegalMonitorStateException.class, + () -> sl.unlock(stamp)); + } + { + StampedLock sl = new StampedLock(); + sl.readLock(); + long stamp = sl.tryConvertToOptimisticRead(sl.readLock()); + sl.readLock(); + assertThrows(IllegalMonitorStateException.class, + () -> sl.unlock(stamp)); + } + } + + static long writeLockInterruptiblyUninterrupted(StampedLock sl) { + try { return sl.writeLockInterruptibly(); } + catch (InterruptedException ex) { throw new AssertionError(ex); } + } + + static long tryWriteLockUninterrupted(StampedLock sl, long time, TimeUnit unit) { + try { return sl.tryWriteLock(time, unit); } + catch (InterruptedException ex) { throw new AssertionError(ex); } + } + + static long readLockInterruptiblyUninterrupted(StampedLock sl) { + try { return sl.readLockInterruptibly(); } + catch (InterruptedException ex) { throw new AssertionError(ex); } + } + + static long tryReadLockUninterrupted(StampedLock sl, long time, TimeUnit unit) { + try { return sl.tryReadLock(time, unit); } + catch (InterruptedException ex) { throw new AssertionError(ex); } + } + + /** + * Invalid stamps result in IllegalMonitorStateException + */ + public void testInvalidStampsThrowIllegalMonitorStateException() { + final StampedLock sl = new StampedLock(); + + assertThrows(IllegalMonitorStateException.class, + () -> sl.unlockWrite(0L), + () -> sl.unlockRead(0L), + () -> sl.unlock(0L)); + + final long optimisticStamp = sl.tryOptimisticRead(); + final long readStamp = sl.readLock(); + sl.unlockRead(readStamp); + final long writeStamp = sl.writeLock(); + sl.unlockWrite(writeStamp); + assertTrue(optimisticStamp != 0L && readStamp != 0L && writeStamp != 0L); + final long[] noLongerValidStamps = { optimisticStamp, readStamp, writeStamp }; + final Runnable assertNoLongerValidStampsThrow = () -> { + for (long noLongerValidStamp : noLongerValidStamps) + assertThrows(IllegalMonitorStateException.class, + () -> sl.unlockWrite(noLongerValidStamp), + () -> sl.unlockRead(noLongerValidStamp), + () -> sl.unlock(noLongerValidStamp)); + }; + assertNoLongerValidStampsThrow.run(); + + for (Function readLocker : readLockers()) + for (BiConsumer readUnlocker : readUnlockers()) { + final long stamp = readLocker.apply(sl); + assertValid(sl, stamp); + assertNoLongerValidStampsThrow.run(); + assertThrows(IllegalMonitorStateException.class, + () -> sl.unlockWrite(stamp), + () -> sl.unlockRead(sl.tryOptimisticRead()), + () -> sl.unlockRead(0L)); + readUnlocker.accept(sl, stamp); + assertUnlocked(sl); + assertNoLongerValidStampsThrow.run(); + } + + for (Function writeLocker : writeLockers()) + for (BiConsumer writeUnlocker : writeUnlockers()) { + final long stamp = writeLocker.apply(sl); + assertValid(sl, stamp); + assertNoLongerValidStampsThrow.run(); + assertThrows(IllegalMonitorStateException.class, + () -> sl.unlockRead(stamp), + () -> sl.unlockWrite(0L)); + writeUnlocker.accept(sl, stamp); + assertUnlocked(sl); + assertNoLongerValidStampsThrow.run(); + } + } + + /** + * Read locks can be very deeply nested + */ + public void testDeeplyNestedReadLocks() { + final StampedLock lock = new StampedLock(); + final int depth = 300; + final long[] stamps = new long[depth]; + final List> readLockers = readLockers(); + final List> readUnlockers = readUnlockers(); + for (int i = 0; i < depth; i++) { + Function readLocker + = readLockers.get(i % readLockers.size()); + long stamp = readLocker.apply(lock); + assertEquals(i + 1, lock.getReadLockCount()); + assertTrue(lock.isReadLocked()); + stamps[i] = stamp; + } + for (int i = 0; i < depth; i++) { + BiConsumer readUnlocker + = readUnlockers.get(i % readUnlockers.size()); + assertEquals(depth - i, lock.getReadLockCount()); + assertTrue(lock.isReadLocked()); + readUnlocker.accept(lock, stamps[depth - 1 - i]); + } + assertUnlocked(lock); + } + + /** + * Stamped locks are not reentrant. + */ + public void testNonReentrant() throws InterruptedException { + final StampedLock lock = new StampedLock(); + long stamp; + + stamp = lock.writeLock(); + assertValid(lock, stamp); + assertEquals(0L, lock.tryWriteLock(0L, DAYS)); + assertEquals(0L, lock.tryReadLock(0L, DAYS)); + assertValid(lock, stamp); + lock.unlockWrite(stamp); + + stamp = lock.tryWriteLock(1L, DAYS); + assertEquals(0L, lock.tryWriteLock(0L, DAYS)); + assertValid(lock, stamp); + lock.unlockWrite(stamp); + + stamp = lock.readLock(); + assertEquals(0L, lock.tryWriteLock(0L, DAYS)); + assertValid(lock, stamp); + lock.unlockRead(stamp); + } + + /** + * """StampedLocks have no notion of ownership. Locks acquired in + * one thread can be released or converted in another.""" + */ + public void testNoOwnership() throws Throwable { + ArrayList> futures = new ArrayList<>(); + for (Function writeLocker : writeLockers()) + for (BiConsumer writeUnlocker : writeUnlockers()) { + StampedLock lock = new StampedLock(); + long stamp = writeLocker.apply(lock); + futures.add(cachedThreadPool.submit(new CheckedRunnable() { + public void realRun() { + writeUnlocker.accept(lock, stamp); + assertUnlocked(lock); + assertFalse(lock.validate(stamp)); + }})); + } + for (Future future : futures) + assertNull(future.get()); + } + + /** Tries out sample usage code from StampedLock javadoc. */ + public void testSampleUsage() throws Throwable { + class Point { + private double x, y; + private final StampedLock sl = new StampedLock(); + + void move(double deltaX, double deltaY) { // an exclusively locked method + long stamp = sl.writeLock(); + try { + x += deltaX; + y += deltaY; + } finally { + sl.unlockWrite(stamp); + } + } + + double distanceFromOrigin() { // A read-only method + double currentX, currentY; long stamp = sl.tryOptimisticRead(); - sl.readLock(); - sl.unlockRead(stamp); - }, - () -> { - StampedLock sl = new StampedLock(); + do { + if (stamp == 0L) + stamp = sl.readLock(); + try { + // possibly racy reads + currentX = x; + currentY = y; + } finally { + stamp = sl.tryConvertToOptimisticRead(stamp); + } + } while (stamp == 0); + return Math.hypot(currentX, currentY); + } + + double distanceFromOrigin2() { long stamp = sl.tryOptimisticRead(); - sl.readLock(); - sl.unlock(stamp); - }, + try { + retryHoldingLock: + for (;; stamp = sl.readLock()) { + if (stamp == 0L) + continue retryHoldingLock; + // possibly racy reads + double currentX = x; + double currentY = y; + if (!sl.validate(stamp)) + continue retryHoldingLock; + return Math.hypot(currentX, currentY); + } + } finally { + if (StampedLock.isReadLockStamp(stamp)) + sl.unlockRead(stamp); + } + } + + void moveIfAtOrigin(double newX, double newY) { + long stamp = sl.readLock(); + try { + while (x == 0.0 && y == 0.0) { + long ws = sl.tryConvertToWriteLock(stamp); + if (ws != 0L) { + stamp = ws; + x = newX; + y = newY; + return; + } + else { + sl.unlockRead(stamp); + stamp = sl.writeLock(); + } + } + } finally { + sl.unlock(stamp); + } + } + } - () -> { - StampedLock sl = new StampedLock(); - long stamp = sl.tryConvertToOptimisticRead(sl.writeLock()); - assertTrue(stamp != 0); - sl.writeLock(); - sl.unlockWrite(stamp); - }, - () -> { - StampedLock sl = new StampedLock(); - long stamp = sl.tryConvertToOptimisticRead(sl.writeLock()); - sl.writeLock(); - sl.unlock(stamp); - }, - () -> { - StampedLock sl = new StampedLock(); - long stamp = sl.tryConvertToOptimisticRead(sl.writeLock()); - sl.readLock(); - sl.unlockRead(stamp); - }, - () -> { - StampedLock sl = new StampedLock(); - long stamp = sl.tryConvertToOptimisticRead(sl.writeLock()); - sl.readLock(); - sl.unlock(stamp); - }, + Point p = new Point(); + p.move(3.0, 4.0); + assertEquals(5.0, p.distanceFromOrigin()); + p.moveIfAtOrigin(5.0, 12.0); + assertEquals(5.0, p.distanceFromOrigin2()); + } + + /** + * Stamp inspection methods work as expected, and do not inspect + * the state of the lock itself. + */ + public void testStampStateInspectionMethods() { + StampedLock lock = new StampedLock(); + + assertFalse(isWriteLockStamp(0L)); + assertFalse(isReadLockStamp(0L)); + assertFalse(isLockStamp(0L)); + assertFalse(isOptimisticReadStamp(0L)); + + { + long stamp = lock.writeLock(); + for (int i = 0; i < 2; i++) { + assertTrue(isWriteLockStamp(stamp)); + assertFalse(isReadLockStamp(stamp)); + assertTrue(isLockStamp(stamp)); + assertFalse(isOptimisticReadStamp(stamp)); + if (i == 0) + lock.unlockWrite(stamp); + } + } + { + long stamp = lock.readLock(); + for (int i = 0; i < 2; i++) { + assertFalse(isWriteLockStamp(stamp)); + assertTrue(isReadLockStamp(stamp)); + assertTrue(isLockStamp(stamp)); + assertFalse(isOptimisticReadStamp(stamp)); + if (i == 0) + lock.unlockRead(stamp); + } + } + + { + long optimisticStamp = lock.tryOptimisticRead(); + long readStamp = lock.tryConvertToReadLock(optimisticStamp); + long writeStamp = lock.tryConvertToWriteLock(readStamp); + for (int i = 0; i < 2; i++) { + assertFalse(isWriteLockStamp(optimisticStamp)); + assertFalse(isReadLockStamp(optimisticStamp)); + assertFalse(isLockStamp(optimisticStamp)); + assertTrue(isOptimisticReadStamp(optimisticStamp)); + + assertFalse(isWriteLockStamp(readStamp)); + assertTrue(isReadLockStamp(readStamp)); + assertTrue(isLockStamp(readStamp)); + assertFalse(isOptimisticReadStamp(readStamp)); + + assertTrue(isWriteLockStamp(writeStamp)); + assertFalse(isReadLockStamp(writeStamp)); + assertTrue(isLockStamp(writeStamp)); + assertFalse(isOptimisticReadStamp(writeStamp)); + if (i == 0) + lock.unlockWrite(writeStamp); + } + } + } + + /** + * Multiple threads repeatedly contend for the same lock. + */ + public void testConcurrentAccess() throws Exception { + final StampedLock sl = new StampedLock(); + final Lock wl = sl.asWriteLock(); + final Lock rl = sl.asReadLock(); + final long testDurationMillis = expensiveTests ? 1000 : 2; + final int nTasks = ThreadLocalRandom.current().nextInt(1, 10); + final AtomicBoolean done = new AtomicBoolean(false); + final List futures = new ArrayList<>(); + final List> stampedWriteLockers = List.of( + () -> sl.writeLock(), + () -> writeLockInterruptiblyUninterrupted(sl), + () -> tryWriteLockUninterrupted(sl, LONG_DELAY_MS, MILLISECONDS), () -> { - StampedLock sl = new StampedLock(); - long stamp = sl.tryConvertToOptimisticRead(sl.readLock()); - assertTrue(stamp != 0); - sl.writeLock(); - sl.unlockWrite(stamp); + long stamp; + do { stamp = sl.tryConvertToWriteLock(sl.tryOptimisticRead()); } + while (stamp == 0L); + return stamp; }, () -> { - StampedLock sl = new StampedLock(); - long stamp = sl.tryConvertToOptimisticRead(sl.readLock()); - sl.writeLock(); - sl.unlock(stamp); + long stamp; + do { stamp = sl.tryWriteLock(); } while (stamp == 0L); + return stamp; }, () -> { - StampedLock sl = new StampedLock(); - long stamp = sl.tryConvertToOptimisticRead(sl.readLock()); - sl.readLock(); - sl.unlockRead(stamp); - }, + long stamp; + do { stamp = sl.tryWriteLock(0L, DAYS); } while (stamp == 0L); + return stamp; + }); + final List> stampedReadLockers = List.of( + () -> sl.readLock(), + () -> readLockInterruptiblyUninterrupted(sl), + () -> tryReadLockUninterrupted(sl, LONG_DELAY_MS, MILLISECONDS), () -> { - StampedLock sl = new StampedLock(); - sl.readLock(); - long stamp = sl.tryConvertToOptimisticRead(sl.readLock()); - assertTrue(stamp != 0); - sl.readLock(); - sl.unlockRead(stamp); + long stamp; + do { stamp = sl.tryConvertToReadLock(sl.tryOptimisticRead()); } + while (stamp == 0L); + return stamp; }, () -> { - StampedLock sl = new StampedLock(); - long stamp = sl.tryConvertToOptimisticRead(sl.readLock()); - sl.readLock(); - sl.unlock(stamp); + long stamp; + do { stamp = sl.tryReadLock(); } while (stamp == 0L); + return stamp; }, () -> { - StampedLock sl = new StampedLock(); - sl.readLock(); - long stamp = sl.tryConvertToOptimisticRead(sl.readLock()); - sl.readLock(); - sl.unlock(stamp); - }, + long stamp; + do { stamp = sl.tryReadLock(0L, DAYS); } while (stamp == 0L); + return stamp; + }); + final List> stampedWriteUnlockers = List.of( + stamp -> sl.unlockWrite(stamp), + stamp -> sl.unlock(stamp), + stamp -> assertTrue(sl.tryUnlockWrite()), + stamp -> wl.unlock(), + stamp -> sl.tryConvertToOptimisticRead(stamp)); + final List> stampedReadUnlockers = List.of( + stamp -> sl.unlockRead(stamp), + stamp -> sl.unlock(stamp), + stamp -> assertTrue(sl.tryUnlockRead()), + stamp -> rl.unlock(), + stamp -> sl.tryConvertToOptimisticRead(stamp)); + final Action writer = () -> { + // repeatedly acquires write lock + var locker = chooseRandomly(stampedWriteLockers); + var unlocker = chooseRandomly(stampedWriteUnlockers); + while (!done.getAcquire()) { + long stamp = locker.call(); + try { + assertTrue(isWriteLockStamp(stamp)); + assertTrue(sl.isWriteLocked()); + assertFalse(isReadLockStamp(stamp)); + assertFalse(sl.isReadLocked()); + assertEquals(0, sl.getReadLockCount()); + assertTrue(sl.validate(stamp)); + } finally { + unlocker.accept(stamp); + } + } }; - - assertThrows(IllegalMonitorStateException.class, actions); + final Action reader = () -> { + // repeatedly acquires read lock + var locker = chooseRandomly(stampedReadLockers); + var unlocker = chooseRandomly(stampedReadUnlockers); + while (!done.getAcquire()) { + long stamp = locker.call(); + try { + assertFalse(isWriteLockStamp(stamp)); + assertFalse(sl.isWriteLocked()); + assertTrue(isReadLockStamp(stamp)); + assertTrue(sl.isReadLocked()); + assertTrue(sl.getReadLockCount() > 0); + assertTrue(sl.validate(stamp)); + } finally { + unlocker.accept(stamp); + } + } + }; + for (int i = nTasks; i--> 0; ) { + Action task = chooseRandomly(writer, reader); + futures.add(CompletableFuture.runAsync(checkedRunnable(task))); + } + Thread.sleep(testDurationMillis); + done.setRelease(true); + for (var future : futures) + checkTimedGet(future, null); } }