diff --git a/android/guava-tests/test/com/google/common/util/concurrent/FuturesTest.java b/android/guava-tests/test/com/google/common/util/concurrent/FuturesTest.java index a6bf1df8fde6..87f1089ce987 100644 --- a/android/guava-tests/test/com/google/common/util/concurrent/FuturesTest.java +++ b/android/guava-tests/test/com/google/common/util/concurrent/FuturesTest.java @@ -2634,6 +2634,141 @@ public ListenableFuture call() throws Exception { gotException.await(); } + public void testWhenAllComplete_runnableResult() throws Exception { + final SettableFuture futureInteger = SettableFuture.create(); + final SettableFuture futureBoolean = SettableFuture.create(); + final String[] result = new String[1]; + Runnable combiner = + new Runnable() { + @Override + public void run() { + assertTrue(futureInteger.isDone()); + assertTrue(futureBoolean.isDone()); + result[0] = + createCombinedResult( + Futures.getUnchecked(futureInteger), Futures.getUnchecked(futureBoolean)); + } + }; + + ListenableFuture futureResult = + whenAllComplete(futureInteger, futureBoolean).run(combiner, directExecutor()); + Integer integerPartial = 1; + futureInteger.set(integerPartial); + Boolean booleanPartial = true; + futureBoolean.set(booleanPartial); + futureResult.get(); + assertEquals(createCombinedResult(integerPartial, booleanPartial), result[0]); + } + + public void testWhenAllComplete_runnableError() throws Exception { + final RuntimeException thrown = new RuntimeException("test"); + + final SettableFuture futureInteger = SettableFuture.create(); + final SettableFuture futureBoolean = SettableFuture.create(); + Runnable combiner = + new Runnable() { + @Override + public void run() { + assertTrue(futureInteger.isDone()); + assertTrue(futureBoolean.isDone()); + throw thrown; + } + }; + + ListenableFuture futureResult = + whenAllComplete(futureInteger, futureBoolean).run(combiner, directExecutor()); + Integer integerPartial = 1; + futureInteger.set(integerPartial); + Boolean booleanPartial = true; + futureBoolean.set(booleanPartial); + + try { + getDone(futureResult); + fail(); + } catch (ExecutionException expected) { + assertSame(thrown, expected.getCause()); + } + } + + @GwtIncompatible // threads + + public void testWhenAllCompleteRunnable_resultCanceledWithoutInterrupt_doesNotInterruptRunnable() + throws Exception { + SettableFuture stringFuture = SettableFuture.create(); + SettableFuture booleanFuture = SettableFuture.create(); + final CountDownLatch inFunction = new CountDownLatch(1); + final CountDownLatch shouldCompleteFunction = new CountDownLatch(1); + final CountDownLatch combinerCompletedWithoutInterrupt = new CountDownLatch(1); + Runnable combiner = + new Runnable() { + @Override + public void run() { + inFunction.countDown(); + try { + shouldCompleteFunction.await(); + combinerCompletedWithoutInterrupt.countDown(); + } catch (InterruptedException e) { + // Ensure the thread's interrupt status is preserved. + Thread.currentThread().interrupt(); + throw new RuntimeException(e); + } + } + }; + + ListenableFuture futureResult = + whenAllComplete(stringFuture, booleanFuture).run(combiner, newSingleThreadExecutor()); + + stringFuture.set("value"); + booleanFuture.set(true); + inFunction.await(); + futureResult.cancel(false); + shouldCompleteFunction.countDown(); + try { + futureResult.get(); + fail(); + } catch (CancellationException expected) { + } + combinerCompletedWithoutInterrupt.await(); + } + + @GwtIncompatible // threads + + public void testWhenAllCompleteRunnable_resultCanceledWithInterrupt_InterruptsRunnable() + throws Exception { + SettableFuture stringFuture = SettableFuture.create(); + SettableFuture booleanFuture = SettableFuture.create(); + final CountDownLatch inFunction = new CountDownLatch(1); + final CountDownLatch gotException = new CountDownLatch(1); + Runnable combiner = + new Runnable() { + @Override + public void run() { + inFunction.countDown(); + try { + new CountDownLatch(1).await(); // wait for interrupt + } catch (InterruptedException expected) { + // Ensure the thread's interrupt status is preserved. + Thread.currentThread().interrupt(); + gotException.countDown(); + } + } + }; + + ListenableFuture futureResult = + whenAllComplete(stringFuture, booleanFuture).run(combiner, newSingleThreadExecutor()); + + stringFuture.set("value"); + booleanFuture.set(true); + inFunction.await(); + futureResult.cancel(true); + try { + futureResult.get(); + fail(); + } catch (CancellationException expected) { + } + gotException.await(); + } + public void testWhenAllSucceed() throws Exception { class PartialResultException extends Exception { diff --git a/android/guava/src/com/google/common/util/concurrent/Futures.java b/android/guava/src/com/google/common/util/concurrent/Futures.java index aeeef81ec734..83f7e5945dbb 100644 --- a/android/guava/src/com/google/common/util/concurrent/Futures.java +++ b/android/guava/src/com/google/common/util/concurrent/Futures.java @@ -988,7 +988,7 @@ public ListenableFuture callAsync(AsyncCallable combiner) { * *

Canceling this future will attempt to cancel all the component futures. */ - @CanIgnoreReturnValue + @CanIgnoreReturnValue // TODO(cpovirk): Remove this public ListenableFuture call(Callable combiner, Executor executor) { return new CombinedFuture(futures, allMustSucceed, executor, combiner); } @@ -1003,17 +1003,32 @@ public ListenableFuture call(Callable combiner, Executor executor) { * ListenableFuture#addListener ListenableFuture.addListener} documentation. This method is * scheduled to be removed in April 2018. */ - @CanIgnoreReturnValue + @CanIgnoreReturnValue // TODO(cpovirk): Remove this @Deprecated public ListenableFuture call(Callable combiner) { return call(combiner, directExecutor()); } - /* - * TODO(cpovirk): Evaluate demand for a run(Runnable) version. Would it allow us to remove - * @CanIgnoreReturnValue from the call() methods above? - * https://github.com/google/guava/issues/2371 + /** + * Creates the {@link ListenableFuture} which will return the result of running {@code combiner} + * when all Futures complete. {@code combiner} will run using {@code executor}. + * + *

If the combiner throws a {@code CancellationException}, the returned future will be + * cancelled. + * + *

Canceling this Future will attempt to cancel all the component futures. */ + public ListenableFuture run(final Runnable combiner, Executor executor) { + return call( + new Callable() { + @Override + public Void call() throws Exception { + combiner.run(); + return null; + } + }, + executor); + } } /** diff --git a/guava-gwt/test/com/google/common/util/concurrent/FuturesTest_gwt.java b/guava-gwt/test/com/google/common/util/concurrent/FuturesTest_gwt.java index aa13681e9a60..cd564f82b05e 100644 --- a/guava-gwt/test/com/google/common/util/concurrent/FuturesTest_gwt.java +++ b/guava-gwt/test/com/google/common/util/concurrent/FuturesTest_gwt.java @@ -3096,6 +3096,60 @@ public void testWhenAllComplete_asyncResult() throws Exception { } } +public void testWhenAllComplete_runnableError() throws Exception { + com.google.common.util.concurrent.FuturesTest testCase = new com.google.common.util.concurrent.FuturesTest(); + testCase.setUp(); + Throwable failure = null; + try { + testCase.testWhenAllComplete_runnableError(); + } catch (Throwable t) { + failure = t; + } + try { + testCase.tearDown(); + } catch (Throwable t) { + if (failure == null) { + failure = t; + } + } + if (failure instanceof Exception) { + throw (Exception) failure; + } + if (failure instanceof Error) { + throw (Error) failure; + } + if (failure != null) { + throw new RuntimeException(failure); + } +} + +public void testWhenAllComplete_runnableResult() throws Exception { + com.google.common.util.concurrent.FuturesTest testCase = new com.google.common.util.concurrent.FuturesTest(); + testCase.setUp(); + Throwable failure = null; + try { + testCase.testWhenAllComplete_runnableResult(); + } catch (Throwable t) { + failure = t; + } + try { + testCase.tearDown(); + } catch (Throwable t) { + if (failure == null) { + failure = t; + } + } + if (failure instanceof Exception) { + throw (Exception) failure; + } + if (failure instanceof Error) { + throw (Error) failure; + } + if (failure != null) { + throw new RuntimeException(failure); + } +} + public void testWhenAllComplete_wildcard() throws Exception { com.google.common.util.concurrent.FuturesTest testCase = new com.google.common.util.concurrent.FuturesTest(); testCase.setUp(); diff --git a/guava-tests/test/com/google/common/util/concurrent/FuturesTest.java b/guava-tests/test/com/google/common/util/concurrent/FuturesTest.java index a6bf1df8fde6..87f1089ce987 100644 --- a/guava-tests/test/com/google/common/util/concurrent/FuturesTest.java +++ b/guava-tests/test/com/google/common/util/concurrent/FuturesTest.java @@ -2634,6 +2634,141 @@ public ListenableFuture call() throws Exception { gotException.await(); } + public void testWhenAllComplete_runnableResult() throws Exception { + final SettableFuture futureInteger = SettableFuture.create(); + final SettableFuture futureBoolean = SettableFuture.create(); + final String[] result = new String[1]; + Runnable combiner = + new Runnable() { + @Override + public void run() { + assertTrue(futureInteger.isDone()); + assertTrue(futureBoolean.isDone()); + result[0] = + createCombinedResult( + Futures.getUnchecked(futureInteger), Futures.getUnchecked(futureBoolean)); + } + }; + + ListenableFuture futureResult = + whenAllComplete(futureInteger, futureBoolean).run(combiner, directExecutor()); + Integer integerPartial = 1; + futureInteger.set(integerPartial); + Boolean booleanPartial = true; + futureBoolean.set(booleanPartial); + futureResult.get(); + assertEquals(createCombinedResult(integerPartial, booleanPartial), result[0]); + } + + public void testWhenAllComplete_runnableError() throws Exception { + final RuntimeException thrown = new RuntimeException("test"); + + final SettableFuture futureInteger = SettableFuture.create(); + final SettableFuture futureBoolean = SettableFuture.create(); + Runnable combiner = + new Runnable() { + @Override + public void run() { + assertTrue(futureInteger.isDone()); + assertTrue(futureBoolean.isDone()); + throw thrown; + } + }; + + ListenableFuture futureResult = + whenAllComplete(futureInteger, futureBoolean).run(combiner, directExecutor()); + Integer integerPartial = 1; + futureInteger.set(integerPartial); + Boolean booleanPartial = true; + futureBoolean.set(booleanPartial); + + try { + getDone(futureResult); + fail(); + } catch (ExecutionException expected) { + assertSame(thrown, expected.getCause()); + } + } + + @GwtIncompatible // threads + + public void testWhenAllCompleteRunnable_resultCanceledWithoutInterrupt_doesNotInterruptRunnable() + throws Exception { + SettableFuture stringFuture = SettableFuture.create(); + SettableFuture booleanFuture = SettableFuture.create(); + final CountDownLatch inFunction = new CountDownLatch(1); + final CountDownLatch shouldCompleteFunction = new CountDownLatch(1); + final CountDownLatch combinerCompletedWithoutInterrupt = new CountDownLatch(1); + Runnable combiner = + new Runnable() { + @Override + public void run() { + inFunction.countDown(); + try { + shouldCompleteFunction.await(); + combinerCompletedWithoutInterrupt.countDown(); + } catch (InterruptedException e) { + // Ensure the thread's interrupt status is preserved. + Thread.currentThread().interrupt(); + throw new RuntimeException(e); + } + } + }; + + ListenableFuture futureResult = + whenAllComplete(stringFuture, booleanFuture).run(combiner, newSingleThreadExecutor()); + + stringFuture.set("value"); + booleanFuture.set(true); + inFunction.await(); + futureResult.cancel(false); + shouldCompleteFunction.countDown(); + try { + futureResult.get(); + fail(); + } catch (CancellationException expected) { + } + combinerCompletedWithoutInterrupt.await(); + } + + @GwtIncompatible // threads + + public void testWhenAllCompleteRunnable_resultCanceledWithInterrupt_InterruptsRunnable() + throws Exception { + SettableFuture stringFuture = SettableFuture.create(); + SettableFuture booleanFuture = SettableFuture.create(); + final CountDownLatch inFunction = new CountDownLatch(1); + final CountDownLatch gotException = new CountDownLatch(1); + Runnable combiner = + new Runnable() { + @Override + public void run() { + inFunction.countDown(); + try { + new CountDownLatch(1).await(); // wait for interrupt + } catch (InterruptedException expected) { + // Ensure the thread's interrupt status is preserved. + Thread.currentThread().interrupt(); + gotException.countDown(); + } + } + }; + + ListenableFuture futureResult = + whenAllComplete(stringFuture, booleanFuture).run(combiner, newSingleThreadExecutor()); + + stringFuture.set("value"); + booleanFuture.set(true); + inFunction.await(); + futureResult.cancel(true); + try { + futureResult.get(); + fail(); + } catch (CancellationException expected) { + } + gotException.await(); + } + public void testWhenAllSucceed() throws Exception { class PartialResultException extends Exception { diff --git a/guava/src/com/google/common/util/concurrent/Futures.java b/guava/src/com/google/common/util/concurrent/Futures.java index aeeef81ec734..83f7e5945dbb 100644 --- a/guava/src/com/google/common/util/concurrent/Futures.java +++ b/guava/src/com/google/common/util/concurrent/Futures.java @@ -988,7 +988,7 @@ public ListenableFuture callAsync(AsyncCallable combiner) { * *

Canceling this future will attempt to cancel all the component futures. */ - @CanIgnoreReturnValue + @CanIgnoreReturnValue // TODO(cpovirk): Remove this public ListenableFuture call(Callable combiner, Executor executor) { return new CombinedFuture(futures, allMustSucceed, executor, combiner); } @@ -1003,17 +1003,32 @@ public ListenableFuture call(Callable combiner, Executor executor) { * ListenableFuture#addListener ListenableFuture.addListener} documentation. This method is * scheduled to be removed in April 2018. */ - @CanIgnoreReturnValue + @CanIgnoreReturnValue // TODO(cpovirk): Remove this @Deprecated public ListenableFuture call(Callable combiner) { return call(combiner, directExecutor()); } - /* - * TODO(cpovirk): Evaluate demand for a run(Runnable) version. Would it allow us to remove - * @CanIgnoreReturnValue from the call() methods above? - * https://github.com/google/guava/issues/2371 + /** + * Creates the {@link ListenableFuture} which will return the result of running {@code combiner} + * when all Futures complete. {@code combiner} will run using {@code executor}. + * + *

If the combiner throws a {@code CancellationException}, the returned future will be + * cancelled. + * + *

Canceling this Future will attempt to cancel all the component futures. */ + public ListenableFuture run(final Runnable combiner, Executor executor) { + return call( + new Callable() { + @Override + public Void call() throws Exception { + combiner.run(); + return null; + } + }, + executor); + } } /**