Skip to content

Commit

Permalink
Add Uninterruptibles methods for Condition.
Browse files Browse the repository at this point in the history
Fixes #3010, #2870

RELNOTES=Add Uninterruptibles methods for Condition.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=178964181
  • Loading branch information
cpiotr authored and cpovirk committed Dec 13, 2017
1 parent 201af43 commit 794a8ca
Show file tree
Hide file tree
Showing 4 changed files with 402 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

/**
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
Loading

0 comments on commit 794a8ca

Please sign in to comment.