--- jsr166/src/test/tck/ReentrantLockTest.java 2011/05/24 23:40:14 1.49 +++ jsr166/src/test/tck/ReentrantLockTest.java 2019/09/26 20:48:52 1.71 @@ -6,39 +6,51 @@ * Pat Fisher, Mike Judd. */ -import junit.framework.*; -import java.util.concurrent.locks.*; -import java.util.concurrent.*; import static java.util.concurrent.TimeUnit.MILLISECONDS; -import java.util.*; -import java.io.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import junit.framework.Test; +import junit.framework.TestSuite; + +@SuppressWarnings("WaitNotInLoop") // we implement spurious-wakeup freedom public class ReentrantLockTest extends JSR166TestCase { public static void main(String[] args) { - junit.textui.TestRunner.run(suite()); + main(suite(), args); } public static Test suite() { return new TestSuite(ReentrantLockTest.class); } /** - * A runnable calling lockInterruptibly + * A checked runnable calling lockInterruptibly */ class InterruptibleLockRunnable extends CheckedRunnable { final ReentrantLock lock; - InterruptibleLockRunnable(ReentrantLock l) { lock = l; } + InterruptibleLockRunnable(ReentrantLock lock) { this.lock = lock; } public void realRun() throws InterruptedException { lock.lockInterruptibly(); } } /** - * A runnable calling lockInterruptibly that expects to be + * A checked runnable calling lockInterruptibly that expects to be * interrupted */ class InterruptedLockRunnable extends CheckedInterruptedRunnable { final ReentrantLock lock; - InterruptedLockRunnable(ReentrantLock l) { lock = l; } + InterruptedLockRunnable(ReentrantLock lock) { this.lock = lock; } public void realRun() throws InterruptedException { lock.lockInterruptibly(); } @@ -78,11 +90,11 @@ public class ReentrantLockTest extends J long startTime = System.nanoTime(); while (!lock.hasQueuedThread(t)) { if (millisElapsedSince(startTime) > LONG_DELAY_MS) - throw new AssertionFailedError("timed out"); + throw new AssertionError("timed out"); Thread.yield(); } assertTrue(t.isAlive()); - assertTrue(lock.getOwner() != t); + assertNotSame(t, lock.getOwner()); } /** @@ -136,10 +148,15 @@ public class ReentrantLockTest extends J lock.unlock(); } - enum AwaitMethod { await, awaitTimed, awaitNanos, awaitUntil }; + enum AwaitMethod { await, awaitTimed, awaitNanos, awaitUntil } + + static AwaitMethod randomAwaitMethod() { + AwaitMethod[] awaitMethods = AwaitMethod.values(); + return awaitMethods[ThreadLocalRandom.current().nextInt(awaitMethods.length)]; + } /** - * Awaits condition using the specified AwaitMethod. + * Awaits condition "indefinitely" using the specified AwaitMethod. */ void await(Condition c, AwaitMethod awaitMethod) throws InterruptedException { @@ -152,13 +169,16 @@ public class ReentrantLockTest extends J assertTrue(c.await(timeoutMillis, MILLISECONDS)); break; case awaitNanos: - long nanosTimeout = MILLISECONDS.toNanos(timeoutMillis); - long nanosRemaining = c.awaitNanos(nanosTimeout); - assertTrue(nanosRemaining > 0); + long timeoutNanos = MILLISECONDS.toNanos(timeoutMillis); + long nanosRemaining = c.awaitNanos(timeoutNanos); + assertTrue(nanosRemaining > timeoutNanos / 2); + assertTrue(nanosRemaining <= timeoutNanos); break; case awaitUntil: assertTrue(c.awaitUntil(delayedDate(timeoutMillis))); break; + default: + throw new AssertionError(); } } @@ -199,7 +219,7 @@ public class ReentrantLockTest extends J public void testUnlock_IMSE() { testUnlock_IMSE(false); } public void testUnlock_IMSE_fair() { testUnlock_IMSE(true); } public void testUnlock_IMSE(boolean fair) { - ReentrantLock lock = new ReentrantLock(fair); + final ReentrantLock lock = new ReentrantLock(fair); try { lock.unlock(); shouldThrow(); @@ -389,11 +409,11 @@ public class ReentrantLockTest extends J public void testTryLock_Timeout_fair() { testTryLock_Timeout(true); } public void testTryLock_Timeout(boolean fair) { final PublicReentrantLock lock = new PublicReentrantLock(fair); + final long timeoutMillis = timeoutMillis(); lock.lock(); Thread t = newStartedThread(new CheckedRunnable() { public void realRun() throws InterruptedException { long startTime = System.nanoTime(); - long timeoutMillis = 10; assertFalse(lock.tryLock(timeoutMillis, MILLISECONDS)); assertTrue(millisElapsedSince(startTime) >= timeoutMillis); }}); @@ -408,14 +428,14 @@ public class ReentrantLockTest extends J public void testGetHoldCount() { testGetHoldCount(false); } public void testGetHoldCount_fair() { testGetHoldCount(true); } public void testGetHoldCount(boolean fair) { - ReentrantLock lock = new ReentrantLock(fair); + final ReentrantLock lock = new ReentrantLock(fair); for (int i = 1; i <= SIZE; i++) { lock.lock(); assertEquals(i, lock.getHoldCount()); } for (int i = SIZE; i > 0; i--) { lock.unlock(); - assertEquals(i-1, lock.getHoldCount()); + assertEquals(i - 1, lock.getHoldCount()); } } @@ -425,8 +445,8 @@ public class ReentrantLockTest extends J public void testIsLocked() { testIsLocked(false); } public void testIsLocked_fair() { testIsLocked(true); } public void testIsLocked(boolean fair) { + final ReentrantLock lock = new ReentrantLock(fair); try { - final ReentrantLock lock = new ReentrantLock(fair); assertFalse(lock.isLocked()); lock.lock(); assertTrue(lock.isLocked()); @@ -451,9 +471,7 @@ public class ReentrantLockTest extends J barrier.await(); awaitTermination(t); assertFalse(lock.isLocked()); - } catch (Exception e) { - threadUnexpectedException(e); - } + } catch (Exception fail) { threadUnexpectedException(fail); } } /** @@ -465,9 +483,7 @@ public class ReentrantLockTest extends J final PublicReentrantLock lock = new PublicReentrantLock(fair); try { lock.lockInterruptibly(); - } catch (InterruptedException ie) { - threadUnexpectedException(ie); - } + } catch (InterruptedException fail) { threadUnexpectedException(fail); } assertLockedByMoi(lock); Thread t = newStartedThread(new InterruptedLockRunnable(lock)); waitForQueuedThread(lock, t); @@ -517,20 +533,18 @@ public class ReentrantLockTest extends J public void testAwaitNanos_Timeout() { testAwaitNanos_Timeout(false); } public void testAwaitNanos_Timeout_fair() { testAwaitNanos_Timeout(true); } public void testAwaitNanos_Timeout(boolean fair) { + final ReentrantLock lock = new ReentrantLock(fair); + final Condition c = lock.newCondition(); + final long timeoutMillis = timeoutMillis(); + final long timeoutNanos = MILLISECONDS.toNanos(timeoutMillis); + lock.lock(); + final long startTime = System.nanoTime(); try { - final ReentrantLock lock = new ReentrantLock(fair); - final Condition c = lock.newCondition(); - lock.lock(); - long startTime = System.nanoTime(); - long timeoutMillis = 10; - long timeoutNanos = MILLISECONDS.toNanos(timeoutMillis); long nanosRemaining = c.awaitNanos(timeoutNanos); assertTrue(nanosRemaining <= 0); - assertTrue(millisElapsedSince(startTime) >= timeoutMillis); - lock.unlock(); - } catch (InterruptedException e) { - threadUnexpectedException(e); - } + } catch (InterruptedException fail) { threadUnexpectedException(fail); } + assertTrue(millisElapsedSince(startTime) >= timeoutMillis); + lock.unlock(); } /** @@ -539,18 +553,16 @@ public class ReentrantLockTest extends J public void testAwait_Timeout() { testAwait_Timeout(false); } public void testAwait_Timeout_fair() { testAwait_Timeout(true); } public void testAwait_Timeout(boolean fair) { + final ReentrantLock lock = new ReentrantLock(fair); + final Condition c = lock.newCondition(); + final long timeoutMillis = timeoutMillis(); + lock.lock(); + final long startTime = System.nanoTime(); try { - final ReentrantLock lock = new ReentrantLock(fair); - final Condition c = lock.newCondition(); - lock.lock(); - long startTime = System.nanoTime(); - long timeoutMillis = 10; assertFalse(c.await(timeoutMillis, MILLISECONDS)); - assertTrue(millisElapsedSince(startTime) >= timeoutMillis); - lock.unlock(); - } catch (InterruptedException e) { - threadUnexpectedException(e); - } + } catch (InterruptedException fail) { threadUnexpectedException(fail); } + assertTrue(millisElapsedSince(startTime) >= timeoutMillis); + lock.unlock(); } /** @@ -559,19 +571,17 @@ public class ReentrantLockTest extends J public void testAwaitUntil_Timeout() { testAwaitUntil_Timeout(false); } public void testAwaitUntil_Timeout_fair() { testAwaitUntil_Timeout(true); } public void testAwaitUntil_Timeout(boolean fair) { + final ReentrantLock lock = new ReentrantLock(fair); + final Condition c = lock.newCondition(); + lock.lock(); + // We shouldn't assume that nanoTime and currentTimeMillis + // use the same time source, so don't use nanoTime here. + final java.util.Date delayedDate = delayedDate(timeoutMillis()); try { - final ReentrantLock lock = new ReentrantLock(fair); - final Condition c = lock.newCondition(); - lock.lock(); - long startTime = System.nanoTime(); - long timeoutMillis = 10; - java.util.Date d = new java.util.Date(); - assertFalse(c.awaitUntil(new java.util.Date(d.getTime() + timeoutMillis))); - assertTrue(millisElapsedSince(startTime) >= timeoutMillis); - lock.unlock(); - } catch (InterruptedException e) { - threadUnexpectedException(e); - } + assertFalse(c.awaitUntil(delayedDate)); + } catch (InterruptedException fail) { threadUnexpectedException(fail); } + assertTrue(new java.util.Date().getTime() >= delayedDate.getTime()); + lock.unlock(); } /** @@ -735,20 +745,20 @@ public class ReentrantLockTest extends J public void testHasWaiters(boolean fair) { final PublicReentrantLock lock = new PublicReentrantLock(fair); final Condition c = lock.newCondition(); - final CountDownLatch locked = new CountDownLatch(1); + final CountDownLatch pleaseSignal = new CountDownLatch(1); Thread t = newStartedThread(new CheckedRunnable() { public void realRun() throws InterruptedException { lock.lock(); assertHasNoWaiters(lock, c); assertFalse(lock.hasWaiters(c)); - locked.countDown(); + pleaseSignal.countDown(); c.await(); assertHasNoWaiters(lock, c); assertFalse(lock.hasWaiters(c)); lock.unlock(); }}); - await(locked); + await(pleaseSignal); lock.lock(); assertHasWaiters(lock, c, t); assertTrue(lock.hasWaiters(c)); @@ -879,33 +889,49 @@ public class ReentrantLockTest extends J } /** - * awaitUninterruptibly doesn't abort on interrupt + * awaitUninterruptibly is uninterruptible */ public void testAwaitUninterruptibly() { testAwaitUninterruptibly(false); } public void testAwaitUninterruptibly_fair() { testAwaitUninterruptibly(true); } public void testAwaitUninterruptibly(boolean fair) { final ReentrantLock lock = new ReentrantLock(fair); - final Condition c = lock.newCondition(); - final CountDownLatch locked = new CountDownLatch(1); - Thread t = newStartedThread(new CheckedRunnable() { + final Condition condition = lock.newCondition(); + final CountDownLatch pleaseInterrupt = new CountDownLatch(2); + + Thread t1 = newStartedThread(new CheckedRunnable() { public void realRun() { + // Interrupt before awaitUninterruptibly lock.lock(); - locked.countDown(); - c.awaitUninterruptibly(); + pleaseInterrupt.countDown(); + Thread.currentThread().interrupt(); + condition.awaitUninterruptibly(); assertTrue(Thread.interrupted()); lock.unlock(); }}); - await(locked); + Thread t2 = newStartedThread(new CheckedRunnable() { + public void realRun() { + // Interrupt during awaitUninterruptibly + lock.lock(); + pleaseInterrupt.countDown(); + condition.awaitUninterruptibly(); + assertTrue(Thread.interrupted()); + lock.unlock(); + }}); + + await(pleaseInterrupt); + t2.interrupt(); lock.lock(); lock.unlock(); - t.interrupt(); - long timeoutMillis = 10; - assertThreadStaysAlive(t, timeoutMillis); + assertThreadBlocks(t1, Thread.State.WAITING); + assertThreadBlocks(t2, Thread.State.WAITING); + lock.lock(); - c.signal(); + condition.signalAll(); lock.unlock(); - awaitTermination(t); + + awaitTermination(t1); + awaitTermination(t2); } /** @@ -923,13 +949,13 @@ public class ReentrantLockTest extends J final PublicReentrantLock lock = new PublicReentrantLock(fair); final Condition c = lock.newCondition(); - final CountDownLatch locked = new CountDownLatch(1); + final CountDownLatch pleaseInterrupt = new CountDownLatch(1); Thread t = newStartedThread(new CheckedInterruptedRunnable() { public void realRun() throws InterruptedException { lock.lock(); assertLockedByMoi(lock); assertHasNoWaiters(lock, c); - locked.countDown(); + pleaseInterrupt.countDown(); try { await(c, awaitMethod); } finally { @@ -940,7 +966,7 @@ public class ReentrantLockTest extends J } }}); - await(locked); + await(pleaseInterrupt); assertHasWaiters(lock, c, t); t.interrupt(); awaitTermination(t); @@ -961,11 +987,11 @@ public class ReentrantLockTest extends J public void testSignalAll(boolean fair, final AwaitMethod awaitMethod) { final PublicReentrantLock lock = new PublicReentrantLock(fair); final Condition c = lock.newCondition(); - final CountDownLatch locked = new CountDownLatch(2); + final CountDownLatch pleaseSignal = new CountDownLatch(2); class Awaiter extends CheckedRunnable { public void realRun() throws InterruptedException { lock.lock(); - locked.countDown(); + pleaseSignal.countDown(); await(c, awaitMethod); lock.unlock(); } @@ -974,7 +1000,7 @@ public class ReentrantLockTest extends J Thread t1 = newStartedThread(new Awaiter()); Thread t2 = newStartedThread(new Awaiter()); - await(locked); + await(pleaseSignal); lock.lock(); assertHasWaiters(lock, c, t1, t2); c.signalAll(); @@ -1039,13 +1065,13 @@ public class ReentrantLockTest extends J public void testAwaitLockCount(boolean fair) { final PublicReentrantLock lock = new PublicReentrantLock(fair); final Condition c = lock.newCondition(); - final CountDownLatch locked = new CountDownLatch(2); + final CountDownLatch pleaseSignal = new CountDownLatch(2); Thread t1 = newStartedThread(new CheckedRunnable() { public void realRun() throws InterruptedException { lock.lock(); assertLockedByMoi(lock); assertEquals(1, lock.getHoldCount()); - locked.countDown(); + pleaseSignal.countDown(); c.await(); assertLockedByMoi(lock); assertEquals(1, lock.getHoldCount()); @@ -1058,7 +1084,7 @@ public class ReentrantLockTest extends J lock.lock(); assertLockedByMoi(lock); assertEquals(2, lock.getHoldCount()); - locked.countDown(); + pleaseSignal.countDown(); c.await(); assertLockedByMoi(lock); assertEquals(2, lock.getHoldCount()); @@ -1066,7 +1092,7 @@ public class ReentrantLockTest extends J lock.unlock(); }}); - await(locked); + await(pleaseSignal); lock.lock(); assertHasWaiters(lock, c, t1, t2); assertEquals(1, lock.getHoldCount()); @@ -1083,7 +1109,7 @@ public class ReentrantLockTest extends J public void testSerialization() { testSerialization(false); } public void testSerialization_fair() { testSerialization(true); } public void testSerialization(boolean fair) { - ReentrantLock lock = new ReentrantLock(fair); + final ReentrantLock lock = new ReentrantLock(fair); lock.lock(); ReentrantLock clone = serialClone(lock); @@ -1109,11 +1135,128 @@ public class ReentrantLockTest extends J public void testToString() { testToString(false); } public void testToString_fair() { testToString(true); } public void testToString(boolean fair) { - ReentrantLock lock = new ReentrantLock(fair); + final ReentrantLock lock = new ReentrantLock(fair); assertTrue(lock.toString().contains("Unlocked")); lock.lock(); - assertTrue(lock.toString().contains("Locked")); + assertTrue(lock.toString().contains("Locked by")); lock.unlock(); assertTrue(lock.toString().contains("Unlocked")); } + + /** + * Tests scenario for JDK-8187408 + * AbstractQueuedSynchronizer wait queue corrupted when thread awaits without holding the lock + */ + public void testBug8187408() throws InterruptedException { + final ThreadLocalRandom rnd = ThreadLocalRandom.current(); + final AwaitMethod awaitMethod = randomAwaitMethod(); + final int nThreads = rnd.nextInt(2, 10); + final ReentrantLock lock = new ReentrantLock(); + final Condition cond = lock.newCondition(); + final CountDownLatch done = new CountDownLatch(nThreads); + final ArrayList threads = new ArrayList<>(); + + Runnable rogue = () -> { + while (done.getCount() > 0) { + try { + // call await without holding lock?! + await(cond, awaitMethod); + throw new AssertionError("should throw"); + } + catch (IllegalMonitorStateException success) {} + catch (Throwable fail) { threadUnexpectedException(fail); }}}; + Thread rogueThread = new Thread(rogue, "rogue"); + threads.add(rogueThread); + rogueThread.start(); + + Runnable waiter = () -> { + lock.lock(); + try { + done.countDown(); + cond.await(); + } catch (Throwable fail) { + threadUnexpectedException(fail); + } finally { + lock.unlock(); + }}; + for (int i = 0; i < nThreads; i++) { + Thread thread = new Thread(waiter, "waiter"); + threads.add(thread); + thread.start(); + } + + assertTrue(done.await(LONG_DELAY_MS, MILLISECONDS)); + lock.lock(); + try { + assertEquals(nThreads, lock.getWaitQueueLength(cond)); + } finally { + cond.signalAll(); + lock.unlock(); + } + for (Thread thread : threads) { + thread.join(LONG_DELAY_MS); + assertFalse(thread.isAlive()); + } + } + + /** + * ThreadMXBean reports the blockers that we expect. + */ + public void testBlockers() { + if (!testImplementationDetails) return; + final boolean fair = randomBoolean(); + final boolean timedAcquire = randomBoolean(); + final boolean timedAwait = randomBoolean(); + final String syncClassName = fair + ? "ReentrantLock$FairSync" + : "ReentrantLock$NonfairSync"; + final String conditionClassName + = "AbstractQueuedSynchronizer$ConditionObject"; + final Thread.State expectedAcquireState = timedAcquire + ? Thread.State.TIMED_WAITING + : Thread.State.WAITING; + final Thread.State expectedAwaitState = timedAwait + ? Thread.State.TIMED_WAITING + : Thread.State.WAITING; + final Lock lock = new ReentrantLock(fair); + final Condition condition = lock.newCondition(); + final AtomicBoolean conditionSatisfied = new AtomicBoolean(false); + lock.lock(); + final Thread thread = newStartedThread((Action) () -> { + if (timedAcquire) + lock.tryLock(LONGER_DELAY_MS, MILLISECONDS); + else + lock.lock(); + while (!conditionSatisfied.get()) + if (timedAwait) + condition.await(LONGER_DELAY_MS, MILLISECONDS); + else + condition.await(); + }); + Callable waitingForLock = () -> { + String className; + return thread.getState() == expectedAcquireState + && (className = blockerClassName(thread)) != null + && className.endsWith(syncClassName); + }; + waitForThreadToEnterWaitState(thread, waitingForLock); + + lock.unlock(); + Callable waitingForCondition = () -> { + String className; + return thread.getState() == expectedAwaitState + && (className = blockerClassName(thread)) != null + && className.endsWith(conditionClassName); + }; + waitForThreadToEnterWaitState(thread, waitingForCondition); + + // politely release the waiter + conditionSatisfied.set(true); + lock.lock(); + try { + condition.signal(); + } finally { lock.unlock(); } + + awaitTermination(thread); + } }