blob: 6ec5620baca26f445043b4e2bf46abe2eea1832b [file] [log] [blame]
/*
* Copyright (C) 2009 The Guava Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.common.util.concurrent;
import static com.google.common.util.concurrent.InterruptionUtil.repeatedlyInterruptTestThread;
import static com.google.common.util.concurrent.Uninterruptibles.getUninterruptibly;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;
import com.google.common.testing.TearDown;
import com.google.common.testing.TearDownStack;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import junit.framework.TestCase;
// TODO(azana/cpovirk): Should this be merged into UninterruptiblesTest?
/**
* Unit test for {@link Uninterruptibles#getUninterruptibly}
*
* @author Kevin Bourrillion
* @author Chris Povirk
*/
public class UninterruptibleFutureTest extends TestCase {
private SleepingRunnable sleeper;
private Future<Boolean> delayedFuture;
private final TearDownStack tearDownStack = new TearDownStack();
@Override
protected void setUp() {
final ExecutorService executor = Executors.newSingleThreadExecutor();
tearDownStack.addTearDown(
new TearDown() {
@Override
public void tearDown() {
executor.shutdownNow();
}
});
sleeper = new SleepingRunnable(1000);
delayedFuture = executor.submit(sleeper, true);
tearDownStack.addTearDown(
new TearDown() {
@Override
public void tearDown() {
Thread.interrupted();
}
});
}
@Override
protected void tearDown() {
tearDownStack.runTearDown();
}
/**
* This first test doesn't test anything in Uninterruptibles, just demonstrates some normal
* behavior of futures so that you can contrast the next test with it.
*/
public void testRegularFutureInterrupted() throws ExecutionException {
/*
* Here's the order of events that we want.
*
* 1. The client thread begins to block on a get() call to a future.
* 2. The client thread is interrupted sometime before the result would be
* available.
* 3. We expect the client's get() to throw an InterruptedException.
* 4. We expect the client thread's interrupt state to be false.
* 5. The client thread again makes a blocking call to get().
* 6. Now the result becomes available.
* 7. We expect get() to return this result.
* 8. We expect the test thread's interrupt state to be false.
*/
InterruptionUtil.requestInterruptIn(200, TimeUnit.MILLISECONDS);
assertFalse(Thread.interrupted());
try {
delayedFuture.get(10000, TimeUnit.MILLISECONDS);
fail("expected to be interrupted");
} catch (InterruptedException expected) {
} catch (TimeoutException e) {
throw new RuntimeException(e);
}
// we were interrupted, but it's been cleared now
assertFalse(Thread.interrupted());
assertFalse(sleeper.completed);
try {
assertTrue(delayedFuture.get());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
assertTrue(sleeper.completed);
}
public void testMakeUninterruptible_timeoutPreservedThroughInterruption()
throws ExecutionException {
repeatedlyInterruptTestThread(100, tearDownStack);
try {
getUninterruptibly(delayedFuture, 500, TimeUnit.MILLISECONDS);
fail("expected to time out");
} catch (TimeoutException expected) {
}
assertTrue(Thread.interrupted()); // clears the interrupt state, too
assertFalse(sleeper.completed);
assertTrue(getUninterruptibly(delayedFuture));
assertTrue(Thread.interrupted()); // clears the interrupt state, too
assertTrue(sleeper.completed);
}
private static class SleepingRunnable implements Runnable {
final int millis;
volatile boolean completed;
public SleepingRunnable(int millis) {
this.millis = millis;
}
@Override
public void run() {
try {
Thread.sleep(millis);
} catch (InterruptedException wontHappen) {
throw new AssertionError();
}
completed = true;
}
}
public void testMakeUninterruptible_untimed_uninterrupted() throws Exception {
runUntimedInterruptsTest(0);
}
public void testMakeUninterruptible_untimed_interrupted() throws Exception {
runUntimedInterruptsTest(1);
}
public void testMakeUninterruptible_untimed_multiplyInterrupted() throws Exception {
runUntimedInterruptsTest(38);
}
public void testMakeUninterruptible_timed_uninterrupted() throws Exception {
runTimedInterruptsTest(0);
}
public void testMakeUninterruptible_timed_interrupted() throws Exception {
runTimedInterruptsTest(1);
}
public void testMakeUninterruptible_timed_multiplyInterrupted() throws Exception {
runTimedInterruptsTest(38);
}
private static void runUntimedInterruptsTest(int times)
throws InterruptedException, ExecutionException, TimeoutException {
SettableFuture<String> future = SettableFuture.create();
FutureTask<Boolean> interruptReporter = untimedInterruptReporter(future, false);
runNInterruptsTest(times, future, interruptReporter);
}
private static void runTimedInterruptsTest(int times)
throws InterruptedException, ExecutionException, TimeoutException {
SettableFuture<String> future = SettableFuture.create();
FutureTask<Boolean> interruptReporter = timedInterruptReporter(future);
runNInterruptsTest(times, future, interruptReporter);
}
private static void runNInterruptsTest(
int times, SettableFuture<String> future, FutureTask<Boolean> interruptReporter)
throws InterruptedException, ExecutionException, TimeoutException {
Thread waitingThread = new Thread(interruptReporter);
waitingThread.start();
for (int i = 0; i < times; i++) {
waitingThread.interrupt();
}
future.set(RESULT);
assertEquals(times > 0, (boolean) interruptReporter.get(20, SECONDS));
}
/**
* Confirms that the test code triggers {@link InterruptedException} in a standard {@link Future}.
*/
public void testMakeUninterruptible_plainFutureSanityCheck() throws Exception {
SettableFuture<String> future = SettableFuture.create();
FutureTask<Boolean> wasInterrupted = untimedInterruptReporter(future, true);
Thread waitingThread = new Thread(wasInterrupted);
waitingThread.start();
waitingThread.interrupt();
try {
wasInterrupted.get();
fail();
} catch (ExecutionException expected) {
assertTrue(
expected.getCause().toString(), expected.getCause() instanceof InterruptedException);
}
}
public void testMakeUninterruptible_timedGetZeroTimeoutAttempted()
throws TimeoutException, ExecutionException {
SettableFuture<String> future = SettableFuture.create();
future.set(RESULT);
/*
* getUninterruptibly should call the timed get method once with a
* wait of 0 seconds (and it should succeed, since the result is already
* available).
*/
assertEquals(RESULT, getUninterruptibly(future, 0, SECONDS));
}
public void testMakeUninterruptible_timedGetNegativeTimeoutAttempted()
throws TimeoutException, ExecutionException {
SettableFuture<String> future = SettableFuture.create();
future.set(RESULT);
/*
* The getUninterruptibly should call the timed get method once with a
* wait of -1 seconds (and it should succeed, since the result is already
* available).
*/
assertEquals(RESULT, getUninterruptibly(future, -1, SECONDS));
}
private static FutureTask<Boolean> untimedInterruptReporter(
final Future<?> future, final boolean allowInterruption) {
return new FutureTask<>(
new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
Object actual;
if (allowInterruption) {
actual = future.get();
} else {
actual = getUninterruptibly(future);
}
assertEquals(RESULT, actual);
return Thread.interrupted();
}
});
}
private static FutureTask<Boolean> timedInterruptReporter(final Future<?> future) {
return new FutureTask<>(
new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
assertEquals(RESULT, getUninterruptibly(future, 10, MINUTES));
return Thread.interrupted();
}
});
}
private static final String RESULT = "result";
}