5

I want to skip chain of completablefutures based a specific condition. I tried the solution proposed at chaining several completionstage, but it doesn't seem to work. Here it the code:

@RunWith(JUnit4.class)
public class ExceptionHandlingTests {
@Test
public void test1() {
    CompletableFuture<Integer> result = new CompletableFuture<>();
    CompletableFuture.runAsync(() -> {
        System.out.println("Completing result1. Result: " + result.isDone());
        result.complete(10);
    }).thenCompose(x -> {
        System.out.println("Completing result2. Result: " + result.isDone());
        result.complete(10);
        return CompletableFuture.completedFuture(5);
    }).thenCompose(x -> {
        System.out.println("Completing result3. Result: " + result.isDone());
        result.complete(10);
        return CompletableFuture.completedFuture(5);
    }).applyToEither(result, Function.identity());
    }
} 

Output:

Completing result1. Result: false
Completing result2. Result: true
Completing result3. Result: true

Even though the "result" completablefuture is marked completed, subsequent completablefutures are still executed. How to skip Completablefuture 2 and 3?

user3832281
  • 53
  • 1
  • 3
  • Why did you expect them to be skipped? These stages are entirely unrelated to `result`. – Holger Jan 08 '18 at 07:26
  • I thought that's what applyToEither does based on earlier stack overflow anwer. I provided the link in the description. Is there a way to skip rest of the completable futures in this case? – user3832281 Jan 08 '18 at 08:07
  • 1
    In the linked answer, there are two futures and only one of them will be completed. Since the other is never completed, its dependent stages are not evaluated. The property of `applyToEither` just is not to wait for the other. But `applyToEither` alone is not sufficient to prevent the evaluation of stages. – Holger Jan 08 '18 at 08:14

1 Answers1

9

You have created a dependency chain like this:

  first
    ↓  (↘)
   next  result
    ↓  ↙
  final

The (↘) is an explicit completion call result.complete(…), but all other completions happen automatically. The final stage, created via applyToEither will be completed with either of the prerequisites, whichever is completed first, but does not modify the behavior of them.

In principle, any code could invoke complete on it without affecting either of these stages that might complete the final stage otherwise. The same applies to cancellation. Calling cancel on a stage will complete the stage, your calling the method on, without affecting the stages you used to construct it.

The answer of the linked question creates stages like

    first
   (↙)  (↘)
 next 1  result
   ↓     |
 next 2  |
   ↘    ↙
    final

The key point is, the initial stage will complete either of two CompletableFutures explicitly, not triggering an automatic completion. The other chain of dependent stages will never get evaluated. Since the final stage is an applyToEither, the completion of only one of the chains is sufficient to evaluate the final function. It still does not affect the prerequisite stages.

Note that for your chain, consisting of thenCompose operations, you can implement a similar logic in a simpler fashion, due to the fact that your functions return a CompletableFuture anyway. So simply returning a new CompletableFuture that will never be completed when you want to shortcut, solves the issue. You can even rewrite your initial runAsync to use thenCompose instead:

for(int shortCutAt: IntStream.range(0, 4).toArray()) {
    System.out.println("Example execution with "
                      +(shortCutAt==0? "no shortcut": "shortcut at "+shortCutAt));

    CompletableFuture<Integer> result = new CompletableFuture<>();
    CompletableFuture.completedFuture(null).thenCompose(justVoid -> { // runAsync
        System.out.println("Completing result1. Result: " + result.isDone());
        if(shortCutAt == 1) { result.complete(10); return new CompletableFuture<>(); }
        return CompletableFuture.completedFuture(justVoid);
    }).thenCompose(x -> {
        System.out.println("Completing result2. Result: " + result.isDone());
        if(shortCutAt == 2) { result.complete(10); return new CompletableFuture<>(); }
        return CompletableFuture.completedFuture(5);
    }).thenCompose(x -> {
        System.out.println("Completing result3. Result: " + result.isDone());
        if(shortCutAt == 3) { result.complete(10); return new CompletableFuture<>(); }
        return CompletableFuture.completedFuture(5);
    })
    .applyToEither(result, Function.identity())
    .thenAccept(fr -> System.out.println("final result: "+fr));

    System.out.println();
}
Example execution with no shortcut
Completing result1. Result: false
Completing result2. Result: false
Completing result3. Result: false
final result: 5

Example execution with shortcut at 1
Completing result1. Result: false
final result: 10

Example execution with shortcut at 2
Completing result1. Result: false
Completing result2. Result: false
final result: 10

Example execution with shortcut at 3
Completing result1. Result: false
Completing result2. Result: false
Completing result3. Result: false
final result: 10
Holger
  • 285,553
  • 42
  • 434
  • 765
  • Brilliant. Thanks Holger. – user3832281 Jan 09 '18 at 01:48
  • Thanks @Holger. I refactored the solution to improve reusability. Wrapped up the approach in a simple class called `ShortCircuitCF` with methods to propagate the result or complete the completablefuture. Here is the [gist](https://gist.github.com/mohansunkara/9d32bb08ec6e9eb18f88ff4527dccf9f) – user3832281 Jan 09 '18 at 04:20