Search code examples
javaasynchronousjava-8completable-future

Do we have alternative to Java 9 CompletableFuture methods to handle timeouts?


I want a way to handle timeouts(custom value) for completable futures where we can assign a default value to the future object if there is a timeout. I know there are a couple of methods to handle it in Java 9 and above.

But is there any alternative to Java 9 CompletableFuture methods similar to

public CompletableFuture<T> completeOnTimeout(T value, long timeout,                                                   TimeUnit unit)
OR
public CompletableFuture<T> orTimeout(long timeout, TimeUnit unit)

Solution

  • There is no magic in the methods completeOnTimeout and orTimeout. When you have something like

    CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
        LockSupport.parkNanos(TimeUnit.MINUTES.toNanos(1));
        return "ordinary value";
    });
    cf.completeOnTimeout("timeout value", 10, TimeUnit.SECONDS);
    
    cf.thenAccept(System.out::println).join();
    

    you can do the same in Java 8 with

    static final ScheduledExecutorService TIMEOUT_SCHEDULER
                                          = Executors.newScheduledThreadPool(0);
    
    CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
        LockSupport.parkNanos(TimeUnit.MINUTES.toNanos(1));
        return "ordinary value";
    });
    TIMEOUT_SCHEDULER.schedule(()->cf.complete("timeout value"), 10, TimeUnit.SECONDS);
    
    cf.thenAccept(System.out::println).join();
    

    To be closer to what Java 9’s CompletableFuture does internally, e.g. use daemon threads, keep at least one thread alive, and having cleanup for cases where the future is completed ordinarily earlier than the timeout, you may use a helper class like

    public final class TimeoutSupport {
        private static final ScheduledExecutorService TIMEOUT_SCHEDULER;
        static {
            ScheduledThreadPoolExecutor e = new ScheduledThreadPoolExecutor(1, r -> {
                Thread t = new Thread(r);
                t.setDaemon(true);
                return t;
            });
            e.setRemoveOnCancelPolicy(true);
            TIMEOUT_SCHEDULER = e;
        }
    
        public static <T> CompletableFuture<T> orTimeout(
            CompletableFuture<T> cf, long timeout, TimeUnit unit) {
            return withTimeout(cf,
                ()->cf.completeExceptionally(new TimeoutException()), timeout, unit);
        }
    
        public static <T> CompletableFuture<T> completeOnTimeout(
            CompletableFuture<T> cf, T value, long timeout, TimeUnit unit) {
    
            return withTimeout(cf, () -> cf.complete(value), timeout, unit);
        }
    
        private static <T> CompletableFuture<T> withTimeout(
            CompletableFuture<T> cf, Runnable action, long timeout, TimeUnit unit) {
    
            Future<?> f = TIMEOUT_SCHEDULER.schedule(action, timeout, unit);
            cf.whenComplete((o, t) -> f.cancel(false));
            return cf;
        }
    
        private TimeoutSupport() {}
    }
    

    to be used like

    CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
        LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(20));
        return "ordinary value";
    });
    TimeoutSupport.completeOnTimeout(cf, "timeout value", 10, TimeUnit.SECONDS);
    
    cf.thenAccept(System.out::println).join();
    

    or in a fluent style

    TimeoutSupport.completeOnTimeout(
        CompletableFuture.supplyAsync(() -> {
            LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(20));
            return "ordinary value";
        }), "timeout value", 10, TimeUnit.SECONDS)
    .thenAccept(System.out::println).join();
    

    though not as neat as just chaining, like with Java 9’s methods.

    The orTimeout method works similar.