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);