0

I am trying to refactor code that sequentially waits on multiple futures to complete, to instead jointly wait for completion.

So I try to wait on multiple futures with a single timeout by using

// Example outcomes
final CompletableFuture<String> completedFuture
    = CompletableFuture.completedFuture("hello");
final CompletableFuture<String> failedFuture
    = new CompletableFuture<>();
failedFuture.completeExceptionally(new RuntimeException("Test Stub Exception"));
final CompletableFuture<String> incompleteFuture
    = new CompletableFuture<>();

final AtomicBoolean timeoutHandled = new AtomicBoolean(false);
final CompletableFuture<String> checkedFuture
    = incompleteFuture.whenComplete(
         (x, e) -> timeoutHandled.set(e instanceof TimeoutException));

// this example timeouts after 1ms
try {
    CompletableFuture
        .allOf(completedFuture, checkedFuture, failedFuture)
        .get(1, TimeUnit.MILLISECONDS);
} catch (final InterruptedException e) {
    Thread.currentThread().interrupt();
} catch (final TimeoutException e) {
    // probably do something here?
}

// but the incomplete future is still pending
assertTrue(checkedFuture.isCompletedExceptionally());
// this still fails even if checkedFuture.completeExceptionally(e) is called
assertTrue(timeoutHandled.get());

However the assert above fails because while the collective future timed out, the individual future did not time out yet. I would like to cancel such individual futures the same way as if they had run into timeouts individually, because they might have individual whenComplete() handlers handling TimeoutExceptions:

Expecting
  <CompletableFuture[Incomplete]>
to be completed exceptionally.

Is there a useful/safe pattern by which I can loop over all exceptions and invoke completeExceptionally() to simulate a timeout in each of the futures, and make sure all "exception handlers" have been invoked before moving on?

tkruse
  • 10,222
  • 7
  • 53
  • 80
  • Related: https://stackoverflow.com/questions/63399831, https://stackoverflow.com/questions/34211080 – tkruse Oct 07 '21 at 15:20

1 Answers1

1

You can create a varargs method with your try/catch that loops through each CompletableFuture and invokes completeExceptionally().

static void completeFutures(CompletableFuture<?>... completableFutures) throws ExecutionException {
        try {
            CompletableFuture.allOf(completableFutures).get(1, TimeUnit.MILLISECONDS);
        } catch (final InterruptedException e) {
            Thread.currentThread().interrupt();
        } catch (final TimeoutException e) {
            for (CompletableFuture<?> cf : completableFutures) {
                cf.completeExceptionally(e);
            }
        }
    }
Geoff
  • 428
  • 2
  • 8
  • Thanks. I believe that still won't make the second assert pass. – tkruse Oct 07 '21 at 21:57
  • @tkruse for that second assert, I'm not sure what the `.isTrue()` is since `.get()` returns a primitive boolean, but when I fixed that syntax it passed for me. I did have to comment out `failedFuture` since that method wasn't provided, but that shouldn't have any affect on the two assertions. – Geoff Oct 07 '21 at 22:41
  • Actually, whether the second assert fails or not depends on which futures are passed into your method, i.e. for completeFutures(completedFuture, checkedFuture, failedFuture) it fails with me on java8. But I can open a separate question on that. – tkruse Oct 08 '21 at 08:46