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?
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 CompletableFutures, 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.
Related
I have two service calls:
String call1() { ... return "ok"; }
void call2(String) { ... }
I know the basic way for CompletableFuture with callback is like
CompletableFuture<Void> future = CompletableFuture
.supplyAsync(() -> call1())
.thenAccept(s -> call2(s));
future.join();
What if I separate the two chained CompletableFutures, like:
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> call1());
CompletableFuture<Void> future2 = future1.thenAccept(s -> call2(s));
future1.join(); // will call2 be executed at this time?
Is this any different from calling join() on future2:
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> call1());
CompletableFuture<Void> future2 = future1.thenAccept(s -> call2(s));
future2.join();
What if I call join() on both of the futures?
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> call1());
CompletableFuture<Void> future2 = future1.thenAccept(s -> call2(s));
future1.join();
future2.join();
It seems they are all the same from running my sample code. But I feel something might be wrong somewhere. Thanks!
They are not the same.
In short, you can look at it as future1 and future2 hold results of distinct tasks (even if future2 uses the result of future1, it's a different future).
future1.join() will block until () -> call1() ends, and future2's task won't start until then. future2.join() will wait until s -> call2(s) is done.
What if I separate the two chained CompletableFutures, like:
This makes no difference as far as task execution is concerned. It's either a question of style or it only matters when you need to use the two future objects separately.
What if I call join() on both of the futures?
It's redundant in this case to call future1.join() as you are not doing anything between the two .join calls. It would make sense if you wanted to perform some action "after completion of task1 and before the completion of task 2".
In this case, though, calling future2.join() is enough.
And the code snippet below should show how this behaves:
public static void main(String[] args) {
CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> delay());
CompletableFuture<Void> future2 = future1.thenRun(() -> delay());
long start = System.currentTimeMillis();
future1.join();
System.out.println("Future 1 done. Waiting for future 2: "
+ (System.currentTimeMillis() - start));
future2.join();
System.out.println("Future 2 complete: "
+ (System.currentTimeMillis() - start));
}
static void delay() {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
As it is, this code outputs:
Future 1 done. Waiting for future 2: 5001
Future 2 complete: 10001
But when you remove future1.join(), the output becomes:
Future 2 complete: 10001
Which simply means that future1.join() is superfluous unless you have actions to perform between the completions of the two futures
The supplyAsync and thenAccept methods should be executed on a separate thread automatically according to the documentation:
All async methods without an explicit Executor argument are performed using the ForkJoinPool.commonPool()
In your examples, the only difference is your thread waits on different events before continuing due to the joining. Here is the breakdown:
CompletableFuture<Void> future = CompletableFuture
.supplyAsync(() -> call1())
.thenAccept(s -> call2(s));
future.join();
This will wait until call2 completes because that is the future returned by thenAccept method.
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> call1());
CompletableFuture<Void> future2 = future1.thenAccept(s -> call2(s));
future1.join(); // will call2 be executed at this time?
This will only wait until call1 completes and moves on. call2 will still gets executed.
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> call1());
CompletableFuture<Void> future2 = future1.thenAccept(s -> call2(s));
future2.join();
This is identical to the first one.
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> call1());
CompletableFuture<Void> future2 = future1.thenAccept(s -> call2(s));
future1.join();
future2.join();
Calling future1.join() ends when call1 completed, then future2.join() ends when call2 completed. This should be identical functionally with the first one as well.
When will CompletableFuture releases thread back to ThreadPool ? Is it after calling get() method or after the associated task is completed?
There is no relationship between a get call and a thread from a pool. There isn’t even a relationship between the future’s completion and a thread.
A CompletableFuture can be completed from anywhere, e.g. by calling complete on it. When you use one of the convenience methods to schedule a task to an executor that will eventually attempt to complete it, then the thread will be used up to that point, when the completion attempt is made, regardless of whether the future is already completed or not.
For example,
CompletableFuture<String> f = CompletableFuture.supplyAsync(() -> "hello");
is semantically equivalent to
CompletableFuture<String> f = new CompletableFuture<>();
ForkJoinPool.commonPool().execute(() -> {
try {
f.complete("hello");
} catch(Throwable t) {
f.completeExceptionally(t);
}
});
It should be obvious that neither, the thread pool nor the scheduled action care for whether someone invokes get() or join() on the future or not.
Even when you complete the future earlier, e.g. via complete("some other string") or via cancel(…), it has no effect on the ongoing computation, as there is no reference from the future to the job. As the documentation of cancel states:
Parameters:
mayInterruptIfRunning - this value has no effect in this implementation because interrupts are not used to control processing.
Given the explanation above, it should be clear why.
There is a difference when you create a dependency chain, e.g. via b = a.thenApply(function). The job which will evaluate the function will not get submitted before a completed. By the time a completed, the completion status of b will be rechecked before the evaluation of function starts. If b has been completed at that time, the evaluation might get skipped.
CompletableFuture<String> a = CompletableFuture.supplyAsync(() -> {
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
return "foo";
});
CompletableFuture<String> b = a.thenApplyAsync(string -> {
System.out.println("starting to evaluate "+string);
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(2));
System.out.println("finishing to evaluate "+string);
return string.toUpperCase();
});
b.complete("faster");
System.out.println(b.join());
ForkJoinPool.commonPool().awaitQuiescence(1, TimeUnit.DAYS);
will just print
faster
But once the evaluation started, it can’t be stopped, so
CompletableFuture<String> a = CompletableFuture.supplyAsync(() -> {
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
return "foo";
});
CompletableFuture<String> b = a.thenApplyAsync(string -> {
System.out.println("starting to evaluate "+string);
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(2));
System.out.println("finishing to evaluate "+string);
return string.toUpperCase();
});
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(2));
b.complete("faster");
System.out.println(b.join());
ForkJoinPool.commonPool().awaitQuiescence(1, TimeUnit.DAYS);
will print
starting to evaluate foo
faster
finishing to evaluate foo
showing that even by the time you successfully retrieved the value from the earlier completed future, there might be a still running background computation that will attempt to complete the future. But subsequent completion attempts will just be ignored.
I have following requirements.
CreateDocument
For document create many release notes (releaseNotesFuture)
For document create many parcels (parcelsFuture)
return objectId of document created in 1.
this is my current code:
public CompletableFuture<ObjectId> createDeliveryNoteDocument(String productId, List<String> releaseNotesIds) {
CompletableFuture<ObjectId> deliveryNoteFuture =
CompletableFuture
.supplyAsync(() -> sequenceServiceFeignClient.getNextValueForSequenceNameNoResponseEntity(DocumentType.DELIVERYNOTE.toString()))
.whenComplete((result, error) -> {
if (error != null)
logger.error("Unable to get next sequence number for DELIVERYNOTE sequence", error);
})
.thenCompose(seqNumber -> {
Set<ObjectAttribute> objectAttributes = new HashSet<>();
objectAttributes.add(new ObjectAttribute(Constants.Document.DOCUMENT_TYPE, DocumentType.DELIVERYNOTE.toString()));
objectAttributes.add(new ObjectAttribute(Constants.Document.DOCUMENT_NO, seqNumber));
objectAttributes.add(new ObjectAttribute(Constants.Document.PRODUCT_ID, productId));
return objectCommandService.createCustomObject(new ObjectTypeTableName(Constants.ObjectTables.DOCUMENT), objectAttributes);
});
CompletableFuture<Void> releaseNotesFuture =
deliveryNoteFuture
.thenComposeAsync(deliveryNoteId -> joinReleaseNotesWithDeliveryNote(deliveryNoteId, releaseNotesIds));
CompletableFuture<Void> parcelsFuture =
deliveryNoteFuture
.thenComposeAsync(deliveryNoteId -> changeParcelsStatusForReleaseNotes(releaseNotesIds));
return deliveryNoteFuture;
}
how could I wait for releaseNotesFuture and parcelsFuturecompletion and then return deliveryNoteFuture result or error if any of releaseNotesFuture or parcelsFuture finished exceptionally?
Instead of returning deliveryNoteFuture, you'll have to have a CompletableFuture that is completed when the releaseNotesFuture and parcelsFuture are completed. Furthermore, you'll want that future to then compose into the result of deliveryNoteFuture, since you want its ObjectId if the whole chain is successful.
Something like
return CompletableFuture.allOf(releaseNotesFuture, parcelsFuture).thenComposeAsync(r -> deliveryNoteFuture);
Since both releaseNotesFuture and parcelsFuture are dependent on deliveryNoteFuture, errors from deliveryNoteFuture will propagate through all these chains.
Similarly, if either of releaseNotesFuture or parcelsFuture fail, the CompletableFuture returned by allOf will be completed with that failure's cause and that cause will be propagated to the future returned by thenComposeAsync. Otherwise, the result of deliveryNoteFuture will be delivered.
As Holger suggests, since you only have those two futures, you could also use thenCombine
releaseNotesFuture.thenCombineAsync(parcelsFuture, (x, y) -> deliveryNoteFuture.join());
Here, the join() won't block since deliveryNoteFuture is definitely already completed. Again, if the deliveryNoteFuture had originally failed, its cause will be propagated down the chain ending with the CompletableFuture returned by thenCombineAsync.
Why is CompletableFuture.allOf declared as CompletableFuture<Void> and not returning collection of results or something else? I think that was good idea to make CompletableFuture.anyOf return CompletableFuture<Object>, but I see these two methods connected and so I'm confused about what they return.
anyOf has to somehow tell you what was the result of the specific CompletableFuture whose completion triggered anyOf. That's not necessary in case of allOf because you know which futures completed -- all of them.
allOf (just as anyOf) doesn't require that all futures bear the same type. So if it were to return a future of collection, it would have to be a collection of Object which is probably not what you want anyway.
If you really want to have allOf return a future of collection, it's fairly straightforward to write your own:
public static CompletableFuture<List<Object>> myAllOf(CompletableFuture<?>... futures) {
return CompletableFuture.allOf(futures)
.thenApply(x -> Arrays.stream(futures)
.map(f -> (Object) f.join())
.collect(toList())
);
}
If you have a type-safe version of this problem and need to convert a collection of futures of a certain type to a future of collection of that same type, see this question for several examples: List<Future> to Future<List> sequence
This is similar to Misha's answer, but with a generic type:
public class Futures {
public static <T> CompletableFuture<List<T>> all(List<CompletableFuture<T>> futures) {
CompletableFuture<Void> cfv = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
return cfv.thenApply(future -> {
return futures.stream()
.map(completableFuture -> completableFuture.join())
.collect(Collectors.toList());
});
}
}
That's the method the Java API should provide.
I have been using the CompletableFuture.allOf(...) helper to create aggregate futures that will only become "done" when their composite futures are marked as complete, i.e:
CompletableFuture<?> future1 = new CompletableFuture<>();
CompletableFuture<?> future2 = new CompletableFuture<>();
CompletableFuture<?> future3 = new CompletableFuture<>();
CompletableFuture<?> future = CompletableFuture.allOf(future1, future2, future3);
I would like a slight variation on this functionality, where the aggregate future is market as complete when:
All futures have completed successfully OR
Any one future has completed unsuccessfuly
In the latter case, the aggregate future should complete (exceptionally) immediately, and not have to wait for the other futures to complete, i.e. to fail-fast.
To illustrate this in contrast to CompletableFuture.allOf(...) consider this:
// First future completed, gotta wait for the rest of them...
future1.complete(null);
System.out.println("Future1 Complete, aggregate status: " + future.isDone());
// Second feature was erroneous! I'd like the aggregate to now be completed with failure
future2.completeExceptionally(new Exception());
System.out.println("Future2 Complete, aggregate status: " + future.isDone());
// Finally complete the third future, that will mark the aggregate as done
future3.complete(null);
System.out.println("Future3 Complete, aggregate status: " + future.isDone());
Using allOf(...), this code yields:
Future1 Complete, aggregate status: false
Future2 Complete, aggregate status: false
Future3 Complete, aggregate status: true
Whereas my alternative aggregate implementation would return "true" after Feature2 was completed, given it was an exceptional.
I cannot find any utils in the Java standard library that will help me achieve this, which feels strange... given it's a relatively vanilla use-case.
Looking at the implementation of CompletableFuture.allOf(...) it's fairly obvious that the logic behind these scenarios is fairly complex. I'd loathe to have to write this myself, I was wondering if there are any alternatives?
Although not as syntactically sweet as the CompletableFuture.allOf(...) method, it appears that thenCompose(...) may offer the solution:
CompletableFuture<?> future = future1.thenCompose((f) -> future2).thenCompose((f) -> future3);
This will yield the desired:
Future1 Complete, aggregate status: false
Future2 Complete, aggregate status: true
Future3 Complete, aggregate status: true
This could be wrapped up in a helper method which would offer some syntactic niceties to the caller:
private static CompletableFuture<?> composed(CompletableFuture<?> ... futures) {
// Complete when ALL the underlying futures are completed
CompletableFuture<?> allComplete = CompletableFuture.allOf(futures);
// Complete when ANY of the underlying futures are exceptional
CompletableFuture<?> anyException = new CompletableFuture<>();
for (CompletableFuture<?> completableFuture : futures) {
completableFuture.exceptionally((t) -> {
anyException.completeExceptionally(t);
return null;
});
}
// Complete when either of the above are satisfied
return CompletableFuture.anyOf(allComplete, anyException);
}
Allowing for:
CompletableFuture<?> future = composed(future1, future2, future3);
You could maybe do this by creating both an allOf and an anyOf, and then combining those into a second anyOf.