Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CompletableFuture return all finished result after timeout

I want to have a list of comletablefutures I want to wait. With following code.

  public static <T> CompletableFuture<List<T>> finishAllQuery(
  List<CompletableFuture<T>> futures) {
CompletableFuture<Void> allDoneFuture =
    CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]));
return allDoneFuture.thenApply(
    v -> futures.stream().filter(Objects::nonNull)
        .map(future -> future.join())
        .collect(Collectors.toList())
);
  }
CompletableFuture<List<Result>> allResponse = finishAllQuery(futures);
allResponse.get(5, milliseconds)

The problem is that among all the futures, some of them can be slow, I want that after the expiration time, the get method return with all completed results. Is there a way to do that?

Thanks a lot.

like image 827
billtian Avatar asked Jul 05 '26 21:07

billtian


1 Answers

This should be handled by finishAllQuery itself. E.g., starting with Java 9, you can use

public static <T> CompletableFuture<List<T>> finishAllQuery(
    List<CompletableFuture<T>> futures, long timeOut, TimeUnit unit) {

    return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
        .completeOnTimeout(null, timeOut, unit)
        .thenApply(v -> futures.stream()
            .filter(CompletableFuture::isDone)
            .map(CompletableFuture::join)
            .collect(Collectors.toList()));
}

With completeOnTimeout, we can force the completion of the future with a predefined value after the timeout elapsed. Here, we just use null, as the result value of allOf doesn’t matter anyway.

We just have to add a filter condition, to skip all futures which are not completed yet, as otherwise, join would block the thread.

This can be used like

CompletableFuture<List<Result>> allResponse
    = finishAllQuery(futures, 5, TimeUnit.MILLISECONDS);
List<Result> list = allResponse.join(); // will wait at most 5 milliseconds

For Java 8, we can use

static <T> CompletableFuture<T> completeOnTimeout(
    CompletableFuture<T> cf, T value, long timeOut, TimeUnit unit) {

    ScheduledExecutorService e = Executors.newSingleThreadScheduledExecutor();
    ScheduledFuture<Boolean> job = e.schedule(() -> cf.complete(value), timeOut, unit);
    return cf.whenComplete((x,y) -> { job.cancel(false); e.shutdown(); });
}

for the missing feature, which requires a small rewrite:

public static <T> CompletableFuture<List<T>> finishAllQuery(
    List<CompletableFuture<T>> futures, long timeOut, TimeUnit unit) {
    return completeOnTimeout(
        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])),
        null, timeOut, unit)
        .thenApply(v -> futures.stream()
            .filter(CompletableFuture::isDone)
            .map(CompletableFuture::join)
            .collect(Collectors.toList()));
}

The way, the caller uses the method, doesn’t change.

For production use, it’s worth rewriting the completeOnTimeout method to reuse the ScheduledExecutorService, but this also requires adding shutdown code or a thread factory creating daemon threads. With Java 9 or newer, you get that for free.

like image 182
Holger Avatar answered Jul 07 '26 12:07

Holger



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!