All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hubspot.singularity.async.CompletableFutures Maven / Gradle / Ivy

package com.hubspot.singularity.async;

import java.util.Collection;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiFunction;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Collectors;

import com.google.common.util.concurrent.ThreadFactoryBuilder;

import io.netty.util.HashedWheelTimer;
import io.netty.util.Timeout;

public class CompletableFutures {
  private CompletableFutures() {}

  public static  CompletableFuture allOf(Collection> futures) {
    return CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]));
  }

  public static  CompletableFuture anyOf(Collection> futures) {
    return CompletableFuture.anyOf(futures.toArray(new CompletableFuture[futures.size()]));
  }

  public static  CompletableFuture exceptionalFuture(Throwable t) {
    CompletableFuture future = new CompletableFuture<>();
    future.completeExceptionally(t);
    return future;
  }

  /**
   * Return a future that completes with a timeout after a delay.
   */
  public static CompletableFuture timeoutFuture(HashedWheelTimer hwt, long delay, TimeUnit timeUnit) {
    try {
      CompletableFuture future = new CompletableFuture<>();
      hwt.newTimeout(future::complete, delay, timeUnit);
      return future;
    } catch (Throwable t) {
      return exceptionalFuture(t);
    }
  }

  /**
   * Useful for composing an async call in response to exceptional cases.
   * For example:
   * {@code
   *    return thenHandleCompose(myFuture, (success, th) -> {
   *        if (th != null) {
   *          return newCompletableFuture();
   *        } else {
   *          return CompletableFuture.completedFuture(success);
   *        }
   *    });
   * }
   */
  public static  CompletableFuture thenHandleCompose(CompletionStage completionStage,
                                                              BiFunction> fn) {
    return completionStage.handle((success, throwable) -> new SuccessOrThrowable<>(success, throwable))
        .thenCompose(pair ->
            fn.apply(pair.item, pair.ex)
        ).toCompletableFuture();
  }

  public static  CompletableFuture thenHandleComposeAsync(CompletionStage completionStage,
                                                                   BiFunction> fn,
                                                                   ExecutorService executorService) {
    return completionStage.handleAsync((success, throwable) -> new SuccessOrThrowable<>(success, throwable), executorService)
        .thenComposeAsync(
            pair -> fn.apply(pair.item, pair.ex),
            executorService
        ).toCompletableFuture();
  }

  private static final HashedWheelTimer DEFAULT_TIMER = new HashedWheelTimer(
      new ThreadFactoryBuilder().setDaemon(true).setNameFormat("futures-utils-%s").build()
  );

  public static  CompletableFuture enforceTimeout(CompletionStage underlyingFuture,
                                                        long timeout,
                                                        TimeUnit timeUnit) {
    return enforceTimeout(underlyingFuture, DEFAULT_TIMER, timeout, timeUnit);
  }

  public static  CompletableFuture enforceTimeout(CompletionStage underlyingFuture,
                                                        long timeout,
                                                        TimeUnit timeUnit,
                                                        Supplier exceptionSupplier) {
    return enforceTimeout(underlyingFuture, DEFAULT_TIMER, timeout, timeUnit, exceptionSupplier);
  }

  public static  CompletableFuture enforceTimeout(CompletionStage underlyingFuture,
                                                        HashedWheelTimer timer,
                                                        long timeout,
                                                        TimeUnit timeUnit) {
    return enforceTimeout(underlyingFuture, timer, timeout, timeUnit, TimeoutException::new);
  }

  public static  CompletableFuture enforceTimeout(CompletionStage underlyingFuture,
                                                        HashedWheelTimer timer,
                                                        long timeout,
                                                        TimeUnit timeUnit,
                                                        Supplier exceptionSupplier) {
    // We don't want to muck with the underlying future passed in, so
    // chaining a .thenApply(x -> x) forces a new future to be created with its own
    // completion tracking. In this way, the original future is left alone and can
    // time out on its own schedule.
    CompletableFuture future = underlyingFuture.thenApply(x -> x)
        .toCompletableFuture();
    Timeout hwtTimeout = timer.newTimeout(
        (ignored) -> future.completeExceptionally(exceptionSupplier.get()),
        timeout,
        timeUnit
    );
    future.whenComplete((result, throwable) -> hwtTimeout.cancel());
    return future;
  }

  public static  CompletableFuture executeWithTimeout(Callable callable,
                                                            ExecutorService executorService,
                                                            long timeout,
                                                            TimeUnit timeUnit) {
    return executeWithTimeout(callable, executorService, DEFAULT_TIMER, timeout, timeUnit);
  }

  public static  CompletableFuture executeWithTimeout(Callable callable,
                                                            ExecutorService executorService,
                                                            HashedWheelTimer timer,
                                                            long timeout,
                                                            TimeUnit timeUnit) {
    CompletableFuture future = new CompletableFuture<>();
    AtomicReference timeoutRef = new AtomicReference<>();
    Future underlying = executorService.submit(() -> {
      if (future.complete(callable.call())) {
        Timeout timeout1 = timeoutRef.get();
        if (timeout1 != null) {
          timeout1.cancel();
        }
      }
      return null;
    });

    timeoutRef.set(timer.newTimeout((ignored) -> {
      if (!future.isDone()) {
        if (future.completeExceptionally(new TimeoutException())) {
          underlying.cancel(true);
        }
      }
    }, timeout, timeUnit));
    return future;
  }

  public static  T join(CompletableFuture future, long timeout, TimeUnit unit) {
    try {
      return future.get(timeout, unit);
    } catch (InterruptedException | ExecutionException | TimeoutException e) {
      throw new RuntimeException(e);
    }
  }

  public static  Collector, ?, CompletableFuture> collectAllOf() {
    return Collectors.collectingAndThen(
        Collectors.toList(),
        CompletableFutures::allOf);
  }

  private static class SuccessOrThrowable {
    private final T item;
    private final Throwable ex;

    public SuccessOrThrowable(T item, Throwable ex) {
      this.item = item;
      this.ex = ex;
    }
  }
}