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

com.spotify.futures.FuturesExtra Maven / Gradle / Ivy

There is a newer version: 4.3.3
Show newest version
/*
 * Copyright (c) 2013-2018 Spotify AB
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.spotify.futures;

import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.util.concurrent.AsyncFunction;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import com.google.common.util.concurrent.Uninterruptibles;

import java.util.Arrays;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Static utility methods pertaining to the {@link ListenableFuture} interface.
 */
@SuppressWarnings("unchecked")
public class FuturesExtra {

  /**
   * Returns a future that fails with a {@link TimeoutException} if the parent future has not
   * finished before the timeout. The new returned future will always be executed on the provided
   * scheduledExecutorService, even when the parent future does not timeout.
   *
   * @param scheduledExecutorService executor that runs the timeout code. If the future times out,
   *                                 this is also the thread any callbacks will run on.
   * @param future                   the future to wrap as a timeout future.
   * @param timeout                  how long to wait before timing out a future
   * @param unit                     unit of the timeout
   * @return a future that may timeout before the parent future is done.
   */
  public static  ListenableFuture makeTimeoutFuture(
          ScheduledExecutorService scheduledExecutorService,
          ListenableFuture future,
          final long timeout, final TimeUnit unit) {
    final SettableFuture promise = SettableFuture.create();

    scheduledExecutorService.schedule(() -> {
      String message = "Future timed out after " + timeout + " " + unit.name();
      promise.setException(new TimeoutException(message));
    }, timeout, unit);

    Futures.addCallback(future, new FutureCallback() {
      @Override
      public void onSuccess(T result) {
        promise.set(result);
      }

      @Override
      public void onFailure(Throwable t) {
        promise.setException(t);
      }
    }, scheduledExecutorService);

    return promise;
  }

  /**
   * This takes two futures of type {@link A} and {@link B} and works like
   * a valve on {@link A}, with validation executed on {@link B}.
   *
   * 

Returns a future with the result of {@link A} that will wait for a * condition on {@link B} to be validated first. Both futures can run in * parallel. If the condition fails validation, the {@link A} future will * be cancelled by a call to {@link ListenableFuture#cancel(boolean)} with * {@code false}. * *

This is useful for when you want to optimistically run a time consuming * path while validating if it should be computed or not by a parallel * async computation. * * @param conditionValue The future computing the value for validation. * @param future The actual value future. * @param validator A validator for the condition. * * @return a new {@link ListenableFuture} eventually either containing * {@param future} or any exception thrown by {@param validator}. */ public static ListenableFuture fastFail( final ListenableFuture conditionValue, final ListenableFuture future, final Validator validator, final Executor executor) { return Futures.transformAsync(conditionValue, value -> { try { validator.validate(value); return future; } catch (Exception e) { future.cancel(false); throw e; } }, executor); } /** * Returns a new {@link ListenableFuture} with the result of the first of futures that * successfully completes. If all ListenableFutures in futures fails the returned feature * fails as well with the Exception of the last failed future. If futures is an empty * list the returned future fails immediately with {@link java.util.NoSuchElementException} * * @param futures a {@link List} of futures * @return a new future with the result of the first completing future. * @throws NullPointerException if the {@param futures} is null */ public static ListenableFuture select( final List> futures, final Executor executor) { Preconditions.checkNotNull(futures); if (futures.isEmpty()) { return Futures.immediateFailedFuture(new NoSuchElementException("List is empty")); } final int count = futures.size(); final AtomicInteger failures = new AtomicInteger(); final SettableFuture promise = SettableFuture.create(); final FutureCallback cb = new FutureCallback() { @Override public void onSuccess(final T result) { promise.set(result); } @Override public void onFailure(Throwable t) { if (failures.incrementAndGet() == count) { promise.setException(t); } } }; for (final ListenableFuture future: futures) { Futures.addCallback(future, cb, executor); } return promise; } /** * Represents an operation that accepts a single input argument and returns no result. */ public interface Consumer { /** * Performs this operation on the given argument. * @param t the input argument */ void accept(T t); } /** * A lambda-friendly way to attach callbacks to a {@link ListenableFuture}. The success * callback will only be run if the future completes successfully, and failure will * only be run if the future fails. * @param future a ListenableFuture to attach the callbacks to. * @param success a consumer, to be called with the result of the successful future. * @param failure a consumer, to be called with the result of the failed future. * @throws NullPointerException if the {@param success} and {@param failure} are null */ public static void addCallback( final ListenableFuture future, final Consumer success, final Consumer failure, final Executor executor) { if (success == null && failure == null) { throw new NullPointerException(); } Futures.addCallback(future, new FutureCallback() { @Override public void onSuccess(final T result) { if (success != null) { success.accept(result); } } @Override public void onFailure(final Throwable throwable) { if (failure != null) { failure.accept(throwable); } } }, executor); } /** * A lambda-friendly way to attach a callback to a {@link ListenableFuture}. The callback will * only be run if the future completes successfully. * @param future a ListenableFuture to attach the callback to. * @param consumer a consumer, to be called with the result of the successful future. */ public static void addSuccessCallback( final ListenableFuture future, final Consumer consumer, final Executor executor) { addCallback(future, consumer, null, executor); } /** * A lambda-friendly way to attach a callback to a {@link ListenableFuture}. The callback will * only be run if the future fails. * @param future a ListenableFuture to attach the callback to. * @param consumer a consumer, to be called with the result of the failed future. */ public static void addFailureCallback( final ListenableFuture future, final Consumer consumer, final Executor executor) { addCallback(future, null, consumer, executor); } /** * Transform the input futures into a single future, using the provided * transform function. The transformation follows the same semantics as as * {@link Futures#transform(ListenableFuture, Function, Executor)} and the input * futures are combined using {@link Futures#allAsList}. * * @param a a ListenableFuture to combine * @param b a ListenableFuture to combine * @param function the implementation of the transform * @return a ListenableFuture holding the result of function.apply() */ public static ListenableFuture syncTransform2( ListenableFuture a, ListenableFuture b, final Function2 function, final Executor executor) { return transform( Arrays.asList(a, b), (Function, Z>) results -> function.apply((A) results.get(0), (B) results.get(1)), executor); } /** * Implementations of this interface is used to synchronously transform the * input values into an output value. */ public interface Function2 { /** * Combine the inputs into the returned output. * * @param a an input value * @param b an input value * @return a result of the combination of the input values. */ Z apply(A a, B b); } /** * Transform the input futures into a single future, using the provided * transform function. The transformation follows the same semantics as as * {@link Futures#transformAsync(ListenableFuture, AsyncFunction, Executor)} and the input * futures are combined using {@link Futures#allAsList}. * * @param a a ListenableFuture to combine * @param b a ListenableFuture to combine * @param function the implementation of the transform * @return a ListenableFuture holding the result of function.apply() */ public static ListenableFuture asyncTransform2( ListenableFuture a, ListenableFuture b, final AsyncFunction2 function, final Executor executor) { return transform( Arrays.asList(a, b), (AsyncFunction, Z>) results -> function.apply((A) results.get(0), (B) results.get(1)), executor); } /** * Implementations of this interface is used in {@link #syncTransform2} to asynchronously * transform two values into a return value. */ public interface AsyncFunction2 { /** * Create and return a {@link ListenableFuture} that will execute combining a and b into * some sort of result. * * @param a the first input value. * @param b the second input value. * @return a ListenableFuture that will complete once the result of a and b is computed. */ ListenableFuture apply(A a, B b) throws Exception; } /** * Transform the input futures into a single future, using the provided * transform function. The transformation follows the same semantics as as * {@link Futures#transform(ListenableFuture, Function, Executor)} and the input * futures are combined using {@link Futures#allAsList}. * * @param a a ListenableFuture to combine * @param b a ListenableFuture to combine * @param c a ListenableFuture to combine * @param function the implementation of the transform * @return a ListenableFuture holding the result of function.apply() */ public static ListenableFuture syncTransform3( ListenableFuture a, ListenableFuture b, ListenableFuture c, final Function3 function, final Executor executor) { return transform( Arrays.asList(a, b, c), (Function, Z>) results -> function.apply((A) results.get(0), (B) results.get(1), (C) results.get(2)), executor); } /** * Implementations of this interface is used to synchronously transform the * input values into an output value. */ public interface Function3 { Z apply(A a, B b, C c); } /** * Transform the input futures into a single future, using the provided * transform function. The transformation follows the same semantics as as * {@link Futures#transformAsync(ListenableFuture, AsyncFunction, Executor)} and the input * futures are combined using {@link Futures#allAsList}. * * @param a a ListenableFuture to combine * @param b a ListenableFuture to combine * @param c a ListenableFuture to combine * @param function the implementation of the transform * @return a ListenableFuture holding the result of function.apply() */ public static ListenableFuture asyncTransform3( ListenableFuture a, ListenableFuture b, ListenableFuture c, final AsyncFunction3 function, final Executor executor) { return transform( Arrays.asList(a, b, c), (AsyncFunction, Z>) results -> function.apply((A) results.get(0), (B) results.get(1), (C) results.get(2)), executor); } public interface AsyncFunction3 { ListenableFuture apply(A a, B b, C c) throws Exception; } /** * Transform the input futures into a single future, using the provided * transform function. The transformation follows the same semantics as as * {@link Futures#transform(ListenableFuture, Function, Executor)} and the input * futures are combined using {@link Futures#allAsList}. * * @param a a ListenableFuture to combine * @param b a ListenableFuture to combine * @param c a ListenableFuture to combine * @param d a ListenableFuture to combine * @param function the implementation of the transform * @return a ListenableFuture holding the result of function.apply() */ public static ListenableFuture syncTransform4( ListenableFuture a, ListenableFuture b, ListenableFuture c, ListenableFuture d, final Function4 function, final Executor executor) { return transform( Arrays.asList(a, b, c, d), (Function, Z>) results -> function.apply( (A) results.get(0), (B) results.get(1), (C) results.get(2), (D) results.get(3)), executor); } /** * Implementations of this interface is used to synchronously transform the * input values into an output value. */ public interface Function4 { Z apply(A a, B b, C c, D d); } /** * Transform the input futures into a single future, using the provided * transform function. The transformation follows the same semantics as as * {@link Futures#transformAsync(ListenableFuture, AsyncFunction, Executor)} and the input * futures are combined using {@link Futures#allAsList}. * * @param a a ListenableFuture to combine * @param b a ListenableFuture to combine * @param c a ListenableFuture to combine * @param d a ListenableFuture to combine * @param function the implementation of the transform * @return a ListenableFuture holding the result of function.apply() */ public static ListenableFuture asyncTransform4( ListenableFuture a, ListenableFuture b, ListenableFuture c, ListenableFuture d, final AsyncFunction4 function, final Executor executor) { return transform( Arrays.asList(a, b, c, d), (AsyncFunction, Z>) results -> function.apply( (A) results.get(0), (B) results.get(1), (C) results.get(2), (D) results.get(3)), executor); } public interface AsyncFunction4 { ListenableFuture apply(A a, B b, C c, D d) throws Exception; } /** * Transform the input futures into a single future, using the provided * transform function. The transformation follows the same semantics as as * {@link Futures#transform(ListenableFuture, Function, Executor)} and the input * futures are combined using {@link Futures#allAsList}. * * @param a a ListenableFuture to combine * @param b a ListenableFuture to combine * @param c a ListenableFuture to combine * @param d a ListenableFuture to combine * @param e a ListenableFuture to combine * @param function the implementation of the transform * @return a ListenableFuture holding the result of function.apply() */ public static ListenableFuture syncTransform5( ListenableFuture a, ListenableFuture b, ListenableFuture c, ListenableFuture d, ListenableFuture e, final Function5 function, final Executor executor) { return transform( Arrays.asList(a, b, c, d, e), (Function, Z>) results -> function.apply( (A) results.get(0), (B) results.get(1), (C) results.get(2), (D) results.get(3), (E) results.get(4)), executor); } /** * Implementations of this interface is used to synchronously transform the * input values into an output value. */ public interface Function5 { Z apply(A a, B b, C c, D d, E e); } /** * Transform the input futures into a single future, using the provided * transform function. The transformation follows the same semantics as as * {@link Futures#transformAsync(ListenableFuture, AsyncFunction, Executor)} and the input * futures are combined using {@link Futures#allAsList}. * * @param a a ListenableFuture to combine * @param b a ListenableFuture to combine * @param c a ListenableFuture to combine * @param d a ListenableFuture to combine * @param e a ListenableFuture to combine * @param function the implementation of the transform * @return a ListenableFuture holding the result of function.apply() */ public static ListenableFuture asyncTransform5( ListenableFuture a, ListenableFuture b, ListenableFuture c, ListenableFuture d, ListenableFuture e, final AsyncFunction5 function, final Executor executor) { return transform( Arrays.asList(a, b, c, d, e), (AsyncFunction, Z>) results -> function.apply( (A) results.get(0), (B) results.get(1), (C) results.get(2), (D) results.get(3), (E) results.get(4)), executor); } public interface AsyncFunction5 { ListenableFuture apply(A a, B b, C c, D d, E e) throws Exception; } /** * Transform the input futures into a single future, using the provided * transform function. The transformation follows the same semantics as as * {@link Futures#transform(ListenableFuture, Function, Executor)} and the input * futures are combined using {@link Futures#allAsList}. * * @param a a ListenableFuture to combine * @param b a ListenableFuture to combine * @param c a ListenableFuture to combine * @param d a ListenableFuture to combine * @param e a ListenableFuture to combine * @param f a ListenableFuture to combine * @param function the implementation of the transform * @return a ListenableFuture holding the result of function.apply() */ public static ListenableFuture syncTransform6( ListenableFuture a, ListenableFuture b, ListenableFuture c, ListenableFuture d, ListenableFuture e, ListenableFuture f, final Function6 function, final Executor executor) { return transform( Arrays.asList(a, b, c, d, e, f), (Function, Z>) results -> function.apply( (A) results.get(0), (B) results.get(1), (C) results.get(2), (D) results.get(3), (E) results.get(4), (F) results.get(5)), executor); } /** * Implementations of this interface is used to synchronously transform the * input values into an output value. */ public interface Function6 { Z apply(A a, B b, C c, D d, E e, F f); } /** * Transform the input futures into a single future, using the provided * transform function. The transformation follows the same semantics as as * {@link Futures#transformAsync(ListenableFuture, AsyncFunction, Executor)} and the input * futures are combined using {@link Futures#allAsList}. * * @param a a ListenableFuture to combine * @param b a ListenableFuture to combine * @param c a ListenableFuture to combine * @param d a ListenableFuture to combine * @param e a ListenableFuture to combine * @param f a ListenableFuture to combine * @param function the implementation of the transform * @return a ListenableFuture holding the result of function.apply() */ public static ListenableFuture asyncTransform6( ListenableFuture a, ListenableFuture b, ListenableFuture c, ListenableFuture d, ListenableFuture e, ListenableFuture f, final AsyncFunction6 function, final Executor executor) { return transform( Arrays.asList(a, b, c, d, e, f), (AsyncFunction, Z>) results -> function.apply( (A) results.get(0), (B) results.get(1), (C) results.get(2), (D) results.get(3), (E) results.get(4), (F) results.get(5)), executor); } public interface AsyncFunction6 { ListenableFuture apply(A a, B b, C c, D d, E e, F f) throws Exception; } private static ListenableFuture transform(final List> inputs, final Function, Z> function, final Executor executor) { return Futures.transform(Futures.allAsList(inputs), function, executor); } private static ListenableFuture transform(final List> inputs, final AsyncFunction, Z> function, final Executor executor) { return Futures.transformAsync(Futures.allAsList(inputs), function, executor); } /** *

Transform a list of futures to a future that returns a joined result of them all. * The result can be used to get the joined results and ensures no future that were not part of * the join is accessed.

* @see #join(Executor, ListenableFuture...) */ public static ListenableFuture join( final Executor executor, final List> inputs) { return Futures.transform( Futures.allAsList(inputs), new JoinedResults.Transform(inputs), executor); } /** *

Transform a list of futures to a future that returns a joined result of them all. * The result can be used to get the joined results and ensures no future that were not part of * the join is accessed.

*

Example

*
   * {@code
   * final Future first = Futures.immediateFuture("ok");
   * final Future second = Futures.immediateFuture(1);
   * JoinedResults futures = FuturesExtra.join(first, second).get();
   * assertEquals("ok", futures.get(first));
   * assertEquals(1, futures.get(second));
   * }
   * 
*/ public static ListenableFuture join( final Executor executor, final ListenableFuture... inputs) { return join(executor, Arrays.asList(inputs)); } /** * check that a future is completed. * @param future the future. * @throws IllegalStateException if the future is not completed. */ public static void checkCompleted(ListenableFuture future) { if (!future.isDone()) { throw new IllegalStateException("future was not completed"); } } /** * Get the value of a completed future. * * @param future a completed future. * @return the value of the future if it has one. * @throws IllegalStateException if the future is not completed. * @throws com.google.common.util.concurrent.UncheckedExecutionException if the future has failed */ public static T getCompleted(ListenableFuture future) { checkCompleted(future); return Futures.getUnchecked(future); } /** * Get the exception of a completed future. * * @param future a completed future. * @return the exception of a future or null if no exception was thrown * @throws IllegalStateException if the future is not completed. */ public static Throwable getException(ListenableFuture future) { checkCompleted(future); try { Uninterruptibles.getUninterruptibly(future); return null; } catch (ExecutionException e) { return e.getCause(); } } }