--- jsr166/src/test/tck/AbstractQueuedSynchronizerTest.java 2017/07/17 21:01:30 1.57 +++ jsr166/src/test/tck/AbstractQueuedSynchronizerTest.java 2019/08/15 16:06:13 1.72 @@ -9,13 +9,13 @@ 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.AssertionFailedError; import junit.framework.Test; import junit.framework.TestSuite; @@ -34,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. */ @@ -41,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); @@ -83,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; } @@ -138,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()); @@ -222,8 +227,8 @@ 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: @@ -250,8 +255,8 @@ public class AbstractQueuedSynchronizerT break; case awaitNanos: startTime = System.nanoTime(); - long nanosTimeout = MILLISECONDS.toNanos(timeoutMillis); - long nanosRemaining = c.awaitNanos(nanosTimeout); + long timeoutNanos = MILLISECONDS.toNanos(timeoutMillis); + long nanosRemaining = c.awaitNanos(timeoutNanos); assertTrue(nanosRemaining <= 0); assertTrue(nanosRemaining > -MILLISECONDS.toNanos(LONG_DELAY_MS)); assertTrue(millisElapsedSince(startTime) >= timeoutMillis); @@ -1254,4 +1259,160 @@ public class AbstractQueuedSynchronizerT 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 + * ant -Djsr166.tckTestClass=AbstractQueuedSynchronizerTest -Djsr166.methodFilter=testInterruptedFailingAcquire -Djsr166.runsPerTest=10000 tck + */ + public void testInterruptedFailingAcquire() throws Throwable { + class PleaseThrow extends RuntimeException {} + final PleaseThrow ex = new PleaseThrow(); + + // 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 boolean acquireInterruptibly = randomBoolean(); + final Action[] uninterruptibleAcquireActions = { + () -> s.acquire(1), + () -> s.acquireShared(1), + }; + final long nanosTimeout = MILLISECONDS.toNanos(2 * LONG_DELAY_MS); + final Action[] interruptibleAcquireActions = { + () -> s.acquireInterruptibly(1), + () -> s.acquireSharedInterruptibly(1), + () -> s.tryAcquireNanos(1, nanosTimeout), + () -> s.tryAcquireSharedNanos(1, nanosTimeout), + }; + final Action[] releaseActions = { + () -> s.release(1), + () -> s.releaseShared(1), + }; + final Action acquireAction = acquireInterruptibly + ? chooseRandomly(interruptibleAcquireActions) + : chooseRandomly(uninterruptibleAcquireActions); + final Action releaseAction + = chooseRandomly(releaseActions); + + // From os_posix.cpp: + // + // NOTE that since there is no "lock" around the interrupt and + // is_interrupted operations, there is the possibility that the + // interrupted flag (in osThread) will be "false" but that the + // low-level events will be in the signaled state. This is + // intentional. The effect of this is that Object.wait() and + // LockSupport.park() will appear to have a spurious wakeup, which + // is allowed and not harmful, and the possibility is so rare that + // it is not worth the added complexity to add yet another lock. + final Thread thread = newStartedThread(new CheckedRunnable() { + public void realRun() throws Throwable { + try { + acquireAction.run(); + shouldThrow(); + } catch (InterruptedException possible) { + assertTrue(acquireInterruptibly); + assertFalse(Thread.interrupted()); + } catch (PleaseThrow possible) { + awaitInterrupted(); + } + }}); + for (long startTime = 0L;; ) { + waitForThreadToEnterWaitState(thread); + if (s.getFirstQueuedThread() == thread + && s.hasQueuedPredecessors() + && s.hasQueuedThreads() + && s.getQueueLength() == 1 + && s.hasContended()) + break; + if (startTime == 0L) + startTime = System.nanoTime(); + else if (millisElapsedSince(startTime) > LONG_DELAY_MS) + fail("timed out waiting for AQS state: " + + "thread state=" + thread.getState() + + ", queued threads=" + s.getQueuedThreads()); + Thread.yield(); + } + + s.pleaseThrow = true; + // release and interrupt, in random order + if (randomBoolean()) { + thread.interrupt(); + releaseAction.run(); + } else { + releaseAction.run(); + thread.interrupt(); + } + awaitTermination(thread); + + assertNull(s.getFirstQueuedThread()); + assertFalse(s.hasQueuedPredecessors()); + assertFalse(s.hasQueuedThreads()); + assertEquals(0, s.getQueueLength()); + assertTrue(s.getQueuedThreads().isEmpty()); + assertTrue(s.hasContended()); + } + }