Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Will all futures passed to CompletableFuture.allOf() run?

So I have a couple futures which I want to run, even if some fail I'd like all to have a chance to run. So if I do:

CompletableFuture.allOf(futures).join()

Will that be the case? My reasoning is that every future would have its own queed job in its executor and therefore all would run provided the main thread doesn't finish first. My issue is that I specifically .join() on .allOf() so my application doesnt end before running everything

So allOf() semantics confuse me: Will the future return complete when all of the passed futures complete regardless if successful? Or will it complete a failed future if it sees one failed without waiting for the rest?

EDIT

To illustrate my question further, does .allOf behaves like this:

Stream.of(futures).forEach(future -> {
  try {
    future.join()
  } catch (Throwable e) {
    //dont throw, we want to join the rest
  }
})

Or does it behaves like the following:

Stream.of(futures).forEach(future -> {
  try {
    future.join()
  } catch (Throwable e) {
    throw e; //All other remaining .join() wont run
  }
})

Which is it? first or second case? Since I want the first case thats what I'm using on my code temporarily, but I'd like to use allOf() if possible because its more aesthetic

Thanks!

like image 805
JSelser Avatar asked Jan 23 '19 15:01

JSelser


1 Answers

Yes, each future will be independently attempted for completion.

I think you are also trying to understand how the control flows in various scenarios. I have come up with 4 scenarios :

  1. A future where failure shall happens due to an unhandled exception
  2. A future which shall be explicitly marked as failed with a completeExceptionally AND has an exceptionally block at its tail.
  3. A future which shall be explicitly marked as failed with a completeExceptionally AND does not have an exceptionally block at its tail.
  4. A future that shall complete to success.
//CASE 1
// A future that shall fail due to an unandled exception in its run 
// and has an exceptionally block at its tail
CompletableFuture<Void> unhandledFailureFutureWithExceptionHandler =
    CompletableFuture.runAsync(() -> {
        throw new RuntimeException("Exception in unhandledFailureFutureWithExceptionHandler");
    });
unhandledFailureFutureWithExceptionHandler = unhandledFailureFutureWithExceptionHandler
    .exceptionally(throwable -> {
        // Handling exception for this future
        // HANDLING POINT 1
        System.out.println("Handling exception at HANDLING POINT FOR CASE 1, 
            failure message is : " + throwable.getMessage());
        return null;
    });

//CASE 2
//A future that shall fail and has an exceptionally block at its tail
CompletableFuture<Void> failedFutureWithExceptionHandler = new CompletableFuture<>();
failedFutureWithExceptionHandler.completeExceptionally(
    new RuntimeException("Exception in failedFutureWithExceptionHandler")
);
failedFutureWithExceptionHandler = failedFutureWithExceptionHandler.exceptionally(throwable -> {
    // Handling exception for this future
    // HANDLING POINT 2
    System.out.println("Handling exception at HANDLING POINT FOR CASE 2, 
        failure message is : " + throwable.getMessage());
    return null;
});

//CASE 3
//A future that shall fail and has no exceptionally block at its tail
CompletableFuture<Void> failedFutureWithoutExceptionHandler = new CompletableFuture<>();
failedFutureWithoutExceptionHandler.completeExceptionally(
    new RuntimeException("Exception in failedFutureWithoutExceptionHandler")
);

//CASE 4
//A future that shall succeed and print a message to console
CompletableFuture<Void> successFuture = CompletableFuture.runAsync(() -> 
    System.out.println("CASE 4 : Running successFuture")
);

CompletableFuture.allOf(unhandledFailureFutureWithExceptionHandler, 
    failedFutureWithExceptionHandler, failedFutureWithoutExceptionHandler, successFuture)
        .exceptionally(throwable -> {
            // Handling exception if ANY of the futures that did not have its own exceptionally block
            // In this case the exception of `failedFutureWithoutExceptionHandler` will be handled here
            // HANDLING POINT 3
            System.out.println("Handling exception at HANDLING POINT FOR CASE 3, 
                failure message is : " + throwable.getMessage());
            return null;
        }).join();

The output produced on the console is

Handling exception at HANDLING POINT FOR CASE 1, failure message is : java.lang.RuntimeException: Exception in unhandledFailureFutureWithExceptionHandler
Handling exception at HANDLING POINT FOR CASE 2, failure message is : Exception in failedFutureWithExceptionHandler
CASE 4 : Running successFuture
Handling exception at HANDLING POINT FOR CASE 3, failure message is : java.lang.RuntimeException: Exception in failedFutureWithoutExceptionHandler

As you can see if a future throws an unhandled error as in case 1, if it has a exceptionally block chained to its tail, the exception shall be handled at that point

As for case 2, in case where the future is marked as failed with completeExceptionally, if the future has a handler chained to its tail, then the exceptionally block shall be handled by that block

In case 3, the future is marked as failed and does not have an exceptionally block, thus it shall be handled by the exceptionally block at the next level, in this case it is the exceptionally block of the allOf().

As you can see, case 4 runs to completion and the message gets print on the console irrespective of the failures of the other futures.

like image 157
Deepak Avatar answered Oct 16 '22 01:10

Deepak