--- jsr166/src/test/tck/AbstractQueuedSynchronizerTest.java 2013/05/02 18:01:09 1.44 +++ jsr166/src/test/tck/AbstractQueuedSynchronizerTest.java 2019/08/13 23:05:18 1.68 @@ -6,15 +6,23 @@ * Pat Fisher, Mike Judd. */ -import junit.framework.*; -import java.util.*; import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.NANOSECONDS; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; import java.util.concurrent.locks.AbstractQueuedSynchronizer; import java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject; +import junit.framework.Test; +import junit.framework.TestSuite; + +@SuppressWarnings("WaitNotInLoop") // we implement spurious-wakeup freedom public class AbstractQueuedSynchronizerTest extends JSR166TestCase { public static void main(String[] args) { - junit.textui.TestRunner.run(suite()); + main(suite(), args); } public static Test suite() { return new TestSuite(AbstractQueuedSynchronizerTest.class); @@ -26,6 +34,9 @@ public class AbstractQueuedSynchronizerT * methods/features of AbstractQueuedSynchronizer are tested via * other test classes, including those for ReentrantLock, * ReentrantReadWriteLock, and Semaphore. + * + * Unlike the javadoc sample, we don't track owner thread via + * AbstractOwnableSynchronizer methods. */ static class Mutex extends AbstractQueuedSynchronizer { /** An eccentric value for locked synchronizer state. */ @@ -33,18 +44,19 @@ public class AbstractQueuedSynchronizerT static final int UNLOCKED = 0; + /** Owner thread is untracked, so this is really just isLocked(). */ @Override public boolean isHeldExclusively() { int state = getState(); assertTrue(state == UNLOCKED || state == LOCKED); return state == LOCKED; } - @Override public boolean tryAcquire(int acquires) { + @Override protected boolean tryAcquire(int acquires) { assertEquals(LOCKED, acquires); return compareAndSetState(UNLOCKED, LOCKED); } - @Override public boolean tryRelease(int releases) { + @Override protected boolean tryRelease(int releases) { if (getState() != LOCKED) throw new IllegalMonitorStateException(); assertEquals(LOCKED, releases); setState(UNLOCKED); @@ -75,13 +87,14 @@ public class AbstractQueuedSynchronizerT release(LOCKED); } + /** Faux-Implements Lock.newCondition(). */ public ConditionObject newCondition() { return new ConditionObject(); } } /** - * A simple latch class, to test shared mode. + * A minimal latch class, to test shared mode. */ static class BooleanLatch extends AbstractQueuedSynchronizer { public boolean isSignalled() { return getState() != 0; } @@ -130,7 +143,7 @@ public class AbstractQueuedSynchronizerT long startTime = System.nanoTime(); while (!sync.isQueued(t)) { if (millisElapsedSince(startTime) > LONG_DELAY_MS) - throw new AssertionFailedError("timed out"); + throw new AssertionError("timed out"); Thread.yield(); } assertTrue(t.isAlive()); @@ -198,7 +211,7 @@ public class AbstractQueuedSynchronizerT new HashSet(Arrays.asList(threads))); } - enum AwaitMethod { await, awaitTimed, awaitNanos, awaitUntil }; + enum AwaitMethod { await, awaitTimed, awaitNanos, awaitUntil } /** * Awaits condition using the specified AwaitMethod. @@ -214,13 +227,15 @@ public class AbstractQueuedSynchronizerT assertTrue(c.await(timeoutMillis, MILLISECONDS)); break; case awaitNanos: - long nanosTimeout = MILLISECONDS.toNanos(timeoutMillis); - long nanosRemaining = c.awaitNanos(nanosTimeout); + long timeoutNanos = MILLISECONDS.toNanos(timeoutMillis); + long nanosRemaining = c.awaitNanos(timeoutNanos); assertTrue(nanosRemaining > 0); break; case awaitUntil: assertTrue(c.awaitUntil(delayedDate(timeoutMillis))); break; + default: + throw new AssertionError(); } } @@ -229,26 +244,34 @@ public class AbstractQueuedSynchronizerT * default timeout duration). */ void assertAwaitTimesOut(ConditionObject c, AwaitMethod awaitMethod) { - long timeoutMillis = timeoutMillis(); - long startTime = System.nanoTime(); + final long timeoutMillis = timeoutMillis(); + final long startTime; try { switch (awaitMethod) { case awaitTimed: + startTime = System.nanoTime(); assertFalse(c.await(timeoutMillis, MILLISECONDS)); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis); break; case awaitNanos: - long nanosTimeout = MILLISECONDS.toNanos(timeoutMillis); - long nanosRemaining = c.awaitNanos(nanosTimeout); + startTime = System.nanoTime(); + long timeoutNanos = MILLISECONDS.toNanos(timeoutMillis); + long nanosRemaining = c.awaitNanos(timeoutNanos); assertTrue(nanosRemaining <= 0); + assertTrue(nanosRemaining > -MILLISECONDS.toNanos(LONG_DELAY_MS)); + assertTrue(millisElapsedSince(startTime) >= timeoutMillis); break; case awaitUntil: + // We shouldn't assume that nanoTime and currentTimeMillis + // use the same time source, so don't use nanoTime here. + java.util.Date delayedDate = delayedDate(timeoutMillis); assertFalse(c.awaitUntil(delayedDate(timeoutMillis))); + assertTrue(new java.util.Date().getTime() >= delayedDate.getTime()); break; default: throw new UnsupportedOperationException(); } } catch (InterruptedException ie) { threadUnexpectedException(ie); } - assertTrue(millisElapsedSince(startTime) >= timeoutMillis); } /** @@ -951,30 +974,30 @@ public class AbstractQueuedSynchronizerT */ public void testAwaitUninterruptibly() { final Mutex sync = new Mutex(); - final ConditionObject c = sync.newCondition(); + final ConditionObject condition = sync.newCondition(); final BooleanLatch pleaseInterrupt = new BooleanLatch(); Thread t = newStartedThread(new CheckedRunnable() { public void realRun() { sync.acquire(); assertTrue(pleaseInterrupt.releaseShared(0)); - c.awaitUninterruptibly(); + condition.awaitUninterruptibly(); assertTrue(Thread.interrupted()); - assertHasWaitersLocked(sync, c, NO_THREADS); + assertHasWaitersLocked(sync, condition, NO_THREADS); sync.release(); }}); pleaseInterrupt.acquireShared(0); sync.acquire(); - assertHasWaitersLocked(sync, c, t); + assertHasWaitersLocked(sync, condition, t); sync.release(); t.interrupt(); - assertHasWaitersUnlocked(sync, c, t); - assertThreadStaysAlive(t); + assertHasWaitersUnlocked(sync, condition, t); + assertThreadBlocks(t, Thread.State.WAITING); sync.acquire(); - assertHasWaitersLocked(sync, c, t); + assertHasWaitersLocked(sync, condition, t); assertHasExclusiveQueuedThreads(sync, NO_THREADS); - c.signal(); - assertHasWaitersLocked(sync, c, NO_THREADS); + condition.signal(); + assertHasWaitersLocked(sync, condition, NO_THREADS); assertHasExclusiveQueuedThreads(sync, t); sync.release(); awaitTermination(t); @@ -1117,7 +1140,7 @@ public class AbstractQueuedSynchronizerT waitForQueuedThread(l, t); assertFalse(l.isSignalled()); - assertThreadStaysAlive(t); + assertThreadBlocks(t, Thread.State.WAITING); assertHasSharedQueuedThreads(l, t); assertTrue(l.releaseShared(0)); assertTrue(l.isSignalled()); @@ -1142,7 +1165,7 @@ public class AbstractQueuedSynchronizerT waitForQueuedThread(l, t); assertFalse(l.isSignalled()); - assertThreadStaysAlive(t); + assertThreadBlocks(t, Thread.State.TIMED_WAITING); assertTrue(l.releaseShared(0)); assertTrue(l.isSignalled()); awaitTermination(t); @@ -1191,7 +1214,6 @@ public class AbstractQueuedSynchronizerT public void testTryAcquireSharedNanos_Timeout() { final BooleanLatch l = new BooleanLatch(); final BooleanLatch observedQueued = new BooleanLatch(); - final long timeoutMillis = timeoutMillis(); Thread t = newStartedThread(new CheckedRunnable() { public void realRun() throws InterruptedException { assertFalse(l.isSignalled()); @@ -1213,4 +1235,128 @@ public class AbstractQueuedSynchronizerT assertFalse(l.isSignalled()); } + /** + * awaitNanos/timed await with 0 wait times out immediately + */ + public void testAwait_Zero() throws InterruptedException { + final Mutex sync = new Mutex(); + final ConditionObject c = sync.newCondition(); + sync.acquire(); + assertTrue(c.awaitNanos(0L) <= 0); + assertFalse(c.await(0L, NANOSECONDS)); + sync.release(); + } + + /** + * awaitNanos/timed await with maximum negative wait times does not underflow + */ + public void testAwait_NegativeInfinity() throws InterruptedException { + final Mutex sync = new Mutex(); + final ConditionObject c = sync.newCondition(); + sync.acquire(); + assertTrue(c.awaitNanos(Long.MIN_VALUE) <= 0); + assertFalse(c.await(Long.MIN_VALUE, NANOSECONDS)); + sync.release(); + } + + /** + * JDK-8191483: AbstractQueuedSynchronizer cancel/cancel race + * ant -Djsr166.tckTestClass=AbstractQueuedSynchronizerTest -Djsr166.methodFilter=testCancelCancelRace -Djsr166.runsPerTest=100 tck + */ + public void testCancelCancelRace() throws InterruptedException { + class Sync extends AbstractQueuedSynchronizer { + protected boolean tryAcquire(int acquires) { + return !hasQueuedPredecessors() && compareAndSetState(0, 1); + } + protected boolean tryRelease(int releases) { + return compareAndSetState(1, 0); + } + } + + Sync s = new Sync(); + s.acquire(1); // acquire to force other threads to enqueue + + // try to trigger double cancel race with two background threads + ArrayList threads = new ArrayList<>(); + Runnable failedAcquire = () -> { + try { + s.acquireInterruptibly(1); + shouldThrow(); + } catch (InterruptedException success) {} + }; + for (int i = 0; i < 2; i++) { + Thread thread = new Thread(failedAcquire); + thread.start(); + threads.add(thread); + } + Thread.sleep(100); + for (Thread thread : threads) thread.interrupt(); + for (Thread thread : threads) awaitTermination(thread); + + s.release(1); + + // no one holds lock now, we should be able to acquire + if (!s.tryAcquire(1)) + throw new RuntimeException( + String.format( + "Broken: hasQueuedPredecessors=%s hasQueuedThreads=%s queueLength=%d firstQueuedThread=%s", + s.hasQueuedPredecessors(), + s.hasQueuedThreads(), + s.getQueueLength(), + s.getFirstQueuedThread())); + } + + /** + * Tests scenario for + * JDK-8191937: Lost interrupt in AbstractQueuedSynchronizer when tryAcquire methods throw + */ + public void testInterruptedFailingAcquire() throws InterruptedException { + final RuntimeException ex = new RuntimeException(); + + // A synchronizer only offering a choice of failure modes + class Sync extends AbstractQueuedSynchronizer { + volatile boolean pleaseThrow; + @Override protected boolean tryAcquire(int ignored) { + if (pleaseThrow) throw ex; + return false; + } + @Override protected int tryAcquireShared(int ignored) { + if (pleaseThrow) throw ex; + return -1; + } + @Override protected boolean tryRelease(int ignored) { + return true; + } + @Override protected boolean tryReleaseShared(int ignored) { + return true; + } + } + + final Sync s = new Sync(); + + final Thread thread = newStartedThread(new CheckedRunnable() { + public void realRun() { + try { + if (randomBoolean()) + s.acquire(1); + else + s.acquireShared(1); + shouldThrow(); + } catch (Throwable t) { + assertSame(ex, t); + assertTrue(Thread.interrupted()); + } + }}); + waitForThreadToEnterWaitState(thread); + assertSame(thread, s.getFirstQueuedThread()); + assertTrue(s.hasQueuedPredecessors()); + assertTrue(s.hasQueuedThreads()); + assertEquals(1, s.getQueueLength()); + + s.pleaseThrow = true; + thread.interrupt(); + s.release(1); + awaitTermination(thread); + } + }