From 794a8ca432c6561ff2495f8155d8f900d79a37c3 Mon Sep 17 00:00:00 2001 From: "c.piotre" Date: Wed, 13 Dec 2017 15:05:58 -0800 Subject: [PATCH] Add Uninterruptibles methods for Condition. Fixes #3010, #2870 RELNOTES=Add Uninterruptibles methods for Condition. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=178964181 --- .../util/concurrent/UninterruptiblesTest.java | 183 ++++++++++++++++-- .../util/concurrent/Uninterruptibles.java | 29 +++ .../util/concurrent/UninterruptiblesTest.java | 183 ++++++++++++++++-- .../util/concurrent/Uninterruptibles.java | 29 +++ 4 files changed, 402 insertions(+), 22 deletions(-) diff --git a/android/guava-tests/test/com/google/common/util/concurrent/UninterruptiblesTest.java b/android/guava-tests/test/com/google/common/util/concurrent/UninterruptiblesTest.java index 16fd838e1320..c5915b94289e 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/UninterruptiblesTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/UninterruptiblesTest.java @@ -17,6 +17,7 @@ package com.google.common.util.concurrent; import static com.google.common.util.concurrent.InterruptionUtil.repeatedlyInterruptTestThread; +import static com.google.common.util.concurrent.Uninterruptibles.awaitUninterruptibly; import static com.google.common.util.concurrent.Uninterruptibles.joinUninterruptibly; import static com.google.common.util.concurrent.Uninterruptibles.putUninterruptibly; import static com.google.common.util.concurrent.Uninterruptibles.takeUninterruptibly; @@ -28,10 +29,18 @@ import com.google.common.testing.NullPointerTester; import com.google.common.testing.TearDown; import com.google.common.testing.TearDownStack; +import java.util.Date; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import junit.framework.TestCase; /** @@ -85,6 +94,53 @@ public void testNull() throws Exception { // CountDownLatch.await() tests + // Condition.await() tests + public void testConditionAwaitTimeoutExceeded() { + Stopwatch stopwatch = Stopwatch.createStarted(); + Condition condition = TestCondition.create(); + + boolean signaledBeforeTimeout = awaitUninterruptibly(condition, 500, MILLISECONDS); + + assertFalse(signaledBeforeTimeout); + assertAtLeastTimePassed(stopwatch, 500); + assertNotInterrupted(); + } + + public void testConditionAwaitTimeoutNotExceeded() { + Stopwatch stopwatch = Stopwatch.createStarted(); + Condition condition = TestCondition.createAndSignalAfter(500, MILLISECONDS); + + boolean signaledBeforeTimeout = awaitUninterruptibly(condition, 1500, MILLISECONDS); + + assertTrue(signaledBeforeTimeout); + assertTimeNotPassed(stopwatch, LONG_DELAY_MS); + assertNotInterrupted(); + } + + public void testConditionAwaitInterruptedTimeoutExceeded() { + Stopwatch stopwatch = Stopwatch.createStarted(); + Condition condition = TestCondition.create(); + requestInterruptIn(500); + + boolean signaledBeforeTimeout = awaitUninterruptibly(condition, 1000, MILLISECONDS); + + assertFalse(signaledBeforeTimeout); + assertAtLeastTimePassed(stopwatch, 1000); + assertInterrupted(); + } + + public void testConditionAwaitInterruptedTimeoutNotExceeded() { + Stopwatch stopwatch = Stopwatch.createStarted(); + Condition condition = TestCondition.createAndSignalAfter(1000, MILLISECONDS); + requestInterruptIn(500); + + boolean signaledBeforeTimeout = awaitUninterruptibly(condition, 1500, MILLISECONDS); + + assertTrue(signaledBeforeTimeout); + assertTimeNotPassed(stopwatch, LONG_DELAY_MS); + assertInterrupted(); + } + // BlockingQueue.put() tests public void testPutWithNoWait() { Stopwatch stopwatch = Stopwatch.createStarted(); @@ -378,18 +434,18 @@ void assertCompletionNotExpected(long timeout) { assertAtLeastTimePassed(stopwatch, timeout); assertTimeNotPassed(stopwatch, expectedCompletionWaitMillis); } + } - private static void assertAtLeastTimePassed(Stopwatch stopwatch, long expectedMillis) { - long elapsedMillis = stopwatch.elapsed(MILLISECONDS); - /* - * The "+ 5" below is to permit, say, sleep(10) to sleep only 9 milliseconds. We see such - * behavior sometimes when running these tests publicly as part of Guava. "+ 5" is probably - * more generous than it needs to be. - */ - assertTrue( - "Expected elapsed millis to be >= " + expectedMillis + " but was " + elapsedMillis, - elapsedMillis + 5 >= expectedMillis); - } + private static void assertAtLeastTimePassed(Stopwatch stopwatch, long expectedMillis) { + long elapsedMillis = stopwatch.elapsed(MILLISECONDS); + /* + * The "+ 5" below is to permit, say, sleep(10) to sleep only 9 milliseconds. We see such + * behavior sometimes when running these tests publicly as part of Guava. "+ 5" is probably more + * generous than it needs to be. + */ + assertTrue( + "Expected elapsed millis to be >= " + expectedMillis + " but was " + elapsedMillis, + elapsedMillis + 5 >= expectedMillis); } // TODO(cpovirk): Split this into separate CountDownLatch and IncrementableCountDownLatch classes. @@ -672,4 +728,109 @@ private static void assertNotInterrupted() { private static void requestInterruptIn(long millis) { InterruptionUtil.requestInterruptIn(millis, MILLISECONDS); } + + private static class TestCondition implements Condition { + private final Lock lock; + private final Condition condition; + + private TestCondition(Lock lock, Condition condition) { + this.lock = lock; + this.condition = condition; + } + + static TestCondition createAndSignalAfter(long delay, TimeUnit unit) { + final TestCondition testCondition = create(); + + ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(1); + // If signal() fails somehow, we should see a failed test, even without looking at the Future. + Future unused = + scheduledPool.schedule( + new Runnable() { + @Override + public void run() { + testCondition.signal(); + } + }, + delay, + unit); + + return testCondition; + } + + static TestCondition create() { + Lock lock = new ReentrantLock(); + Condition condition = lock.newCondition(); + return new TestCondition(lock, condition); + } + + @Override + public void await() throws InterruptedException { + lock.lock(); + try { + condition.await(); + } finally { + lock.unlock(); + } + } + + @Override + public void awaitUninterruptibly() { + lock.lock(); + try { + condition.awaitUninterruptibly(); + } finally { + lock.unlock(); + } + } + + @Override + public long awaitNanos(long nanosTimeout) throws InterruptedException { + lock.lock(); + try { + return condition.awaitNanos(nanosTimeout); + } finally { + lock.unlock(); + } + } + + @Override + public boolean await(long time, TimeUnit unit) throws InterruptedException { + lock.lock(); + try { + return condition.await(time, unit); + } finally { + lock.unlock(); + } + } + + @Override + public boolean awaitUntil(Date deadline) throws InterruptedException { + lock.lock(); + try { + return condition.awaitUntil(deadline); + } finally { + lock.unlock(); + } + } + + @Override + public void signal() { + lock.lock(); + try { + condition.signal(); + } finally { + lock.unlock(); + } + } + + @Override + public void signalAll() { + lock.lock(); + try { + condition.signalAll(); + } finally { + lock.unlock(); + } + } + } } diff --git a/android/guava/src/com/google/common/util/concurrent/Uninterruptibles.java b/android/guava/src/com/google/common/util/concurrent/Uninterruptibles.java index 03637c2103e0..9f332a20820a 100644 --- a/android/guava/src/com/google/common/util/concurrent/Uninterruptibles.java +++ b/android/guava/src/com/google/common/util/concurrent/Uninterruptibles.java @@ -29,6 +29,7 @@ import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.concurrent.locks.Condition; /** * Utilities for treating interruptible operations as uninterruptible. In all cases, if a thread is @@ -93,6 +94,34 @@ public static boolean awaitUninterruptibly(CountDownLatch latch, long timeout, T } } + /** + * Invokes {@code condition.}{@link Condition#await(long, TimeUnit) await(timeout, unit)} + * uninterruptibly. + * + * @since NEXT + */ + @GwtIncompatible // concurrency + public static boolean awaitUninterruptibly(Condition condition, long timeout, TimeUnit unit) { + boolean interrupted = false; + try { + long remainingNanos = unit.toNanos(timeout); + long end = System.nanoTime() + remainingNanos; + + while (true) { + try { + return condition.await(remainingNanos, NANOSECONDS); + } catch (InterruptedException e) { + interrupted = true; + remainingNanos = end - System.nanoTime(); + } + } + } finally { + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + } + /** Invokes {@code toJoin.}{@link Thread#join() join()} uninterruptibly. */ @GwtIncompatible // concurrency public static void joinUninterruptibly(Thread toJoin) { diff --git a/guava-tests/test/com/google/common/util/concurrent/UninterruptiblesTest.java b/guava-tests/test/com/google/common/util/concurrent/UninterruptiblesTest.java index 16fd838e1320..c5915b94289e 100644 --- a/guava-tests/test/com/google/common/util/concurrent/UninterruptiblesTest.java +++ b/guava-tests/test/com/google/common/util/concurrent/UninterruptiblesTest.java @@ -17,6 +17,7 @@ package com.google.common.util.concurrent; import static com.google.common.util.concurrent.InterruptionUtil.repeatedlyInterruptTestThread; +import static com.google.common.util.concurrent.Uninterruptibles.awaitUninterruptibly; import static com.google.common.util.concurrent.Uninterruptibles.joinUninterruptibly; import static com.google.common.util.concurrent.Uninterruptibles.putUninterruptibly; import static com.google.common.util.concurrent.Uninterruptibles.takeUninterruptibly; @@ -28,10 +29,18 @@ import com.google.common.testing.NullPointerTester; import com.google.common.testing.TearDown; import com.google.common.testing.TearDownStack; +import java.util.Date; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import junit.framework.TestCase; /** @@ -85,6 +94,53 @@ public void testNull() throws Exception { // CountDownLatch.await() tests + // Condition.await() tests + public void testConditionAwaitTimeoutExceeded() { + Stopwatch stopwatch = Stopwatch.createStarted(); + Condition condition = TestCondition.create(); + + boolean signaledBeforeTimeout = awaitUninterruptibly(condition, 500, MILLISECONDS); + + assertFalse(signaledBeforeTimeout); + assertAtLeastTimePassed(stopwatch, 500); + assertNotInterrupted(); + } + + public void testConditionAwaitTimeoutNotExceeded() { + Stopwatch stopwatch = Stopwatch.createStarted(); + Condition condition = TestCondition.createAndSignalAfter(500, MILLISECONDS); + + boolean signaledBeforeTimeout = awaitUninterruptibly(condition, 1500, MILLISECONDS); + + assertTrue(signaledBeforeTimeout); + assertTimeNotPassed(stopwatch, LONG_DELAY_MS); + assertNotInterrupted(); + } + + public void testConditionAwaitInterruptedTimeoutExceeded() { + Stopwatch stopwatch = Stopwatch.createStarted(); + Condition condition = TestCondition.create(); + requestInterruptIn(500); + + boolean signaledBeforeTimeout = awaitUninterruptibly(condition, 1000, MILLISECONDS); + + assertFalse(signaledBeforeTimeout); + assertAtLeastTimePassed(stopwatch, 1000); + assertInterrupted(); + } + + public void testConditionAwaitInterruptedTimeoutNotExceeded() { + Stopwatch stopwatch = Stopwatch.createStarted(); + Condition condition = TestCondition.createAndSignalAfter(1000, MILLISECONDS); + requestInterruptIn(500); + + boolean signaledBeforeTimeout = awaitUninterruptibly(condition, 1500, MILLISECONDS); + + assertTrue(signaledBeforeTimeout); + assertTimeNotPassed(stopwatch, LONG_DELAY_MS); + assertInterrupted(); + } + // BlockingQueue.put() tests public void testPutWithNoWait() { Stopwatch stopwatch = Stopwatch.createStarted(); @@ -378,18 +434,18 @@ void assertCompletionNotExpected(long timeout) { assertAtLeastTimePassed(stopwatch, timeout); assertTimeNotPassed(stopwatch, expectedCompletionWaitMillis); } + } - private static void assertAtLeastTimePassed(Stopwatch stopwatch, long expectedMillis) { - long elapsedMillis = stopwatch.elapsed(MILLISECONDS); - /* - * The "+ 5" below is to permit, say, sleep(10) to sleep only 9 milliseconds. We see such - * behavior sometimes when running these tests publicly as part of Guava. "+ 5" is probably - * more generous than it needs to be. - */ - assertTrue( - "Expected elapsed millis to be >= " + expectedMillis + " but was " + elapsedMillis, - elapsedMillis + 5 >= expectedMillis); - } + private static void assertAtLeastTimePassed(Stopwatch stopwatch, long expectedMillis) { + long elapsedMillis = stopwatch.elapsed(MILLISECONDS); + /* + * The "+ 5" below is to permit, say, sleep(10) to sleep only 9 milliseconds. We see such + * behavior sometimes when running these tests publicly as part of Guava. "+ 5" is probably more + * generous than it needs to be. + */ + assertTrue( + "Expected elapsed millis to be >= " + expectedMillis + " but was " + elapsedMillis, + elapsedMillis + 5 >= expectedMillis); } // TODO(cpovirk): Split this into separate CountDownLatch and IncrementableCountDownLatch classes. @@ -672,4 +728,109 @@ private static void assertNotInterrupted() { private static void requestInterruptIn(long millis) { InterruptionUtil.requestInterruptIn(millis, MILLISECONDS); } + + private static class TestCondition implements Condition { + private final Lock lock; + private final Condition condition; + + private TestCondition(Lock lock, Condition condition) { + this.lock = lock; + this.condition = condition; + } + + static TestCondition createAndSignalAfter(long delay, TimeUnit unit) { + final TestCondition testCondition = create(); + + ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(1); + // If signal() fails somehow, we should see a failed test, even without looking at the Future. + Future unused = + scheduledPool.schedule( + new Runnable() { + @Override + public void run() { + testCondition.signal(); + } + }, + delay, + unit); + + return testCondition; + } + + static TestCondition create() { + Lock lock = new ReentrantLock(); + Condition condition = lock.newCondition(); + return new TestCondition(lock, condition); + } + + @Override + public void await() throws InterruptedException { + lock.lock(); + try { + condition.await(); + } finally { + lock.unlock(); + } + } + + @Override + public void awaitUninterruptibly() { + lock.lock(); + try { + condition.awaitUninterruptibly(); + } finally { + lock.unlock(); + } + } + + @Override + public long awaitNanos(long nanosTimeout) throws InterruptedException { + lock.lock(); + try { + return condition.awaitNanos(nanosTimeout); + } finally { + lock.unlock(); + } + } + + @Override + public boolean await(long time, TimeUnit unit) throws InterruptedException { + lock.lock(); + try { + return condition.await(time, unit); + } finally { + lock.unlock(); + } + } + + @Override + public boolean awaitUntil(Date deadline) throws InterruptedException { + lock.lock(); + try { + return condition.awaitUntil(deadline); + } finally { + lock.unlock(); + } + } + + @Override + public void signal() { + lock.lock(); + try { + condition.signal(); + } finally { + lock.unlock(); + } + } + + @Override + public void signalAll() { + lock.lock(); + try { + condition.signalAll(); + } finally { + lock.unlock(); + } + } + } } diff --git a/guava/src/com/google/common/util/concurrent/Uninterruptibles.java b/guava/src/com/google/common/util/concurrent/Uninterruptibles.java index 03637c2103e0..9f332a20820a 100644 --- a/guava/src/com/google/common/util/concurrent/Uninterruptibles.java +++ b/guava/src/com/google/common/util/concurrent/Uninterruptibles.java @@ -29,6 +29,7 @@ import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.concurrent.locks.Condition; /** * Utilities for treating interruptible operations as uninterruptible. In all cases, if a thread is @@ -93,6 +94,34 @@ public static boolean awaitUninterruptibly(CountDownLatch latch, long timeout, T } } + /** + * Invokes {@code condition.}{@link Condition#await(long, TimeUnit) await(timeout, unit)} + * uninterruptibly. + * + * @since NEXT + */ + @GwtIncompatible // concurrency + public static boolean awaitUninterruptibly(Condition condition, long timeout, TimeUnit unit) { + boolean interrupted = false; + try { + long remainingNanos = unit.toNanos(timeout); + long end = System.nanoTime() + remainingNanos; + + while (true) { + try { + return condition.await(remainingNanos, NANOSECONDS); + } catch (InterruptedException e) { + interrupted = true; + remainingNanos = end - System.nanoTime(); + } + } + } finally { + if (interrupted) { + Thread.currentThread().interrupt(); + } + } + } + /** Invokes {@code toJoin.}{@link Thread#join() join()} uninterruptibly. */ @GwtIncompatible // concurrency public static void joinUninterruptibly(Thread toJoin) {