CompletableFuture -- Aggregate Future to Fail Fast - java

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.

Related

How to Construct an asynchronously executed task chain based on Java CompletableFuture

I have N tasks to execute, and the number of tasks is not fixed. The next task can only be executed after the previous task is completed. How can the entire task chain be executed asynchronously?
If the number of tasks is fixed, such as N=2, I can use the following code. How to do it if N is not fixed
public void futureTest() throws InterruptedException {
CompletableFuture<Integer> finalFuture = new CompletableFuture<>();
CompletableFuture<Integer> cf1 = doTask(1);
AtomicReference<CompletableFuture<Integer>> cf2 = new AtomicReference<>(new CompletableFuture<>());
cf1.whenComplete(((integer1, throwable1) -> {
if (throwable1 != null) {
finalFuture.completeExceptionally(throwable1);
return;
}
// when task1 complete then submit task2
cf2.set(doTask(2));
cf2.get().whenComplete(((integer2, throwable2) -> {
if (throwable2 != null) {
finalFuture.completeExceptionally(throwable2);
return;
}
finalFuture.complete(integer2);
}));
}));
finalFuture.whenComplete(((integer, throwable) -> {
System.out.println("all task is done");
}));
Thread.sleep(1000000);
}
private CompletableFuture<Integer> doTask(int index) {
CompletableFuture<Integer> cf = new CompletableFuture<>();
// Simulate task execution
THREAD_POOL.execute(() -> {
Thread.sleep(3_000);
cf.complete(index);
});
return cf;
}
I looked at Compeltable's API docs and none of them seem to solve my problem. I tried to use a loop to control the task submission, but all failed, unable to submit the next task after the previous task is completed
Refer to this answer on this thread Click here. Seems a duplicate of this question.
thenRun method is used to run the task after the previous future is completed successfully. This method will be skipped in case of any failures in previous stages.
whenComplete method is used as the final stage of execution chain. Here you will receive the composed result of all the other functions in the supply chain and you can choose to fail your future or handle exceptions accordingly inside this.
You can compose the futures for the individual tasks via CompletableFuture#thenCompose in a loop:
CompletableFuture<?> future = createFirstTask();
while (hasMoreTasks()) {
future = future.thenCompose(this::createNextTask);
}
Here every next tasks depends on the result of the previous one until no more task is left.
Conceptionally this is a fold operation, which unfortunately is not part of the API of CompletableFuture. But if you don't mind using my better future library (which is just a thin wrapper around CompleteableFuture), I just recently added support for folding streams of futures there.

Java CompletableFutures: is it ever a acceptable to not join/get?

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.

what is the best way to know when all submitted tasks has been finished by Executor Service

here is two options , just bit confuse which one is best to go.
Option 1:
ExecutorService es = Executors.newFixedThreadPool(4);
List<Runnable> tasks = getTasks();
CompletableFuture<?>[] futures = tasks.stream()
.map(task -> CompletableFuture.runAsync(task, es))
.toArray(CompletableFuture[]::new);
CompletableFuture.allOf(futures).join();
es.shutdown();
Option 2:
ExecutorService es = Executors.newFixedThreadPool(4);
List< Future<?>> futures = new ArrayList<>();
for(Runnable task : taskList) {
futures.add(es.submit(task));
}
for(Future<?> future : futures) {
try {
future.get();
}catch(Exception e){
// do logging and nothing else
}
}
es.shutdown();
Here putting future.get(); in try catch is good idea right?
Since you effectively hold each submitted Future in a list of of futures by doing this:
List< Future<?>> futures = new ArrayList<>();
for(Runnable task : taskList) {
futures.add(es.submit(task));
}
You can very easily check if all the submitted jobs are done executing just, by invoking the Future#isDone method, which wilkl return true or false based on whether the task has finished or not. You can check more on this on the related documentation here.
Hence, with the above in mind you could very well create a simple helper method, that will iterate the list of your futures and check on their state. For example:
private boolean areJobsDone() {
return futures.stream()
.allMatch(Future::isDone);
}
Note that in contrast to the Future#get method, isDone is non blocking (as it's not waiting for the task to return it's result) as it effectively queries it's state.
With this you can go ahead and check for the state of your runnables, blocking the flow with a loop that will invoke the helper method, before proceeding.
Hope this helps.
There is another way to wait for all tasks to complete. After you submitted all of your tasks, call
es.shutdown()
es.awaitTermination(Long.MAX_VALUE, TimeUnit.NANO_SECONDS)
Oracle's Java Docs read:
shutdown [...] Initiates an orderly shutdown in which previously submitted tasks are executed.
awaitTermination [...] Blocks until all tasks have completed execution after a shutdown request, or the timeout occurs, or the current thread is interrupted, whichever happens first.
Concerning the timeout: with the above values, the thread-pool will only terminate after about 300 years.
Similar to the Aris_Kortex' proposal,
List<CompletableFuture<?>> futures = new ArrayList<>();
for(Runnable task : taskList) {
futures.add(CompletableFuture.runAsync(task, es));
}
and then create the combined CompletableFuture:
CompletableFuture<Void> cf = CompletableFuture.allOf(futures.toArray(futires.size()));
then you can wait for all tasks synchronously:
cf.get();
synchronously with timeout:
cf.get(1, TimeUnit.SECOND);
of asynchronously:
cf.thenRun(()->{finalActions();});

When will Completable Future releases thread back to thread pool?

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.

CompletableFuture get result of first future waiting for other futures depending of first one

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.

Categories

Resources