Reputation: 35171
Is it ever correct to not return or join on a CompletableFuture?
That is, is this code ever correct?
void noJoin() {
CompletableFuture<Void> cfa = CompletableFuture
.supplyAsync(() -> "a")
.thenAccept(this::someSideEffect);
CompletableFuture<Void> cfb = CompletableFuture
.supplyAsync(() -> "b")
.thenAccept(this::someSideEffect);
}
In the above code, we never know if the Futures complete successfully, or at all. (And I don't even know that the Futures created in noJoin are not eligible for garbage collection; but presumably the ForkJoinPool.commonPool() holds onto those references?)
In any case, if it truly doesn't matter to our program whether or not those Futures succeed, a better implementation of noJoin
is just a no-op:
void noJoin() {}
If on the other hand, it does matter, we need to either join on or return both Futures, by composing them (either serially with thenCompose, or in parallel with thenCombine or allOf) and then join the composed Future or return it to our caller.
By joining, we block and throw an exception if either Future is exceptional; or better, by returning a Future, we remain asynchronous while ensuring that our caller gets a Future that holds any exceptional result:
Future<Void> returnBothAsync() {
CompletableFuture<Void> cfa = CompletableFuture
.supplyAsync(() -> "a")
.thenAccept(this::someSideEffect);
CompletableFuture<Void> cfb = CompletableFuture
.supplyAsync(() -> "b")
.thenAccept(this::someSideEffect);
return CompletableFuture.allOf(cfa, cfb);
}
or
void joinBothBlocking() {
CompletableFuture<Void> cfa = CompletableFuture
.supplyAsync(() -> "a")
.thenAccept(this::someSideEffect);
CompletableFuture<Void> cfb = CompletableFuture
.supplyAsync(() -> "b")
.thenAccept(this::someSideEffect);
CompletableFuture.allOf(cfa, cfb).get(50L, TimeUnit.MILLISECONDS);
}
I think this is true even if we arrange to handle all exceptions:
void noJoin() {
CompletableFuture<Void> cfa = CompletableFuture
.supplyAsync(() -> "a")
.thenAccept(this::someSideEffect)
.exceptionally(e -> {
Logger.log(e);
return DEFAULT;
});
CompletableFuture<Void> cfb = CompletableFuture
.supplyAsync(() -> "b")
.thenAccept(this::someSideEffect);
}
because even if the exceptional are handled / "can't happen", we still don't know if the Future ever completed at all.
Or am I wrong, and there are cases where code like that in noJoin
is correct?
Upvotes: 4
Views: 2829
Reputation: 6934
This is not a complete answer to your question. It probably depends on the exact use case to be able to tell how to handle CompletableFuture
and their results.
If you choose to not wait for the results of the CompletableFuture
s, you will likely have to make sure that the used executor finishes all tasks. In your case the used executor is ForkJoinPool.commonPool() whose documentation says:
[...] However this pool and any ongoing processing are automatically terminated upon program System.exit(int). Any program that relies on asynchronous task processing to complete before program termination should invoke commonPool().awaitQuiescence, before exit.
Upvotes: 3