Reputation: 4197
I'm trying to test a method that uses CompletableFuture.allOf()
. This is my method:
static CompletableFuture<byte[]> anySuccess(List<CompletableFuture<byte[]>> futures) {
CompletableFuture<byte[]> delegateFuture = new CompletableFuture<>();
CompletableFuture.allOf(futures.stream().map(s -> s.thenApply(t -> {
if (t == null) {
// Here we treat null as a failed lookup.
// The inner dummy exception serves as a signal for the caller to return null.
throw new RuntimeException();
}
return t;
}).thenAccept(delegateFuture::complete)).toArray(CompletableFuture<?>[]::new))
.exceptionally(ex -> {
delegateFuture.completeExceptionally(ex);
return null;
});
return delegateFuture;
}
The input set of CompletableFutures can complete/are expected to complete in different times. Here's how my unit test looks
@Test
void testAnySuccess() {
final byte[] payload1 = "payload1".getBytes();
final byte[] payload2 = "payload2".getBytes();
List<CompletableFuture<byte[]>> futures = Arrays.asList(
CompletableFuture.supplyAsync(
() -> null
),
CompletableFuture.supplyAsync(
() -> {
LockSupport.parkNanos(Duration.ofSeconds(5).toNanos());
System.out.println("Wait for 5 seconds");
return payload2;
}),
CompletableFuture.supplyAsync(
() -> {
LockSupport.parkNanos(Duration.ofMillis(100).toNanos());
System.out.println("Wait for 100ms");
return payload1;
})
);
long start = System.nanoTime();
CompletableFuture<byte[]> res = anySuccess(futures);
byte[] actualRes = res.join();
Assert.assertEquals(actualRes, payload1);
From what I understand - the allOf()
method isn't blocking - but it returns another CompletableFuture, that is complete only when all the futures in it are complete.
Here's my expected outcome - Return the first non null value, or if all of them are null - then return an exception.
I'm confused on whether I need to use the allOf
-vs- anyOf
method in this particular case. Or do I need to change the case such that I don't use the .exceptionally(...)
method, and try and modify it using thenAccept(...)
Upvotes: 3
Views: 121
Reputation: 9175
anyOf
will not work. It will wait until any future is done, either successfully or exceptionally. That means that it will fail if the quickest future fails.
I think your current code works fine as long as the list is not empty. If it is there will be no call to complete
or completeExceptionally
, and delegateFuture
will never complete. A quick check should solve that.
Once you've ensured the list isn't empty, the result of calling allOf
isn't returned but remains active in the background until all futures are done. Any future with a null
result will fail, but as soon as one has a non-null
value it will complete delegateFuture
. Since both complete
and completeExceptionally
don't do anything if delegateFuture
is already completed, there are two options:
At least one of the futures has a non-null
value and will complete delegateFuture
. All other futures with a non-null
value will effectively be ignored. If any of the futures failed due to a null
value of another exception then completeExceptionally
is called, but this won't do anything since delegateFuture
was already completed.
All of the futures fail due to a null
value or another exception. The exceptionally
block will call completeExceptionally
which will be the final result of delegateFuture
. The exception you get is determined by the first failure.
There is a better solution coming in a future Java version - StructuredTaskScope, especially using StructuredTaskScope.ShutdownOnSuccess
. That will return once the first success is found, and fail if there are no successes. Unlike the solution with allOf
it will not continue with any unnecessary work once the first success is found.
Upvotes: 1