Future.cancel() return value was pretty consistent before CompletableFuture
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
Future<Void> foo = executor.submit(() -> {
try {
Thread.sleep(TimeUnit.DAYS.toMillis(1));
} catch (InterruptedException e) {
//ignore
}
return null;
});
System.out.println(foo.cancel(true));
System.out.println(foo.cancel(true));
executor.shutdown();
prints
true
false
Guava
Future<Void> foo = SettableFuture.create();
System.out.println(foo.cancel(true));
System.out.println(foo.cancel(true));
prints
true
false
Spring
Future<Void> foo = new SettableListenableFuture<>();
System.out.println(foo.cancel(true));
System.out.println(foo.cancel(true));
prints
true
false
However CompletableFuture
Future<Void> foo = new CompletableFuture<>();
System.out.println(foo.cancel(true));
System.out.println(foo.cancel(true));
prints
true
true
and states in javadoc that it `return true if this task is now cancelled
But the following code:
CompletableFuture<Void> foo = new SettableListenableFuture<Void>().completable();
System.out.println(foo.cancel(true));
System.out.println(foo.cancel(true));
prints
true
false
AFAIU Future interface does not specify strictly what should cancel() return for subsequent invocations, CompletableFuture specified it differently than major other implementations and now your DelegatingCompletableFuture violates this specification(?) returning to previous de-facto standard.
I don't know whether this should be fixed in DelegatingCompletableFuture, but it seems that for the consumers of Future-based API it is better to avoid using cancel return value.
Note that in Reactor:
CompletableFuture<Void> foo = Mono.<Void>fromFuture(CompletableFuture::new).toFuture();
System.out.println(foo.cancel(true));
System.out.println(foo.cancel(true));
prints
true
true
since reactor.core.publisher.MonoToCompletableFuture.cancel() logic for return value relies on super.