From be04285c4d83b8552ce8151686904a9785db8243 Mon Sep 17 00:00:00 2001 From: Jose Alavez <414v32@gmail.com> Date: Thu, 4 Jun 2020 22:51:48 +0200 Subject: [PATCH] Fail fast implementation for joinList --- .../spotify/futures/CompletableFutures.java | 16 ++++++++++++-- .../futures/CompletableFuturesTest.java | 21 +++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/spotify/futures/CompletableFutures.java b/src/main/java/com/spotify/futures/CompletableFutures.java index fc39804..4242923 100644 --- a/src/main/java/com/spotify/futures/CompletableFutures.java +++ b/src/main/java/com/spotify/futures/CompletableFutures.java @@ -53,7 +53,7 @@ private CompletableFutures() { * Returns a new {@link CompletableFuture} which completes to a list of all values of its input * stages, if all succeed. The list of results is in the same order as the input stages. * - *

If any of the given stages complete exceptionally, then the returned future also does so, + *

As soon as any of the given stages complete exceptionally, then the returned future also does so, * with a {@link CompletionException} holding this exception as its cause. * *

If no stages are provided, returns a future holding an empty list. @@ -75,7 +75,19 @@ public static CompletableFuture> allAsList( for (int i = 0; i < stages.size(); i++) { all[i] = stages.get(i).toCompletableFuture(); } - return CompletableFuture.allOf(all) + + CompletableFuture allOf = CompletableFuture.allOf(all); + + for (int i = 0; i < all.length; i++) { + all[i].exceptionally(throwable -> { + if (!allOf.isDone()) { + allOf.completeExceptionally(throwable); + } + return null; // intentionally unused + }); + } + + return allOf .thenApply(ignored -> { final List result = new ArrayList<>(all.length); for (int i = 0; i < all.length; i++) { diff --git a/src/test/java/com/spotify/futures/CompletableFuturesTest.java b/src/test/java/com/spotify/futures/CompletableFuturesTest.java index 4cb0e00..17ac09f 100644 --- a/src/test/java/com/spotify/futures/CompletableFuturesTest.java +++ b/src/test/java/com/spotify/futures/CompletableFuturesTest.java @@ -37,6 +37,8 @@ import java.util.concurrent.ScheduledFuture; import java.util.function.Supplier; import java.util.stream.Stream; +import java.util.concurrent.CompletionException; +import java.util.concurrent.TimeoutException; import static com.spotify.futures.CompletableFutures.allAsList; import static com.spotify.futures.CompletableFutures.allAsMap; @@ -64,6 +66,7 @@ import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.hasProperty; import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.core.Is.is; import static org.hamcrest.core.Is.isA; @@ -71,6 +74,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyLong; import static org.mockito.Matchers.eq; @@ -125,6 +129,23 @@ public void allAsList_exceptional() throws Exception { allAsList(input).get(); } + @Test + public void allAsList_exceptional_failFast() { + final CompletableFuture incomplete = incompleteFuture(); + final CompletableFuture failed = + exceptionallyCompletedFuture(new TimeoutException()); + final List> input = + asList(incomplete, failed); + + try { + allAsList(input).join(); + fail("Expected exception being thrown."); + } catch (Exception e) { + assertThat(e, instanceOf(CompletionException.class)); + assertThat(e.getCause(), instanceOf(TimeoutException.class)); + } + } + @Test public void allAsList_null() throws Exception { exception.expect(NullPointerException.class);