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

org.threadly.concurrent.future.FutureUtils Maven / Gradle / Ivy

package org.threadly.concurrent.future;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.Supplier;

import org.threadly.concurrent.RunnableCallableAdapter;
import org.threadly.concurrent.SameThreadSubmitterExecutor;
import org.threadly.concurrent.SubmitterScheduler;
import org.threadly.util.ArgumentVerifier;
import org.threadly.util.ArrayIterator;
import org.threadly.util.Clock;
import org.threadly.util.ExceptionUtils;

/**
 * A collection of small utilities for handling futures.  This class has lots of tools for dealing 
 * with collections of futures, ranging from blocking, extracting results, and more.
 * 

* Generating already done futures: *

    *
  • {@link #immediateResultFuture(Object)} *
  • {@link #immediateFailureFuture(Throwable)} *
*

* Tools for blocking: *

    *
  • {@link #blockTillAllComplete(Iterable)} *
  • {@link #blockTillAllComplete(Iterable, long)} *
  • {@link #blockTillAllCompleteOrFirstError(Iterable)} *
  • {@link #blockTillAllCompleteOrFirstError(Iterable, long)} *
*

* Tools for manipulating collections of futures: *

    *
  • {@link #cancelIncompleteFutures(Iterable, boolean)} *
  • {@link #cancelIncompleteFuturesIfAnyFail(boolean, Iterable, boolean)} *
  • {@link #countFuturesWithResult(Iterable, Object)} *
  • {@link #countFuturesWithResult(Iterable, Object, long)} *
  • {@link #invokeAfterAllComplete(Collection, Runnable)} *
  • {@link #invokeAfterAllComplete(Collection, Runnable, Executor)} *
  • {@link #makeFirstResultFuture(Collection, boolean)} *
  • {@link #makeFirstResultFuture(Collection, boolean, boolean)} *
  • {@link #makeCompleteFuture(Iterable)} *
  • {@link #makeCompleteFuture(Iterable, Object)} *
  • {@link #makeFailurePropagatingCompleteFuture(Iterable)} *
  • {@link #makeFailurePropagatingCompleteFuture(Iterable, Object)} *
  • {@link #makeCompleteListFuture(Iterable)} *
  • {@link #makeFailureListFuture(Iterable)} *
  • {@link #makeResultListFuture(Iterable, boolean)} *
  • {@link #makeSuccessListFuture(Iterable)} *
*

* Retry operation and return final result in future: *

    *
  • {@link #scheduleWhile(SubmitterScheduler, long, boolean, Callable, Predicate)} *
  • {@link #scheduleWhile(SubmitterScheduler, long, boolean, Callable, Predicate, long, boolean)} *
  • {@link #scheduleWhile(SubmitterScheduler, long, ListenableFuture, Callable, Predicate)} *
  • {@link #scheduleWhile(SubmitterScheduler, long, ListenableFuture, Callable, Predicate, long, boolean)} *
  • {@link #scheduleWhile(SubmitterScheduler, long, boolean, Runnable, Supplier)} *
  • {@link #scheduleWhile(SubmitterScheduler, long, boolean, Runnable, Supplier, long)} *
  • {@link #executeWhile(Callable, Predicate)} *
  • {@link #executeWhile(Callable, Predicate, long, boolean)} *
  • {@link #executeWhile(ListenableFuture, Callable, Predicate)} *
  • {@link #executeWhile(ListenableFuture, Callable, Predicate, long, boolean)} *
* * @since 1.0.0 */ public class FutureUtils extends InternalFutureUtils { /** * This call blocks till all futures in the list have completed. If the future completed with * an error, the {@link ExecutionException} is swallowed. Meaning that this does not attempt to * verify that all futures completed successfully. If you need to know if any failed, please * use {@link #blockTillAllCompleteOrFirstError(Iterable)}. *

* If you need to specify a timeout to control how long to block, consider using * {@link #blockTillAllComplete(Iterable, long)}. * * @param futures Futures to block till they complete * @throws InterruptedException Thrown if thread is interrupted while waiting on future */ public static void blockTillAllComplete(Future ... futures) throws InterruptedException { countFuturesWithResult(ArrayIterator.makeIterable(futures), null); } /** * This call blocks till all futures in the list have completed. If the future completed with * an error, the {@link ExecutionException} is swallowed. Meaning that this does not attempt to * verify that all futures completed successfully. If you need to know if any failed, please * use {@link #blockTillAllCompleteOrFirstError(Iterable)}. *

* If you need to specify a timeout to control how long to block, consider using * {@link #blockTillAllComplete(Iterable, long)}. * * @param futures Structure of futures to iterate over * @throws InterruptedException Thrown if thread is interrupted while waiting on future */ public static void blockTillAllComplete(Iterable> futures) throws InterruptedException { countFuturesWithResult(futures, null); } /** * This call blocks till all futures in the list have completed. If the future completed with * an error, the {@link ExecutionException} is swallowed. Meaning that this does not attempt to * verify that all futures completed successfully. If you need to know if any failed, please * use {@link #blockTillAllCompleteOrFirstError(Iterable, long)}. * * @since 4.0.0 * @param futures Structure of futures to iterate over * @param timeoutInMillis timeout to wait for futures to complete in milliseconds * @throws InterruptedException Thrown if thread is interrupted while waiting on future * @throws TimeoutException Thrown if the timeout elapsed while waiting on futures to complete */ public static void blockTillAllComplete(Iterable> futures, long timeoutInMillis) throws InterruptedException, TimeoutException { countFuturesWithResult(futures, null, timeoutInMillis); } /** * This call blocks till all futures in the list have completed. If the future completed with * an error an {@link ExecutionException} is thrown. If this exception is thrown, all futures * may or may not be completed, the exception is thrown as soon as it is hit. There also may be * additional futures that errored (but were not hit yet). *

* If you need to specify a timeout to control how long to block, consider using * {@link #blockTillAllCompleteOrFirstError(Iterable, long)}. * * @param futures Futures to iterate over * @throws InterruptedException Thrown if thread is interrupted while waiting on future * @throws ExecutionException Thrown if future throws exception on .get() call */ public static void blockTillAllCompleteOrFirstError(Future ... futures) throws InterruptedException, ExecutionException { blockTillAllCompleteOrFirstError(ArrayIterator.makeIterable(futures)); } /** * This call blocks till all futures in the list have completed. If the future completed with * an error an {@link ExecutionException} is thrown. If this exception is thrown, all futures * may or may not be completed, the exception is thrown as soon as it is hit. There also may be * additional futures that errored (but were not hit yet). *

* If you need to specify a timeout to control how long to block, consider using * {@link #blockTillAllCompleteOrFirstError(Iterable, long)}. * * @param futures Structure of futures to iterate over * @throws InterruptedException Thrown if thread is interrupted while waiting on future * @throws ExecutionException Thrown if future throws exception on .get() call */ public static void blockTillAllCompleteOrFirstError(Iterable> futures) throws InterruptedException, ExecutionException { if (futures == null) { return; } for (Future f : futures) { f.get(); } } /** * This call blocks till all futures in the list have completed. If the future completed with * an error an {@link ExecutionException} is thrown. If this exception is thrown, all futures * may or may not be completed, the exception is thrown as soon as it is hit. There also may be * additional futures that errored (but were not hit yet). * * @since 4.0.0 * @param futures Structure of futures to iterate over * @param timeoutInMillis timeout to wait for futures to complete in milliseconds * @throws InterruptedException Thrown if thread is interrupted while waiting on future * @throws TimeoutException Thrown if the timeout elapsed while waiting on futures to complete * @throws ExecutionException Thrown if future throws exception on .get() call */ public static void blockTillAllCompleteOrFirstError(Iterable> futures, long timeoutInMillis) throws InterruptedException, TimeoutException, ExecutionException { if (futures == null) { return; } long startTime = Clock.accurateForwardProgressingMillis(); long remainingTime; for (Future f : futures) { if ((remainingTime = timeoutInMillis - (Clock.lastKnownForwardProgressingMillis() - startTime)) <= 0) { throw new TimeoutException(); } f.get(remainingTime, TimeUnit.MILLISECONDS); } } /** * Counts how many futures provided completed with a result that matches the one provided here. * This can be most useful if your looking to know if an error occurred that was not an * {@link ExecutionException}. For example assume an API return's {@code Future} and a * {@code false} represents a failure, this can be used to look for those types of error * results. *

* Just like {@link #blockTillAllComplete(Iterable)}, this will block until all futures have * completed (so we can verify if their result matches or not). *

* If you need to specify a timeout to control how long to block, consider using * {@link #countFuturesWithResult(Iterable, Object, long)}. * * @since 4.0.0 * @param type of result futures provide to compare against * @param futures Structure of futures to iterate over * @param comparisonResult Object to compare future results against to look for match * @return Number of futures which match the result using a {@link Object#equals(Object)} comparison * @throws InterruptedException Thrown if thread is interrupted while waiting on future's result */ public static int countFuturesWithResult(Iterable> futures, T comparisonResult) throws InterruptedException { if (futures == null) { return 0; } int resultCount = 0; for (Future f : futures) { if (f.isCancelled()) { continue; } try { if (comparisonResult == null) { if (f.get() == null) { resultCount++; } } else if (comparisonResult.equals(f.get())) { resultCount++; } } catch (CancellationException | ExecutionException e) { // swallowed } } return resultCount; } /** * Counts how many futures provided completed with a result that matches the one provided here. * This can be most useful if your looking to know if an error occurred that was not an * {@link ExecutionException}. For example assume an API return's {@code Future} and a * {@code false} represents a failure, this can be used to look for those types of error * results. *

* Just like {@link #blockTillAllComplete(Iterable)}, this will block until all futures have * completed (so we can verify if their result matches or not). * * @since 4.0.0 * @param type of result futures provide to compare against * @param futures Structure of futures to iterate over * @param comparisonResult Object to compare future results against to look for match * @param timeoutInMillis timeout to wait for futures to complete in milliseconds * @return Number of futures which match the result using a {@link Object#equals(Object)} comparison * @throws InterruptedException Thrown if thread is interrupted while waiting on future's result * @throws TimeoutException Thrown if the timeout elapsed while waiting on futures to complete */ public static int countFuturesWithResult(Iterable> futures, T comparisonResult, long timeoutInMillis) throws InterruptedException, TimeoutException { if (futures == null) { return 0; } int resultCount = 0; long startTime = Clock.accurateForwardProgressingMillis(); long remainingTime; for (Future f : futures) { if ((remainingTime = timeoutInMillis - (Clock.lastKnownForwardProgressingMillis() - startTime)) <= 0) { throw new TimeoutException(); } try { if (comparisonResult == null) { if (f.get(remainingTime, TimeUnit.MILLISECONDS) == null) { resultCount++; } } else if (comparisonResult.equals(f.get(remainingTime, TimeUnit.MILLISECONDS))) { resultCount++; } } catch (CancellationException | ExecutionException e) { // swallowed } } return resultCount; } /** * A potentially more performant option than {@link #makeCompleteFuture(List)} when only a * listener invocation is desired after all the futures complete. This is effective an async * implementation of {@link #blockTillAllComplete(Iterable)}. If the listener needs to be * invoked on another thread than one of the provided futures please use * {@link #invokeAfterAllComplete(Collection, Runnable, Executor)}. Please see * {@link ListenableFuture#listener(Runnable)} for more information on execution without an * {@link Executor}. *

* It is critical that the collection is NOT modified while this is invoked. A change in the * futures contained in the collection will lead to unreliable behavior with the exectuion of the * listener. * * @param futures Futures that must complete before listener is invoked * @param listener Invoked once all the provided futures have completed */ public static void invokeAfterAllComplete(Collection> futures, Runnable listener) { invokeAfterAllComplete(futures, listener, null); } /** * A potentially more performant option than {@link #makeCompleteFuture(List)} when only a * listener invocation is desired after all the futures complete. This is effective an async * implementation of {@link #blockTillAllComplete(Iterable)}. *

* It is critical that the collection is NOT modified while this is invoked. A change in the * futures contained in the collection will lead to unreliable behavior with the exectuion of the * listener. * * @param futures Futures that must complete before listener is invoked * @param listener Invoked once all the provided futures have completed * @param executor Executor (or {@code null}) to invoke listener on, see * {@link ListenableFuture#listener(Runnable, Executor)} */ public static void invokeAfterAllComplete(Collection> futures, Runnable listener, Executor executor) { ArgumentVerifier.assertNotNull(listener, "listener"); int size = futures == null ? 0 : futures.size(); if (size == 0) { if (executor == null) { listener.run(); } else { executor.execute(listener); } } else if (size == 1) { futures.iterator().next().listener(listener, executor); } else { AtomicInteger remaining = new AtomicInteger(size); Runnable decrementingListener = () -> { if (remaining.decrementAndGet() == 0) { if (executor == null) { listener.run(); } else { executor.execute(listener); } } }; for (ListenableFuture lf : futures) { lf.listener(decrementingListener); } } } /** * Converts a collection of {@link ListenableFuture}'s into a single {@link ListenableFuture} * where the result will be the first result provided from the collection. *

* If {@code ignoreErrors} is {@code false} the returned future will complete as soon as the * first future completes, if it completes in error then the error would be returned. If * {@code ignoreErrors} is {@code true} then the returned future will complete once a result is * provided, or once all futures have completed in error. If all futures did complete in error * then the last error state will be specified to the resulting {@link ListenableFuture}. This * minor bookkeeping to ignore errors does incur a slight overhead. * * @since 6.6 * @param type of result provided in the returned future * @param c Collection of futures to monitor for result * @param ignoreErrors {@code false} to communicate the first completed future state, even if in error * @return A future which will be provided the first result from any in the provided {@link Collection} */ public static ListenableFuture makeFirstResultFuture(Collection> c, boolean ignoreErrors) { SettableListenableFuture result = new SettableListenableFuture<>(false); FutureCallback callback; int expectedSize = 0; AtomicInteger atomicSize = null; if (ignoreErrors) { expectedSize = c.size(); AtomicInteger errorsRemaining = atomicSize = new AtomicInteger(expectedSize); callback = new FutureCallback() { @Override public void handleResult(T t) { result.setResult(t); } @Override public void handleFailure(Throwable t) { if (errorsRemaining.decrementAndGet() == 0) { // ignore failures till we reach the last failure result.setFailure(t); } } }; } else { callback = result; } int callbackCount = 0; for (ListenableFuture lf : c) { callbackCount++; lf.callback(callback); } if (callbackCount == 0) { result.setFailure(new IllegalArgumentException("Empty collection")); } else if (atomicSize != null && expectedSize != callbackCount) { atomicSize.addAndGet(callbackCount - expectedSize); } return result; } /** * Converts a collection of {@link ListenableFuture}'s into a single {@link ListenableFuture} * where the result will be the first result provided from the collection. *

* If {@code ignoreErrors} is {@code false} the returned future will complete as soon as the * first future completes, if it completes in error then the error would be returned. If * {@code ignoreErrors} is {@code true} then the returned future will complete once a result is * provided, or once all futures have completed in error. If all futures did complete in error * then the last error state will be specified to the resulting {@link ListenableFuture}. This * minor bookkeeping to ignore errors does incur a slight overhead. *

* It is expected that the first result is the only result desired, once it is found this will * attempt to cancel all remaining futures. If you may want other results which were in * progress, then specifying {@code interruptOnCancel} as {@code false} will mean that any * futures which started can complete. You can then inspect the collection for done futures * which might have a result. If there is no concern for other results, then you likely will * want to interrupt started futures. * * @since 5.38 * @param type of result provided in the returned future * @param c Collection of futures to monitor for result * @param ignoreErrors {@code false} to communicate the first completed future state, even if in error * @param interruptOnCancel {@code true} to send a interrupt on any running futures after we have a result * @return A future which will be provided the first result from any in the provided {@link Collection} */ public static ListenableFuture makeFirstResultFuture(Collection> c, boolean ignoreErrors, boolean interruptOnCancel) { SettableListenableFuture result = new SettableListenableFuture<>(false); FutureCallback callback; int expectedSize = 0; AtomicInteger atomicSize = null; if (ignoreErrors) { expectedSize = c.size(); AtomicInteger errorsRemaining = atomicSize = new AtomicInteger(expectedSize); callback = new FutureCallback() { @Override public void handleResult(T t) { if (result.setResult(t)) { FutureUtils.cancelIncompleteFutures(c, interruptOnCancel); } } @Override public void handleFailure(Throwable t) { if (errorsRemaining.decrementAndGet() == 0) { // ignore failures till we reach the last failure result.setFailure(t); } } }; } else { callback = new FutureCallback() { @Override public void handleResult(T t) { if (result.setResult(t)) { FutureUtils.cancelIncompleteFutures(c, interruptOnCancel); } } @Override public void handleFailure(Throwable t) { if (result.setFailure(t)) { FutureUtils.cancelIncompleteFutures(c, interruptOnCancel); } } }; } int callbackCount = 0; for (ListenableFuture lf : c) { callbackCount++; lf.callback(callback); } if (callbackCount == 0) { result.setFailure(new IllegalArgumentException("Empty collection")); } else if (atomicSize != null && expectedSize != callbackCount) { atomicSize.addAndGet(callbackCount - expectedSize); } return result; } /** * An alternative to {@link #blockTillAllComplete(Iterable)}, this provides the ability to know * when all futures are complete without blocking. Unlike * {@link #blockTillAllComplete(Iterable)}, this requires that you provide a collection of * {@link ListenableFuture}'s. But will return immediately, providing a new * {@link ListenableFuture} that will be called once all the provided futures have finished. *

* The future returned will provide a {@code null} result, it is the responsibility of the * caller to get the actual results from the provided futures. This is designed to just be an * indicator as to when they have finished. If you need the results from the provided futures, * consider using {@link #makeCompleteListFuture(Iterable)}. You should also consider using * {@link #makeFailurePropagatingCompleteFuture(Iterable)}, it has the same semantics as this one * except it will put the returned future into an error state if any of the provided futures error. * * @since 5.3 * @param futures Collection of futures that must finish before returned future is satisfied * @return ListenableFuture which will be done once all futures provided are done */ public static ListenableFuture makeCompleteFuture(List> futures) { if (futures == null || futures.isEmpty()) { return ImmediateResultListenableFuture.NULL_RESULT; } else if (futures.size() == 1) { return futures.get(0); } else { return makeCompleteFuture((Iterable>)futures); } } /** * An alternative to {@link #blockTillAllComplete(Iterable)}, this provides the ability to know * when all futures are complete without blocking. Unlike * {@link #blockTillAllComplete(Iterable)}, this requires that you provide a collection of * {@link ListenableFuture}'s. But will return immediately, providing a new * {@link ListenableFuture} that will be called once all the provided futures have finished. *

* The future returned will provide a {@code null} result, it is the responsibility of the * caller to get the actual results from the provided futures. This is designed to just be an * indicator as to when they have finished. If you need the results from the provided futures, * consider using {@link #makeCompleteListFuture(Iterable)}. You should also consider using * {@link #makeFailurePropagatingCompleteFuture(Iterable)}, it has the same semantics as this one * except it will put the returned future into an error state if any of the provided futures error. * * @since 5.3 * @param futures Collection of futures that must finish before returned future is satisfied * @return ListenableFuture which will be done once all futures provided are done */ public static ListenableFuture makeCompleteFuture(Collection> futures) { if (futures == null || futures.isEmpty()) { return ImmediateResultListenableFuture.NULL_RESULT; } else if (futures.size() == 1) { return futures.iterator().next(); } else { return makeCompleteFuture((Iterable>)futures); } } /** * An alternative to {@link #blockTillAllComplete(Iterable)}, this provides the ability to know * when all futures are complete without blocking. Unlike * {@link #blockTillAllComplete(Iterable)}, this requires that you provide a collection of * {@link ListenableFuture}'s. But will return immediately, providing a new * {@link ListenableFuture} that will be called once all the provided futures have finished. *

* The future returned will provide a {@code null} result, it is the responsibility of the * caller to get the actual results from the provided futures. This is designed to just be an * indicator as to when they have finished. If you need the results from the provided futures, * consider using {@link #makeCompleteListFuture(Iterable)}. You should also consider using * {@link #makeFailurePropagatingCompleteFuture(Iterable)}, it has the same semantics as this one * except it will put the returned future into an error state if any of the provided futures error. * * @since 1.2.0 * @param futures Futures that must finish before returned future is satisfied * @return ListenableFuture which will be done once all futures provided are done */ public static ListenableFuture makeCompleteFuture(ListenableFuture ... futures) { return makeCompleteFuture(ArrayIterator.makeIterable(futures)); } /** * An alternative to {@link #blockTillAllComplete(Iterable)}, this provides the ability to know * when all futures are complete without blocking. Unlike * {@link #blockTillAllComplete(Iterable)}, this requires that you provide a collection of * {@link ListenableFuture}'s. But will return immediately, providing a new * {@link ListenableFuture} that will be called once all the provided futures have finished. *

* The future returned will provide a {@code null} result, it is the responsibility of the * caller to get the actual results from the provided futures. This is designed to just be an * indicator as to when they have finished. If you need the results from the provided futures, * consider using {@link #makeCompleteListFuture(Iterable)}. You should also consider using * {@link #makeFailurePropagatingCompleteFuture(Iterable)}, it has the same semantics as this one * except it will put the returned future into an error state if any of the provided futures error. * * @since 1.2.0 * @param futures Collection of futures that must finish before returned future is satisfied * @return ListenableFuture which will be done once all futures provided are done */ public static ListenableFuture makeCompleteFuture(Iterable> futures) { if (futures == null) { return ImmediateResultListenableFuture.NULL_RESULT; } Iterator> it = futures.iterator(); if (! it.hasNext()) { return ImmediateResultListenableFuture.NULL_RESULT; } ListenableFuture result = new EmptyFutureCollection(it); if (result.isDone()) { return ImmediateResultListenableFuture.NULL_RESULT; } else { return result; } } /** * An alternative to {@link #blockTillAllComplete(Iterable)}, this provides the ability to know * when all futures are complete without blocking. Unlike * {@link #blockTillAllComplete(Iterable)}, this requires that you provide a collection of * {@link ListenableFuture}'s. But will return immediately, providing a new * {@link ListenableFuture} that will be called once all the provided futures have finished. *

* The future returned will provide the result object once all provided futures have completed. * If any failures occured, they will not be represented in the returned future. If that is * desired you should consider using * {@link #makeFailurePropagatingCompleteFuture(Iterable, Object)}, it has the same semantics as * this one except it will put the returned future into an error state if any of the provided * futures error. * * @since 3.3.0 * @param type of result returned from the future * @param futures Collection of futures that must finish before returned future is satisfied * @param result Result to provide returned future once all futures complete * @return ListenableFuture which will be done once all futures provided are done */ @SuppressWarnings("unchecked") public static ListenableFuture makeCompleteFuture(Iterable> futures, final T result) { if (futures == null) { return immediateResultFuture(result); } Iterator> it = futures.iterator(); if (! it.hasNext()) { return immediateResultFuture(result); } final EmptyFutureCollection efc = new EmptyFutureCollection(it); if (efc.isDone()) { return immediateResultFuture(result); } final SettableListenableFuture resultFuture = new CancelDelegateSettableListenableFuture<>(efc, null); efc.callback(new FutureCallback() { @Override public void handleResult(Object ignored) { resultFuture.setResult(result); } @Override public void handleFailure(Throwable t) { resultFuture.setFailure(t); } }, null, null); return resultFuture; } /** * Similar to {@link #makeCompleteFuture(Iterable)} in that the returned future wont complete * until all the provided futures complete. However this implementation will check if any * futures failed or were canceled once all have completed. If any did not complete normally * then the returned futures state will match the state of one of the futures that did not * normally (randomly chosen). *

* Since the returned future wont complete until all futures complete, you may want to consider * using {@link #cancelIncompleteFuturesIfAnyFail(boolean, Iterable, boolean)} in addition to * this so that the future will resolve as soon as any failures occur. * * @since 5.0 * @param futures Collection of futures that must finish before returned future is satisfied * @return ListenableFuture which will be done once all futures provided are done */ public static ListenableFuture makeFailurePropagatingCompleteFuture(ListenableFuture ... futures) { return makeFailurePropagatingCompleteFuture(ArrayIterator.makeIterable(futures), null); } /** * Similar to {@link #makeCompleteFuture(Iterable)} in that the returned future wont complete * until all the provided futures complete. However this implementation will check if any * futures failed or were canceled once all have completed. If any did not complete normally * then the returned futures state will match the state of one of the futures that did not * normally (randomly chosen). *

* Since the returned future wont complete until all futures complete, you may want to consider * using {@link #cancelIncompleteFuturesIfAnyFail(boolean, Iterable, boolean)} in addition to * this so that the future will resolve as soon as any failures occur. * * @since 5.0 * @param futures Collection of futures that must finish before returned future is satisfied * @return ListenableFuture which will be done once all futures provided are done */ public static ListenableFuture makeFailurePropagatingCompleteFuture(Iterable> futures) { return makeFailurePropagatingCompleteFuture(futures, null); } /** * Similar to {@link #makeCompleteFuture(Iterable, Object)} in that the returned future wont * complete until all the provided futures complete. However this implementation will check if * any futures failed or were canceled once all have completed. If any did not complete normally * then the returned futures state will match the state of one of the futures that did not * normally (randomly chosen). *

* Since the returned future wont complete until all futures complete, you may want to consider * using {@link #cancelIncompleteFuturesIfAnyFail(boolean, Iterable, boolean)} in addition to * this so that the future will resolve as soon as any failures occur. * * @since 5.0 * @param type of result returned from the future * @param futures Collection of futures that must finish before returned future is satisfied * @param result Result to provide returned future once all futures complete * @return ListenableFuture which will be done once all futures provided are done */ @SuppressWarnings("unchecked") public static ListenableFuture makeFailurePropagatingCompleteFuture(Iterable> futures, final T result) { if (futures == null) { return immediateResultFuture(result); } Iterator> it = futures.iterator(); if (! it.hasNext()) { return immediateResultFuture(result); } FailureFutureCollection ffc = new FailureFutureCollection<>(it); if (ffc.isDone()) { // optimize already done case try { List> failedFutures = ffc.get(); if (failedFutures.isEmpty()) { return immediateResultFuture(result); } else { // propagate first error return (ListenableFuture) failedFutures.get(0); } } catch (ExecutionException e) { return immediateFailureFuture(e.getCause()); } catch (InterruptedException e) { // should not be possible throw new RuntimeException(e); } } final CancelDelegateSettableListenableFuture resultFuture = new CancelDelegateSettableListenableFuture<>(ffc, null); ffc.callback(new FutureCallback>>() { @Override public void handleResult(List> failedFutures) { if (failedFutures.isEmpty()) { resultFuture.setResult(result); } else { // propagate error ListenableFuture f = failedFutures.get(0); if (f.isCancelled()) { resultFuture.cancel(false); } else { try { resultFuture.setFailure(f.getFailure()); } catch (InterruptedException e) { // should not be possible throw new RuntimeException(e); } } } } @Override public void handleFailure(Throwable t) { resultFuture.setFailure(t); } }, null, null); return resultFuture; } /** * This call is similar to {@link #makeCompleteFuture(Iterable)} in that it will immediately * provide a future that will not be satisfied till all provided futures complete. *

* This future provides a list of the completed futures as the result. The order of the result * list will match the order returned by the provided {@link Iterable}. *

* If {@link ListenableFuture#cancel(boolean)} is invoked on the returned future, all provided * futures will attempt to be canceled in the same way. * * @since 1.2.0 * @param The result object type returned from the futures * @param futures Structure of futures to iterate over * @return ListenableFuture which will be done once all futures provided are done */ @SuppressWarnings("unchecked") public static ListenableFuture>> makeCompleteListFuture(Iterable> futures) { if (futures == null) { return (ListenableFuture>>)ImmediateResultListenableFuture.EMPTY_LIST_RESULT; } Iterator> it = futures.iterator(); if (! it.hasNext()) { return (ListenableFuture>>)ImmediateResultListenableFuture.EMPTY_LIST_RESULT; } return new AllFutureCollection<>(it); } /** * This call is similar to {@link #makeCompleteFuture(Iterable)} in that it will immediately * provide a future that will not be satisfied till all provided futures complete. *

* This future provides a list of the futures that completed without throwing an exception nor * were canceled. The order of the resulting list is NOT deterministic. If order is needed * please see {@link #makeCompleteListFuture(Iterable)} and check for results. *

* If {@link ListenableFuture#cancel(boolean)} is invoked on the returned future, all provided * futures will attempt to be canceled in the same way. * * @since 1.2.0 * @param The result object type returned from the futures * @param futures Structure of futures to iterate over * @return ListenableFuture which will be done once all futures provided are done */ @SuppressWarnings("unchecked") public static ListenableFuture>> makeSuccessListFuture(Iterable> futures) { if (futures == null) { return (ListenableFuture>>)ImmediateResultListenableFuture.EMPTY_LIST_RESULT; } Iterator> it = futures.iterator(); if (! it.hasNext()) { return (ListenableFuture>>)ImmediateResultListenableFuture.EMPTY_LIST_RESULT; } return new SuccessFutureCollection<>(it); } /** * This call is similar to {@link #makeCompleteFuture(Iterable)} in that it will immediately * provide a future that will not be satisfied till all provided futures complete. *

* This future provides a list of the futures that failed by either throwing an exception or * were canceled. The order of the resulting list is NOT deterministic. If order is needed * please see {@link #makeCompleteListFuture(Iterable)} and check for results. *

* If {@link ListenableFuture#cancel(boolean)} is invoked on the returned future, all provided * futures will attempt to be canceled in the same way. * * @since 1.2.0 * @param The result object type returned from the futures * @param futures Structure of futures to iterate over * @return ListenableFuture which will be done once all futures provided are done */ @SuppressWarnings("unchecked") public static ListenableFuture>> makeFailureListFuture(Iterable> futures) { if (futures == null) { return (ListenableFuture>>)ImmediateResultListenableFuture.EMPTY_LIST_RESULT; } Iterator> it = futures.iterator(); if (! it.hasNext()) { return (ListenableFuture>>)ImmediateResultListenableFuture.EMPTY_LIST_RESULT; } return new FailureFutureCollection<>(it); } /** * This returns a future which provides the results of all the provided futures. Thus * preventing the need to iterate over all the futures and manually extract the results. This * call does NOT block, instead it will return a future which will not complete until all the * provided futures complete. *

* The order of the result list will match the order returned by the provided {@link Iterable}. *

* If called with {@code true} for {@code ignoreFailedFutures}, even if some of the provided * futures finished in error, they will be ignored and just the successful results will be * provided. If called with {@code false} then if any futures complete in error, then the * returned future will throw a {@link ExecutionException} with the error as the cause when * {@link Future#get()} is invoked. In addition if called with {@code false} and any of the * provided futures are canceled, then the returned future will also be canceled, resulting in a * {@link CancellationException} being thrown when {@link Future#get()} is invoked. In the case * where there is canceled and failed exceptions in the collection, this will prefer to throw the * failure as an {@link ExecutionException} rather than obscure it with a * {@link CancellationException}. In other words {@link CancellationException} will be thrown * ONLY if there was canceled tasks, but NO tasks which finished in error. * * @since 4.0.0 * @param The result object type returned from the futures * @param futures Structure of futures to iterate over and extract results from * @param ignoreFailedFutures {@code true} to ignore any failed or canceled futures * @return A {@link ListenableFuture} which will provide a list of the results from the provided futures */ @SuppressWarnings("unchecked") public static ListenableFuture> makeResultListFuture(Iterable> futures, final boolean ignoreFailedFutures) { if (futures == null) { return (ListenableFuture>)ImmediateResultListenableFuture.EMPTY_LIST_RESULT; } Iterator> it = futures.iterator(); if (! it.hasNext()) { return (ListenableFuture>)ImmediateResultListenableFuture.EMPTY_LIST_RESULT; } ListenableFuture>> completeFuture = new AllFutureCollection<>(it); final SettableListenableFuture> result = new CancelDelegateSettableListenableFuture<>(completeFuture, null); completeFuture.callback(new FutureCallback>>() { @Override public void handleResult(List> resultFutures) { boolean needToCancel = false; ArrayList results = new ArrayList<>(resultFutures.size()); Iterator> it = resultFutures.iterator(); while (it.hasNext()) { ListenableFuture f = it.next(); if (f.isCancelled()) { if (! ignoreFailedFutures) { needToCancel = true; // mark to cancel, but search for failure before actually canceling } continue; } try { Throwable failure = f.getFailure(); if (failure != null) { if (! ignoreFailedFutures) { result.setFailure(failure); return; } else { continue; } } results.add(f.get()); } catch (Exception e) { // should not be possible, future is done, cancel checked first, and failure also checked result.setFailure(e); return; } } if (needToCancel) { if (! result.cancel(true)) { result.setFailure(new IllegalStateException("Failed to cancel after dependent future was canceled")); } } else { result.setResult(results); } } @Override public void handleFailure(Throwable t) { result.setFailure(t); } }, null, null); return result; } /** * Invoked {@link Future#cancel(boolean)} for every future in this collection. Thus if there * are any futures which have not already completed, they will now be marked as canceled. * * @param futures Collection of futures to iterate through and cancel * @param interruptThread Valued passed in to interrupt thread when calling {@link Future#cancel(boolean)} */ public static void cancelIncompleteFutures(Iterable> futures, boolean interruptThread) { if (futures == null) { return; } for (Future f : futures) { f.cancel(interruptThread); } } /** * Provide a group of futures and cancel all of them if any of them are canceled or fail. *

* If {@code false} is provided for {@code copy} parameter, then {@code futures} will be * iterated over twice, once during this invocation, and again when needing to cancel the * futures. Because of that it is critical the {@link Iterable} provided returns the exact same * future contents at the time of invoking this call. If that guarantee can not be provided, * you must specify {@code true} for the {@code copy} parameter. * * @since 4.7.2 * @param copy {@code true} to copy provided futures to avoid * @param futures Futures to be monitored and canceled on error * @param interruptThread Valued passed in to interrupt thread when calling {@link Future#cancel(boolean)} */ public static void cancelIncompleteFuturesIfAnyFail(boolean copy, Iterable> futures, final boolean interruptThread) { if (futures == null) { return; } final ArrayList> futuresCopy; final Iterable> callbackFutures; if (copy) { callbackFutures = futuresCopy = new ArrayList<>(); } else { futuresCopy = null; callbackFutures = futures; } Consumer cancelingCallback = new CancelOnErrorFutureCallback(callbackFutures, interruptThread); for (ListenableFuture f : futures) { if (copy) { futuresCopy.add(f); } f.failureCallback(cancelingCallback); } } /** * Constructs a {@link ListenableFuture} that has already had the provided result given to it. * Thus the resulting future can not error, block, or be canceled. *

* If {@code null} is provided here the static instance of * {@link ImmediateResultListenableFuture#NULL_RESULT} will be returned to reduce GC overhead. * This function may additionally try to optimize the references to other common cases (like * {@link Boolean} results) when performance permits it. Those de-duplications may change based * off benchmarking results, so be careful about depending on them. If no de-duplication is * desired (ie the Future is used as a key in a {@code Map}), then manually construct a new * {@link ImmediateResultListenableFuture}. * * @since 1.2.0 * @param The result object type returned by the returned future * @param result result to be provided in .get() call * @return Already satisfied future */ @SuppressWarnings("unchecked") public static ListenableFuture immediateResultFuture(T result) { if (result == null) { return (ListenableFuture)ImmediateResultListenableFuture.NULL_RESULT; } else if (result == Optional.empty()) { return (ListenableFuture)ImmediateResultListenableFuture.EMPTY_OPTIONAL_RESULT; } else if (result == Boolean.TRUE) { return (ListenableFuture)ImmediateResultListenableFuture.BOOLEAN_TRUE_RESULT; } else if (result == Boolean.FALSE) { return (ListenableFuture)ImmediateResultListenableFuture.BOOLEAN_FALSE_RESULT; } else if (result == "") { // equality check is ideal since the JVM will de-duplicate literal strings return (ListenableFuture)ImmediateResultListenableFuture.EMPTY_STRING_RESULT; /* The below seem to impact performance a surprising amount } else if (result == Collections.emptyList()) { return (ListenableFuture)ImmediateResultListenableFuture.EMPTY_LIST_RESULT; } else if (result == Collections.emptyMap()) { return (ListenableFuture)ImmediateResultListenableFuture.EMPTY_MAP_RESULT; } else if (result == Collections.emptySet()) { return (ListenableFuture)ImmediateResultListenableFuture.EMPTY_SET_RESULT;*/ } else { return new ImmediateResultListenableFuture<>(result); } } /** * Constructs a {@link ListenableFuture} that has failed with the given failure. Thus the * resulting future can not block, or be canceled. Calls to {@link ListenableFuture#get()} will * immediately throw an {@link ExecutionException}. * * @since 1.2.0 * @param The result object type returned by the returned future * @param failure to provide as cause for ExecutionException thrown from .get() call * @return Already satisfied future */ public static ListenableFuture immediateFailureFuture(Throwable failure) { return new ImmediateFailureListenableFuture<>(failure); } /** * Executes a task until the provided supplier returns {@code false}. This can be a good way to * implement retry logic where there completion (but not result) needs to be communicated. If * a result is needed please see * {@link #scheduleWhile(SubmitterScheduler, long, boolean, Callable, Predicate)}. *

* The returned future will only provide a result once the looping of the task has completed. * Canceling the returned future will prevent future executions from being attempted. Canceling * with an interrupt will transmit the interrupt to the running task if it is currently running. *

* The first execution will either be immediately executed in thread or submitted for immediate * execution on the provided scheduler (depending on {@code firstRunAsync} parameter). Once * this execution completes the result will be provided to the {@link Supplier} to determine if * another schedule should occur to re-run the task. *

* If you want to ensure this does not reschedule forever consider using * {@link #scheduleWhile(SubmitterScheduler, long, boolean, Runnable, Supplier, long)}. * * @since 5.0 * @param scheduler Scheduler to schedule out task executions * @param scheduleDelayMillis Delay after predicate indicating to loop again before re-executed * @param firstRunAsync {@code False} to run first try on invoking thread, {@code true} to submit on scheduler * @param task Task to execute as long as test returns {@code true} * @param loopTest Test to see if scheduled loop should continue * @return Future that will resolve once returned {@link Supplier} returns {@code false} */ public static ListenableFuture scheduleWhile(SubmitterScheduler scheduler, long scheduleDelayMillis, boolean firstRunAsync, Runnable task, Supplier loopTest) { return scheduleWhile(scheduler, scheduleDelayMillis, firstRunAsync, task, loopTest, -1); } /** * Executes a task, checking the result from the task to see if it needs to reschedule the task * again. This can be a good way to implement retry logic where a result ultimately needs to be * communicated through a future. *

* The returned future will only provide a result once the looping of the task has completed, or * until the provided timeout is reached. If the timeout is reached then the task wont be * rescheduled. Even if only 1 millisecond before timeout, the entire * {@code rescheduleDelayMillis} will be provided for the next attempt's scheduled delay. On a * timeout, if {@code true} was provided for {@code timeoutProvideLastValue} then the future * will be resolved with the last result provided. If {@code false} was provided then the * future will complete in an error state, the cause of which being a {@link TimeoutException}. * Canceling the returned future will prevent future executions from being attempted. Canceling * with an interrupt will transmit the interrupt to the running task if it is currently running. *

* The first execution will either be immediately executed in thread or submitted for immediate * execution on the provided scheduler (depending on {@code firstRunAsync} parameter). Once * this execution completes the result will be provided to the {@link Supplier} to determine if * another schedule should occur to re-run the task. * * @since 5.0 * @param scheduler Scheduler to schedule out task executions * @param scheduleDelayMillis Delay after predicate indicating to loop again before re-executed * @param firstRunAsync {@code False} to run first try on invoking thread, {@code true} to submit on scheduler * @param task Task to execute as long as test returns {@code true} * @param loopTest Test to see if scheduled loop should continue * @param timeoutMillis If greater than zero, wont reschedule and instead will just return the last result * @return Future that will resolve once returned {@link Supplier} returns {@code false} */ public static ListenableFuture scheduleWhile(SubmitterScheduler scheduler, long scheduleDelayMillis, boolean firstRunAsync, Runnable task, Supplier loopTest, long timeoutMillis) { ArgumentVerifier.assertNotNull(task, "task"); ArgumentVerifier.assertNotNull(loopTest, "loopTest"); return scheduleWhile(scheduler, scheduleDelayMillis, firstRunAsync, RunnableCallableAdapter.adapt(task, null), (ignored) -> loopTest.get(), timeoutMillis, false); } /** * Executes a task, checking the result from the task to see if it needs to reschedule the task * again. This can be a good way to implement retry logic where a result ultimately needs to be * communicated through a future. *

* The returned future will only provide a result once the looping of the task has completed. * Canceling the returned future will prevent future executions from being attempted. Canceling * with an interrupt will transmit the interrupt to the running task if it is currently running. *

* The first execution will either be immediately executed in thread or submitted for immediate * execution on the provided scheduler (depending on {@code firstRunAsync} parameter). Once * this execution completes the result will be provided to the {@link Predicate} to determine if * another schedule should occur to re-run the task. *

* If you want to ensure this does not reschedule forever consider using * {@link #scheduleWhile(SubmitterScheduler, long, boolean, Callable, Predicate, long, boolean)}. * * @since 5.0 * @param The result object type returned by the task and provided by the future * @param scheduler Scheduler to schedule out task executions * @param scheduleDelayMillis Delay after predicate indicating to loop again before re-executed * @param firstRunAsync {@code False} to run first try on invoking thread, {@code true} to submit on scheduler * @param task Task which will provide result to compare in provided {@code Predicate} * @param loopTest Test for result to see if scheduled loop should continue * @return Future that will resolve once returned {@link Predicate} returns {@code false} */ public static ListenableFuture scheduleWhile(SubmitterScheduler scheduler, long scheduleDelayMillis, boolean firstRunAsync, Callable task, Predicate loopTest) { return scheduleWhile(scheduler, scheduleDelayMillis, firstRunAsync, task, loopTest, -1, false); } /** * Executes a task, checking the result from the task to see if it needs to reschedule the task * again. This can be a good way to implement retry logic where a result ultimately needs to be * communicated through a future. *

* The returned future will only provide a result once the looping of the task has completed, or * until the provided timeout is reached. If the timeout is reached then the task wont be * rescheduled. Even if only 1 millisecond before timeout, the entire * {@code rescheduleDelayMillis} will be provided for the next attempt's scheduled delay. On a * timeout, if {@code true} was provided for {@code timeoutProvideLastValue} then the future * will be resolved with the last result provided. If {@code false} was provided then the * future will complete in an error state, the cause of which being a {@link TimeoutException}. * Canceling the returned future will prevent future executions from being attempted. Canceling * with an interrupt will transmit the interrupt to the running task if it is currently running. *

* The first execution will either be immediately executed in thread or submitted for immediate * execution on the provided scheduler (depending on {@code firstRunAsync} parameter). Once * this execution completes the result will be provided to the {@link Predicate} to determine if * another schedule should occur to re-run the task. * * @since 5.0 * @param The result object type returned by the task and provided by the future * @param scheduler Scheduler to schedule out task executions * @param scheduleDelayMillis Delay after predicate indicating to loop again before re-executed * @param firstRunAsync {@code False} to run first try on invoking thread, {@code true} to submit on scheduler * @param task Task which will provide result to compare in provided {@code Predicate} * @param loopTest Test for result to see if scheduled loop should continue * @param timeoutMillis If greater than zero, wont reschedule and instead will just return the last result * @param timeoutProvideLastValue On timeout {@code false} will complete with a TimeoutException, * {@code true} completes with the last result * @return Future that will resolve once returned {@link Predicate} returns {@code false} or timeout is reached */ public static ListenableFuture scheduleWhile(SubmitterScheduler scheduler, long scheduleDelayMillis, boolean firstRunAsync, Callable task, Predicate loopTest, long timeoutMillis, boolean timeoutProvideLastValue) { return scheduleWhile(scheduler, scheduleDelayMillis, (firstRunAsync ? scheduler : SameThreadSubmitterExecutor.instance()).submit(task), task, loopTest, timeoutMillis, timeoutProvideLastValue); } /** * Executes a task, checking the result from the task to see if it needs to reschedule the task * again. This can be a good way to implement retry logic where a result ultimately needs to be * communicated through a future. *

* The returned future will only provide a result once the looping of the task has completed. * Canceling the returned future will prevent future executions from being attempted. Canceling * with an interrupt will transmit the interrupt to the running task if it is currently running. *

* The first execution will happen as soon as the provided {@code startingFuture} completes. *

* If you want to ensure this does not reschedule forever consider using * {@link #scheduleWhile(SubmitterScheduler, long, ListenableFuture, Callable, Predicate, long, boolean)}. * * @since 5.0 * @param The result object type returned by the task and provided by the future * @param scheduler Scheduler to schedule out task executions * @param scheduleDelayMillis Delay after predicate indicating to loop again before re-executed * @param startingFuture Future to use for first result to test for loop * @param task Task which will provide result to compare in provided {@code Predicate} * @param loopTest Test for result to see if scheduled loop should continue * @return Future that will resolve once returned {@link Predicate} returns {@code false} */ public static ListenableFuture scheduleWhile(SubmitterScheduler scheduler, long scheduleDelayMillis, ListenableFuture startingFuture, Callable task, Predicate loopTest) { return scheduleWhile(scheduler, scheduleDelayMillis, startingFuture, task, loopTest, -1, false); } /** * Executes a task, checking the result from the task to see if it needs to reschedule the task * again. This can be a good way to implement retry logic where a result ultimately needs to be * communicated through a future. *

* The returned future will only provide a result once the looping of the task has completed, or * until the provided timeout is reached. If the timeout is reached then the task wont be * rescheduled. Even if only 1 millisecond before timeout, the entire * {@code rescheduleDelayMillis} will be provided for the next attempt's scheduled delay. On a * timeout, if {@code true} was provided for {@code timeoutProvideLastValue} then the future * will be resolved with the last result provided. If {@code false} was provided then the * future will complete in an error state, the cause of which being a {@link TimeoutException}. * Canceling the returned future will prevent future executions from being attempted. Canceling * with an interrupt will transmit the interrupt to the running task if it is currently running. *

* The first execution will happen as soon as the provided {@code startingFuture} completes. * * @since 5.0 * @param The result object type returned by the task and provided by the future * @param scheduler Scheduler to schedule out task executions * @param scheduleDelayMillis Delay after predicate indicating to loop again before re-executed * @param startingFuture Future to use for first result to test for loop * @param task Task which will provide result to compare in provided {@code Predicate} * @param loopTest Test for result to see if scheduled loop should continue * @param timeoutMillis If greater than zero, wont reschedule and instead will just return the last result * @param timeoutProvideLastValue On timeout {@code false} will complete with a TimeoutException, * {@code true} completes with the last result * @return Future that will resolve once returned {@link Predicate} returns {@code false} */ public static ListenableFuture scheduleWhile(SubmitterScheduler scheduler, long scheduleDelayMillis, ListenableFuture startingFuture, Callable task, Predicate loopTest, long timeoutMillis, boolean timeoutProvideLastValue) { ArgumentVerifier.assertNotNull(scheduler, "scheduler"); ArgumentVerifier.assertNotNegative(scheduleDelayMillis, "scheduleDelayMillis"); ArgumentVerifier.assertNotNull(startingFuture, "startingFuture"); ArgumentVerifier.assertNotNull(task, "task"); ArgumentVerifier.assertNotNull(loopTest, "loopTest"); ListenableFuture alreadyDoneResult = shortcutAsyncWhile(startingFuture, loopTest); if (alreadyDoneResult != null) { return alreadyDoneResult; } long startTime = timeoutMillis > 0 ? Clock.accurateForwardProgressingMillis() : -1; // can not assert not resolved in parallel because user may cancel future at any point CancelDelegateSettableListenableFuture resultFuture = new CancelDelegateSettableListenableFuture<>(startingFuture, scheduler); Callable cancelCheckingTask = new Callable() { @Override public T call() throws Exception { // set thread before check canceled state so if canceled with interrupt we will interrupt the call resultFuture.setRunningThread(Thread.currentThread()); try { if (! resultFuture.isCancelled()) { return task.call(); } else { // result future is already complete, so throw to avoid executing, but only throw such // that we wont attempt to do anything with the result future throw FailurePropogatingFutureCallback.IGNORED_FAILURE; } } finally { resultFuture.setRunningThread(null); } } }; startingFuture.callback(new FailurePropogatingFutureCallback(resultFuture) { @Override public void handleResult(T result) { try { if (startTime > 0 && Clock.lastKnownForwardProgressingMillis() - startTime > timeoutMillis) { if (timeoutProvideLastValue) { resultFuture.setResult(result); } else { resultFuture.setFailure(new TimeoutException()); } } else if (loopTest.test(result)) { ListenableFuture lf = scheduler.submitScheduled(cancelCheckingTask, scheduleDelayMillis); resultFuture.updateDelegateFuture(lf); // TODO - if future is always already complete, this may StackOverflow lf.callback(this); // add this to check again once execution completes } else { // once we have our result, this will end our loop resultFuture.setResult(result); } } catch (Throwable t) { // failure likely from the predicate test, handle exception // so the behavior is closer to if the exception was thrown from a task submitted to the pool ExceptionUtils.handleException(t); resultFuture.setFailure(t); } } }, null, null); return resultFuture; } /** * Similar to {@link #scheduleWhile(SubmitterScheduler, long, boolean, Callable, Predicate)} * except that no executor is needed because the callable instead will return a future from the * provided async submission (which may be a scheduled task or otherwise). *

* In the end this is just another way to have a loop of async actions, checking results as they * are provided but not resolving the returned future till the async action completes. * * @param The result object type returned by the task and provided by the future * @param asyncTask Callable to produce a {@link ListenableFuture} for when a result is ready * @param loopTest The test to check the ready result to see if we need to keep looping * @return Future that will resolve once returned {@link Predicate} returns {@code false} */ public static ListenableFuture executeWhile(Callable> asyncTask, Predicate loopTest) { return executeWhile(asyncTask, loopTest, -1, false); } /** * Similar to {@link #scheduleWhile(SubmitterScheduler, long, boolean, Callable, Predicate, long, boolean)} * except that no executor is needed because the callable instead will return a future from the * provided async submission (which may be a scheduled task or otherwise). *

* In the end this is just another way to have a loop of async actions, checking results as they * are provided but not resolving the returned future till the async action completes. On a * timeout, if {@code true} was provided for {@code timeoutProvideLastValue} then the future * will be resolved with the last result provided. If {@code false} was provided then the * future will complete in an error state, the cause of which being a {@link TimeoutException}. * Canceling the returned future will prevent future executions from being attempted. Canceling * with an interrupt will transmit the interrupt to the running task if it is currently running. * * @param The result object type returned by the task and provided by the future * @param asyncTask Callable to produce a {@link ListenableFuture} for when a result is ready * @param loopTest The test to check the ready result to see if we need to keep looping * @param timeoutMillis If greater than zero, wont reschedule and instead will just return the last result * @param timeoutProvideLastValue On timeout {@code false} will complete with a TimeoutException, * {@code true} completes with the last result * @return Future that will resolve once returned {@link Predicate} returns {@code false} */ public static ListenableFuture executeWhile(Callable> asyncTask, Predicate loopTest, long timeoutMillis, boolean timeoutProvideLastValue) { ArgumentVerifier.assertNotNull(asyncTask, "asyncTask"); ArgumentVerifier.assertNotNull(loopTest, "loopTest"); try { return executeWhile(asyncTask.call(), asyncTask, loopTest, timeoutMillis, timeoutProvideLastValue); } catch (Exception e) { return immediateFailureFuture(e); } } /** * Similar to {@link #scheduleWhile(SubmitterScheduler, long, ListenableFuture, Callable, Predicate)} * except that no executor is needed because the callable instead will return a future from the * provided async submission (which may be a scheduled task or otherwise). *

* In the end this is just another way to have a loop of async actions, checking results as they * are provided but not resolving the returned future till the async action completes. * * @param The result object type returned by the task and provided by the future * @param startingFuture Future to use for first result to test for loop * @param asyncTask Callable to produce a {@link ListenableFuture} for when a result is ready * @param loopTest The test to check the ready result to see if we need to keep looping * @return Future that will resolve once returned {@link Predicate} returns {@code false} */ public static ListenableFuture executeWhile(ListenableFuture startingFuture, Callable> asyncTask, Predicate loopTest) { return executeWhile(startingFuture, asyncTask, loopTest, -1, false); } /** * Similar to {@link #scheduleWhile(SubmitterScheduler, long, ListenableFuture, Callable, Predicate, long, boolean)} * except that no executor is needed because the callable instead will return a future from the * provided async submission (which may be a scheduled task or otherwise). *

* In the end this is just another way to have a loop of async actions, checking results as they * are provided but not resolving the returned future till the async action completes. On a * timeout, if {@code true} was provided for {@code timeoutProvideLastValue} then the future * will be resolved with the last result provided. If {@code false} was provided then the * future will complete in an error state, the cause of which being a {@link TimeoutException}. * Canceling the returned future will prevent future executions from being attempted. Canceling * with an interrupt will transmit the interrupt to the running task if it is currently running. * * @param The result object type returned by the task and provided by the future * @param startingFuture Future to use for first result to test for loop * @param asyncTask Callable to produce a {@link ListenableFuture} for when a result is ready * @param loopTest The test to check the ready result to see if we need to keep looping * @param timeoutMillis If greater than zero, wont reschedule and instead will just return the last result * @param lastValueOnTimeout On timeout {@code false} will complete with a TimeoutException, * {@code true} completes with the last result * @return Future that will resolve once returned {@link Predicate} returns {@code false} */ public static ListenableFuture executeWhile(ListenableFuture startingFuture, Callable> asyncTask, Predicate loopTest, long timeoutMillis, boolean lastValueOnTimeout) { ArgumentVerifier.assertNotNull(startingFuture, "startingFuture"); ArgumentVerifier.assertNotNull(asyncTask, "asyncTask"); ArgumentVerifier.assertNotNull(loopTest, "loopTest"); ListenableFuture alreadyDoneResult = shortcutAsyncWhile(startingFuture, loopTest); if (alreadyDoneResult != null) { return alreadyDoneResult; } long startTime = timeoutMillis > 0 ? Clock.accurateForwardProgressingMillis() : -1; CancelDelegateSettableListenableFuture resultFuture = new CancelDelegateSettableListenableFuture<>(startingFuture, null); startingFuture.callback(new FailurePropogatingFutureCallback(resultFuture) { @Override public void handleResult(T result) { resultFuture.setRunningThread(Thread.currentThread()); try { while (loopTest.test(result)) { if (startTime > -1 && Clock.lastKnownForwardProgressingMillis() - startTime > timeoutMillis) { if (lastValueOnTimeout) { resultFuture.setResult(result); } else { resultFuture.setFailure(new TimeoutException()); } return; } if (resultFuture.isCancelled()) { // already completed, just break the loop return; } ListenableFuture lf = asyncTask.call(); if (lf.isDone()) { // prevent StackOverflow when already done futures are returned if (lf.isCompletedExceptionally()) { resultFuture.setFailure(lf.getFailure()); return; } else { result = lf.get(); continue; } } else { resultFuture.updateDelegateFuture(lf); lf.callback(this, null, null); return; } } // if broke loop without return, result is ready resultFuture.setResult(result); } catch (Throwable t) { // failure likely from the predicate test, handle exception // so the behavior is closer to if the exception was thrown from a task submitted to the pool ExceptionUtils.handleException(t); resultFuture.setFailure(t); } finally { // unset running thread in case loop broke without final result resultFuture.setRunningThread(null); } } }, null, null); return resultFuture; } /** * Check if we can shortcut our async while loop by producing an already completed future. If * we can do this it will be returned, otherwise {@code null} will be returned. * * @param startingFuture Future to check for already complete result * @param doneTest Test to see if the initial result is valid * @return A future that is already complete with the existing result, otherwise {@code null} */ @SuppressWarnings("unchecked") private static ListenableFuture shortcutAsyncWhile(ListenableFuture startingFuture, Predicate doneTest) { if (startingFuture.isDone()) { try { if (startingFuture.isCompletedExceptionally()) { return (ListenableFuture) startingFuture; } else if (! doneTest.test(startingFuture.get())) { return immediateResultFuture(startingFuture.get()); } } catch (Throwable t) { ExceptionUtils.handleException(t); return immediateFailureFuture(t); } } return null; } }