Recursively cancel an allOf CompletableFuture
Before you make you life harder than necessary, you should become aware of what cancelling a CompletableFuture
actually does. Most important, it does not stop the associated computation.
If a computation associated with a CompletableFuture
is already running, but has not completed yet, cancelling a CompletableFuture
turns it into the “cancelled” state, which may have an immediate effect on all dependent stages, but not on the computation, which will continue until complete, though its attempt to complete the cancelled future will not have any effect.
While other Future
’s might be cancelled with interruption, which will stop the computation, if it checks for interruption, this doesn’t apply to CompletableFuture
, see CompletableFuture.cancel(boolean)
:
Parameters:
mayInterruptIfRunning - this value has no effect in this implementation because interrupts are not used to control processing.
So when you cancel either, future1
or future2
, successfully, the only immediate effect would be the cancellation of many
, which you can also achieve by calling cancel
on many
itself. It would have a broader effect, if there were more dependent stages, but since you stated, that you don’t want to keep references to future1
or future2
, this doesn’t seem to be the case.
The following code demonstrates the behavior:
CompletableFuture<String> supply = CompletableFuture.supplyAsync(() -> {
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(2));
System.out.println("supplying value");
return "foo";
});
CompletableFuture<String> then = supply.thenApply(s -> {
System.out.println("Evaluating next stage");
return s;
});
CompletableFuture<?> last = then.handle((s,t) -> {
System.out.println("last stage: value: "+s+", throwable: "+t);
return "";
});
System.out.println("cancelling: "+supply.cancel(true));
ForkJoinPool.commonPool().awaitQuiescence(1, TimeUnit.DAYS);
This code reproducible prints:
last stage: value: null, throwable: java.util.concurrent.CompletionException: java.util.concurrent.CancellationException
canceling: true
supplying value
(the order might change)
regardless of whether you call supply.cancel(true)
or then.cancel(true)
or whether you pass true
or false
; it won’t stop the ongoing Supplier
evaluation.
There will be a difference, if the associated computation hasn’t been started yet and it does check the cancellation state when starting, like with the actions produced by the convenience methods in CompletableFuture
. This is a rare situation, as normally, your service.request(paramN)
call is supposed to trigger the evaluation.
It’s a fundamental property of the CompletableFuture
, as its name suggests, that it is completable, i.e. anyone could call complete
on it, thus, the CompletableFuture
can’t control whoever might eventually call complete
on it in the future. So all, cancel
can achieve, is to set it to the cancelled state, which implies ignoring subsequent completion attempts and propagating the cancellation downward to the dependent actions.
So the bottom line is that you might already be fine with just calling cancel
on the many
instance, because calling cancel
on future1
and future2
is unlikely to have an effect that is worth the complication of your code.
The tree constructed by CompletableFuture.allOf
doesn't hold any references to the given instances of CompletableFuture
. Instead if just builds completion tree, which is is completed when all of the given CompletableFutures complete (from JavaDocs).
So probably you have to keep references to all CompletableFuture
in order to cancel them sequentially when it is needed.