org.apache.flink.runtime.concurrent.FutureUtils Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.flink.runtime.concurrent;
import org.apache.flink.api.common.time.Deadline;
import org.apache.flink.api.common.time.Time;
import org.apache.flink.runtime.util.ExecutorThreadFactory;
import org.apache.flink.runtime.util.FatalExitExceptionHandler;
import org.apache.flink.util.ExceptionUtils;
import org.apache.flink.util.function.RunnableWithException;
import org.apache.flink.util.function.SupplierWithException;
import akka.dispatch.OnComplete;
import javax.annotation.Nullable;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RunnableFuture;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import scala.concurrent.Future;
import static org.apache.flink.util.Preconditions.checkNotNull;
/**
* A collection of utilities that expand the usage of {@link CompletableFuture}.
*/
public class FutureUtils {
private static final CompletableFuture COMPLETED_VOID_FUTURE = CompletableFuture.completedFuture(null);
/**
* Returns a completed future of type {@link Void}.
*
* @return a completed future of type {@link Void}
*/
public static CompletableFuture completedVoidFuture() {
return COMPLETED_VOID_FUTURE;
}
/**
* Fakes asynchronous execution by immediately executing the operation and returns a (exceptionally) completed
* future.
*
* @param operation to executed
* @param type of the result
*/
public static CompletableFuture runSync(Callable operation) {
try {
return CompletableFuture.completedFuture(operation.call());
} catch (Exception e) {
return completedExceptionally(e);
}
}
// ------------------------------------------------------------------------
// retrying operations
// ------------------------------------------------------------------------
/**
* Retry the given operation the given number of times in case of a failure.
*
* @param operation to executed
* @param retries if the operation failed
* @param executor to use to run the futures
* @param type of the result
* @return Future containing either the result of the operation or a {@link RetryException}
*/
public static CompletableFuture retry(
final Supplier> operation,
final int retries,
final Executor executor) {
final CompletableFuture resultFuture = new CompletableFuture<>();
retryOperation(resultFuture, operation, retries, executor);
return resultFuture;
}
/**
* Helper method which retries the provided operation in case of a failure.
*
* @param resultFuture to complete
* @param operation to retry
* @param retries until giving up
* @param executor to run the futures
* @param type of the future's result
*/
private static void retryOperation(
final CompletableFuture resultFuture,
final Supplier> operation,
final int retries,
final Executor executor) {
if (!resultFuture.isDone()) {
final CompletableFuture operationFuture = operation.get();
operationFuture.whenCompleteAsync(
(t, throwable) -> {
if (throwable != null) {
if (throwable instanceof CancellationException) {
resultFuture.completeExceptionally(new RetryException("Operation future was cancelled.", throwable));
} else {
if (retries > 0) {
retryOperation(
resultFuture,
operation,
retries - 1,
executor);
} else {
resultFuture.completeExceptionally(new RetryException("Could not complete the operation. Number of retries " +
"has been exhausted.", throwable));
}
}
} else {
resultFuture.complete(t);
}
},
executor);
resultFuture.whenComplete(
(t, throwable) -> operationFuture.cancel(false));
}
}
/**
* Retry the given operation with the given delay in between failures.
*
* @param operation to retry
* @param retries number of retries
* @param retryDelay delay between retries
* @param retryPredicate Predicate to test whether an exception is retryable
* @param scheduledExecutor executor to be used for the retry operation
* @param type of the result
* @return Future which retries the given operation a given amount of times and delays the retry in case of failures
*/
public static CompletableFuture retryWithDelay(
final Supplier> operation,
final int retries,
final Time retryDelay,
final Predicate retryPredicate,
final ScheduledExecutor scheduledExecutor) {
final CompletableFuture resultFuture = new CompletableFuture<>();
retryOperationWithDelay(
resultFuture,
operation,
retries,
retryDelay,
retryPredicate,
scheduledExecutor);
return resultFuture;
}
/**
* Retry the given operation with the given delay in between failures.
*
* @param operation to retry
* @param retries number of retries
* @param retryDelay delay between retries
* @param scheduledExecutor executor to be used for the retry operation
* @param type of the result
* @return Future which retries the given operation a given amount of times and delays the retry in case of failures
*/
public static CompletableFuture retryWithDelay(
final Supplier> operation,
final int retries,
final Time retryDelay,
final ScheduledExecutor scheduledExecutor) {
return retryWithDelay(
operation,
retries,
retryDelay,
(throwable) -> true,
scheduledExecutor);
}
/**
* Schedule the operation with the given delay.
*
* @param operation to schedule
* @param delay delay to schedule
* @param scheduledExecutor executor to be used for the operation
* @return Future which schedules the given operation with given delay.
*/
public static CompletableFuture scheduleWithDelay(
final Runnable operation,
final Time delay,
final ScheduledExecutor scheduledExecutor) {
Supplier operationSupplier = () -> {
operation.run();
return null;
};
return scheduleWithDelay(operationSupplier, delay, scheduledExecutor);
}
/**
* Schedule the operation with the given delay.
*
* @param operation to schedule
* @param delay delay to schedule
* @param scheduledExecutor executor to be used for the operation
* @param type of the result
* @return Future which schedules the given operation with given delay.
*/
public static CompletableFuture scheduleWithDelay(
final Supplier operation,
final Time delay,
final ScheduledExecutor scheduledExecutor) {
final CompletableFuture resultFuture = new CompletableFuture<>();
ScheduledFuture> scheduledFuture = scheduledExecutor.schedule(
() -> {
try {
resultFuture.complete(operation.get());
} catch (Throwable t) {
resultFuture.completeExceptionally(t);
}
},
delay.getSize(),
delay.getUnit()
);
resultFuture.whenComplete(
(t, throwable) -> {
if (!scheduledFuture.isDone()) {
scheduledFuture.cancel(false);
}
});
return resultFuture;
}
private static void retryOperationWithDelay(
final CompletableFuture resultFuture,
final Supplier> operation,
final int retries,
final Time retryDelay,
final Predicate retryPredicate,
final ScheduledExecutor scheduledExecutor) {
if (!resultFuture.isDone()) {
final CompletableFuture operationResultFuture = operation.get();
operationResultFuture.whenComplete(
(t, throwable) -> {
if (throwable != null) {
if (throwable instanceof CancellationException) {
resultFuture.completeExceptionally(new RetryException("Operation future was cancelled.", throwable));
} else {
throwable = ExceptionUtils.stripExecutionException(throwable);
if (!retryPredicate.test(throwable)) {
resultFuture.completeExceptionally(throwable);
} else if (retries > 0) {
final ScheduledFuture> scheduledFuture = scheduledExecutor.schedule(
(Runnable) () -> retryOperationWithDelay(resultFuture, operation, retries - 1, retryDelay, retryPredicate, scheduledExecutor),
retryDelay.toMilliseconds(),
TimeUnit.MILLISECONDS);
resultFuture.whenComplete(
(innerT, innerThrowable) -> scheduledFuture.cancel(false));
} else {
RetryException retryException = new RetryException(
"Could not complete the operation. Number of retries has been exhausted.",
throwable);
resultFuture.completeExceptionally(retryException);
}
}
} else {
resultFuture.complete(t);
}
});
resultFuture.whenComplete(
(t, throwable) -> operationResultFuture.cancel(false));
}
}
/**
* Retry the given operation with the given delay in between successful completions where the
* result does not match a given predicate.
*
* @param operation to retry
* @param retryDelay delay between retries
* @param deadline A deadline that specifies at what point we should stop retrying
* @param acceptancePredicate Predicate to test whether the result is acceptable
* @param scheduledExecutor executor to be used for the retry operation
* @param type of the result
* @return Future which retries the given operation a given amount of times and delays the retry
* in case the predicate isn't matched
*/
public static CompletableFuture retrySuccessfulWithDelay(
final Supplier> operation,
final Time retryDelay,
final Deadline deadline,
final Predicate acceptancePredicate,
final ScheduledExecutor scheduledExecutor) {
final CompletableFuture resultFuture = new CompletableFuture<>();
retrySuccessfulOperationWithDelay(
resultFuture,
operation,
retryDelay,
deadline,
acceptancePredicate,
scheduledExecutor);
return resultFuture;
}
private static void retrySuccessfulOperationWithDelay(
final CompletableFuture resultFuture,
final Supplier> operation,
final Time retryDelay,
final Deadline deadline,
final Predicate acceptancePredicate,
final ScheduledExecutor scheduledExecutor) {
if (!resultFuture.isDone()) {
final CompletableFuture operationResultFuture = operation.get();
operationResultFuture.whenComplete(
(t, throwable) -> {
if (throwable != null) {
if (throwable instanceof CancellationException) {
resultFuture.completeExceptionally(new RetryException("Operation future was cancelled.", throwable));
} else {
resultFuture.completeExceptionally(throwable);
}
} else {
if (acceptancePredicate.test(t)) {
resultFuture.complete(t);
} else if (deadline.hasTimeLeft()) {
final ScheduledFuture> scheduledFuture = scheduledExecutor.schedule(
(Runnable) () -> retrySuccessfulOperationWithDelay(resultFuture, operation, retryDelay, deadline, acceptancePredicate, scheduledExecutor),
retryDelay.toMilliseconds(),
TimeUnit.MILLISECONDS);
resultFuture.whenComplete(
(innerT, innerThrowable) -> scheduledFuture.cancel(false));
} else {
resultFuture.completeExceptionally(
new RetryException("Could not satisfy the predicate within the allowed time."));
}
}
});
resultFuture.whenComplete(
(t, throwable) -> operationResultFuture.cancel(false));
}
}
/**
* Exception with which the returned future is completed if the {@link #retry(Supplier, int, Executor)}
* operation fails.
*/
public static class RetryException extends Exception {
private static final long serialVersionUID = 3613470781274141862L;
public RetryException(String message) {
super(message);
}
public RetryException(String message, Throwable cause) {
super(message, cause);
}
public RetryException(Throwable cause) {
super(cause);
}
}
/**
* Times the given future out after the timeout.
*
* @param future to time out
* @param timeout after which the given future is timed out
* @param timeUnit time unit of the timeout
* @param type of the given future
* @return The timeout enriched future
*/
public static CompletableFuture orTimeout(CompletableFuture future, long timeout, TimeUnit timeUnit) {
return orTimeout(future, timeout, timeUnit, Executors.directExecutor());
}
/**
* Times the given future out after the timeout.
*
* @param future to time out
* @param timeout after which the given future is timed out
* @param timeUnit time unit of the timeout
* @param timeoutFailExecutor executor that will complete the future exceptionally after the timeout is reached
* @param type of the given future
* @return The timeout enriched future
*/
public static CompletableFuture orTimeout(
CompletableFuture future,
long timeout,
TimeUnit timeUnit,
Executor timeoutFailExecutor) {
if (!future.isDone()) {
final ScheduledFuture> timeoutFuture = Delayer.delay(
() -> timeoutFailExecutor.execute(new Timeout(future)), timeout, timeUnit);
future.whenComplete((T value, Throwable throwable) -> {
if (!timeoutFuture.isDone()) {
timeoutFuture.cancel(false);
}
});
}
return future;
}
// ------------------------------------------------------------------------
// Future actions
// ------------------------------------------------------------------------
/**
* Run the given {@code RunnableFuture} if it is not done, and then retrieves its result.
* @param future to run if not done and get
* @param type of the result
* @return the result after running the future
* @throws ExecutionException if a problem occurred
* @throws InterruptedException if the current thread has been interrupted
*/
public static T runIfNotDoneAndGet(RunnableFuture future) throws ExecutionException, InterruptedException {
if (null == future) {
return null;
}
if (!future.isDone()) {
future.run();
}
return future.get();
}
/**
* Run the given action after the completion of the given future. The given future can be
* completed normally or exceptionally. In case of an exceptional completion the, the
* action's exception will be added to the initial exception.
*
* @param future to wait for its completion
* @param runnable action which is triggered after the future's completion
* @return Future which is completed after the action has completed. This future can contain an exception,
* if an error occurred in the given future or action.
*/
public static CompletableFuture runAfterwards(CompletableFuture> future, RunnableWithException runnable) {
return runAfterwardsAsync(future, runnable, Executors.directExecutor());
}
/**
* Run the given action after the completion of the given future. The given future can be
* completed normally or exceptionally. In case of an exceptional completion the, the
* action's exception will be added to the initial exception.
*
* @param future to wait for its completion
* @param runnable action which is triggered after the future's completion
* @return Future which is completed after the action has completed. This future can contain an exception,
* if an error occurred in the given future or action.
*/
public static CompletableFuture runAfterwardsAsync(CompletableFuture> future, RunnableWithException runnable) {
return runAfterwardsAsync(future, runnable, ForkJoinPool.commonPool());
}
/**
* Run the given action after the completion of the given future. The given future can be
* completed normally or exceptionally. In case of an exceptional completion the, the
* action's exception will be added to the initial exception.
*
* @param future to wait for its completion
* @param runnable action which is triggered after the future's completion
* @param executor to run the given action
* @return Future which is completed after the action has completed. This future can contain an exception,
* if an error occurred in the given future or action.
*/
public static CompletableFuture runAfterwardsAsync(
CompletableFuture> future,
RunnableWithException runnable,
Executor executor) {
final CompletableFuture resultFuture = new CompletableFuture<>();
future.whenCompleteAsync(
(Object ignored, Throwable throwable) -> {
try {
runnable.run();
} catch (Throwable e) {
throwable = ExceptionUtils.firstOrSuppressed(e, throwable);
}
if (throwable != null) {
resultFuture.completeExceptionally(throwable);
} else {
resultFuture.complete(null);
}
},
executor);
return resultFuture;
}
/**
* Run the given asynchronous action after the completion of the given future. The given future can be
* completed normally or exceptionally. In case of an exceptional completion, the
* asynchronous action's exception will be added to the initial exception.
*
* @param future to wait for its completion
* @param composedAction asynchronous action which is triggered after the future's completion
* @return Future which is completed after the asynchronous action has completed. This future can contain
* an exception if an error occurred in the given future or asynchronous action.
*/
public static CompletableFuture composeAfterwards(
CompletableFuture> future,
Supplier> composedAction) {
final CompletableFuture resultFuture = new CompletableFuture<>();
future.whenComplete(
(Object outerIgnored, Throwable outerThrowable) -> {
final CompletableFuture> composedActionFuture = composedAction.get();
composedActionFuture.whenComplete(
(Object innerIgnored, Throwable innerThrowable) -> {
if (innerThrowable != null) {
resultFuture.completeExceptionally(ExceptionUtils.firstOrSuppressed(innerThrowable, outerThrowable));
} else if (outerThrowable != null) {
resultFuture.completeExceptionally(outerThrowable);
} else {
resultFuture.complete(null);
}
});
});
return resultFuture;
}
// ------------------------------------------------------------------------
// composing futures
// ------------------------------------------------------------------------
/**
* Creates a future that is complete once multiple other futures completed.
* The future fails (completes exceptionally) once one of the futures in the
* conjunction fails. Upon successful completion, the future returns the
* collection of the futures' results.
*
* The ConjunctFuture gives access to how many Futures in the conjunction have already
* completed successfully, via {@link ConjunctFuture#getNumFuturesCompleted()}.
*
* @param futures The futures that make up the conjunction. No null entries are allowed.
* @return The ConjunctFuture that completes once all given futures are complete (or one fails).
*/
public static ConjunctFuture> combineAll(Collection extends CompletableFuture extends T>> futures) {
checkNotNull(futures, "futures");
return new ResultConjunctFuture<>(futures);
}
/**
* Creates a future that is complete once all of the given futures have completed.
* The future fails (completes exceptionally) once one of the given futures
* fails.
*
* The ConjunctFuture gives access to how many Futures have already
* completed successfully, via {@link ConjunctFuture#getNumFuturesCompleted()}.
*
* @param futures The futures to wait on. No null entries are allowed.
* @return The WaitingFuture that completes once all given futures are complete (or one fails).
*/
public static ConjunctFuture waitForAll(Collection extends CompletableFuture>> futures) {
checkNotNull(futures, "futures");
return new WaitingConjunctFuture(futures);
}
/**
* A future that is complete once multiple other futures completed. The futures are not
* necessarily of the same type. The ConjunctFuture fails (completes exceptionally) once
* one of the Futures in the conjunction fails.
*
* The advantage of using the ConjunctFuture over chaining all the futures (such as via
* {@link CompletableFuture#thenCombine(CompletionStage, BiFunction)} )}) is that ConjunctFuture
* also tracks how many of the Futures are already complete.
*/
public abstract static class ConjunctFuture extends CompletableFuture {
/**
* Gets the total number of Futures in the conjunction.
* @return The total number of Futures in the conjunction.
*/
public abstract int getNumFuturesTotal();
/**
* Gets the number of Futures in the conjunction that are already complete.
* @return The number of Futures in the conjunction that are already complete
*/
public abstract int getNumFuturesCompleted();
}
/**
* The implementation of the {@link ConjunctFuture} which returns its Futures' result as a collection.
*/
private static class ResultConjunctFuture extends ConjunctFuture> {
/** The total number of futures in the conjunction. */
private final int numTotal;
/** The number of futures in the conjunction that are already complete. */
private final AtomicInteger numCompleted = new AtomicInteger(0);
/** The set of collected results so far. */
private volatile T[] results;
/** The function that is attached to all futures in the conjunction. Once a future
* is complete, this function tracks the completion or fails the conjunct.
*/
private void handleCompletedFuture(int index, T value, Throwable throwable) {
if (throwable != null) {
completeExceptionally(throwable);
} else {
results[index] = value;
if (numCompleted.incrementAndGet() == numTotal) {
complete(Arrays.asList(results));
}
}
}
@SuppressWarnings("unchecked")
ResultConjunctFuture(Collection extends CompletableFuture extends T>> resultFutures) {
this.numTotal = resultFutures.size();
results = (T[]) new Object[numTotal];
if (resultFutures.isEmpty()) {
complete(Collections.emptyList());
}
else {
int counter = 0;
for (CompletableFuture extends T> future : resultFutures) {
final int index = counter;
counter++;
future.whenComplete((value, throwable) -> handleCompletedFuture(index, value, throwable));
}
}
}
@Override
public int getNumFuturesTotal() {
return numTotal;
}
@Override
public int getNumFuturesCompleted() {
return numCompleted.get();
}
}
/**
* Implementation of the {@link ConjunctFuture} interface which waits only for the completion
* of its futures and does not return their values.
*/
private static final class WaitingConjunctFuture extends ConjunctFuture {
/** Number of completed futures. */
private final AtomicInteger numCompleted = new AtomicInteger(0);
/** Total number of futures to wait on. */
private final int numTotal;
/** Method which increments the atomic completion counter and completes or fails the WaitingFutureImpl. */
private void handleCompletedFuture(Object ignored, Throwable throwable) {
if (throwable == null) {
if (numTotal == numCompleted.incrementAndGet()) {
complete(null);
}
} else {
completeExceptionally(throwable);
}
}
private WaitingConjunctFuture(Collection extends CompletableFuture>> futures) {
this.numTotal = futures.size();
if (futures.isEmpty()) {
complete(null);
} else {
for (java.util.concurrent.CompletableFuture> future : futures) {
future.whenComplete(this::handleCompletedFuture);
}
}
}
@Override
public int getNumFuturesTotal() {
return numTotal;
}
@Override
public int getNumFuturesCompleted() {
return numCompleted.get();
}
}
/**
* Creates a {@link ConjunctFuture} which is only completed after all given futures have completed.
* Unlike {@link FutureUtils#waitForAll(Collection)}, the resulting future won't be completed directly
* if one of the given futures is completed exceptionally. Instead, all occurring exception will be
* collected and combined to a single exception. If at least on exception occurs, then the resulting
* future will be completed exceptionally.
*
* @param futuresToComplete futures to complete
* @return Future which is completed after all given futures have been completed.
*/
public static ConjunctFuture completeAll(Collection extends CompletableFuture>> futuresToComplete) {
return new CompletionConjunctFuture(futuresToComplete);
}
/**
* {@link ConjunctFuture} implementation which is completed after all the given futures have been
* completed. Exceptional completions of the input futures will be recorded but it won't trigger the
* early completion of this future.
*/
private static final class CompletionConjunctFuture extends ConjunctFuture {
private final Object lock = new Object();
private final int numFuturesTotal;
private int futuresCompleted;
private Throwable globalThrowable;
private CompletionConjunctFuture(Collection extends CompletableFuture>> futuresToComplete) {
numFuturesTotal = futuresToComplete.size();
futuresCompleted = 0;
globalThrowable = null;
if (futuresToComplete.isEmpty()) {
complete(null);
} else {
for (CompletableFuture> completableFuture : futuresToComplete) {
completableFuture.whenComplete(this::completeFuture);
}
}
}
private void completeFuture(Object ignored, Throwable throwable) {
synchronized (lock) {
futuresCompleted++;
if (throwable != null) {
globalThrowable = ExceptionUtils.firstOrSuppressed(throwable, globalThrowable);
}
if (futuresCompleted == numFuturesTotal) {
if (globalThrowable != null) {
completeExceptionally(globalThrowable);
} else {
complete(null);
}
}
}
}
@Override
public int getNumFuturesTotal() {
return numFuturesTotal;
}
@Override
public int getNumFuturesCompleted() {
synchronized (lock) {
return futuresCompleted;
}
}
}
// ------------------------------------------------------------------------
// Helper methods
// ------------------------------------------------------------------------
/**
* Returns an exceptionally completed {@link CompletableFuture}.
*
* @param cause to complete the future with
* @param type of the future
* @return An exceptionally completed CompletableFuture
*/
public static CompletableFuture completedExceptionally(Throwable cause) {
CompletableFuture result = new CompletableFuture<>();
result.completeExceptionally(cause);
return result;
}
/**
* Returns a future which is completed with the result of the {@link SupplierWithException}.
*
* @param supplier to provide the future's value
* @param executor to execute the supplier
* @param type of the result
* @return Future which is completed with the value of the supplier
*/
public static CompletableFuture supplyAsync(SupplierWithException supplier, Executor executor) {
return CompletableFuture.supplyAsync(
() -> {
try {
return supplier.get();
} catch (Throwable e) {
throw new CompletionException(e);
}
},
executor);
}
/**
* Converts Flink time into a {@link Duration}.
*
* @param time to convert into a Duration
* @return Duration with the length of the given time
*/
public static Duration toDuration(Time time) {
return Duration.ofMillis(time.toMilliseconds());
}
// ------------------------------------------------------------------------
// Converting futures
// ------------------------------------------------------------------------
/**
* Converts a Scala {@link Future} to a {@link CompletableFuture}.
*
* @param scalaFuture to convert to a Java 8 CompletableFuture
* @param type of the future value
* @param type of the original future
* @return Java 8 CompletableFuture
*/
public static CompletableFuture toJava(Future scalaFuture) {
final CompletableFuture result = new CompletableFuture<>();
scalaFuture.onComplete(new OnComplete() {
@Override
public void onComplete(Throwable failure, U success) {
if (failure != null) {
result.completeExceptionally(failure);
} else {
result.complete(success);
}
}
}, Executors.directExecutionContext());
return result;
}
/**
* This function takes a {@link CompletableFuture} and a function to apply to this future. If the input future
* is already done, this function returns {@link CompletableFuture#thenApply(Function)}. Otherwise, the return
* value is {@link CompletableFuture#thenApplyAsync(Function, Executor)} with the given executor.
*
* @param completableFuture the completable future for which we want to apply.
* @param executor the executor to run the apply function if the future is not yet done.
* @param applyFun the function to apply.
* @param type of the input future.
* @param type of the output future.
* @return a completable future that is applying the given function to the input future.
*/
public static CompletableFuture thenApplyAsyncIfNotDone(
CompletableFuture completableFuture,
Executor executor,
Function super IN, ? extends OUT> applyFun) {
return completableFuture.isDone() ?
completableFuture.thenApply(applyFun) :
completableFuture.thenApplyAsync(applyFun, executor);
}
/**
* This function takes a {@link CompletableFuture} and a function to compose with this future. If the input future
* is already done, this function returns {@link CompletableFuture#thenCompose(Function)}. Otherwise, the return
* value is {@link CompletableFuture#thenComposeAsync(Function, Executor)} with the given executor.
*
* @param completableFuture the completable future for which we want to compose.
* @param executor the executor to run the compose function if the future is not yet done.
* @param composeFun the function to compose.
* @param type of the input future.
* @param type of the output future.
* @return a completable future that is a composition of the input future and the function.
*/
public static CompletableFuture thenComposeAsyncIfNotDone(
CompletableFuture completableFuture,
Executor executor,
Function super IN, ? extends CompletionStage> composeFun) {
return completableFuture.isDone() ?
completableFuture.thenCompose(composeFun) :
completableFuture.thenComposeAsync(composeFun, executor);
}
/**
* This function takes a {@link CompletableFuture} and a bi-consumer to call on completion of this future. If the
* input future is already done, this function returns {@link CompletableFuture#whenComplete(BiConsumer)}.
* Otherwise, the return value is {@link CompletableFuture#whenCompleteAsync(BiConsumer, Executor)} with the given
* executor.
*
* @param completableFuture the completable future for which we want to call #whenComplete.
* @param executor the executor to run the whenComplete function if the future is not yet done.
* @param whenCompleteFun the bi-consumer function to call when the future is completed.
* @param type of the input future.
* @return the new completion stage.
*/
public static CompletableFuture whenCompleteAsyncIfNotDone(
CompletableFuture completableFuture,
Executor executor,
BiConsumer super IN, ? super Throwable> whenCompleteFun) {
return completableFuture.isDone() ?
completableFuture.whenComplete(whenCompleteFun) :
completableFuture.whenCompleteAsync(whenCompleteFun, executor);
}
/**
* This function takes a {@link CompletableFuture} and a consumer to accept the result of this future. If the input
* future is already done, this function returns {@link CompletableFuture#thenAccept(Consumer)}. Otherwise, the
* return value is {@link CompletableFuture#thenAcceptAsync(Consumer, Executor)} with the given executor.
*
* @param completableFuture the completable future for which we want to call #thenAccept.
* @param executor the executor to run the thenAccept function if the future is not yet done.
* @param consumer the consumer function to call when the future is completed.
* @param type of the input future.
* @return the new completion stage.
*/
public static CompletableFuture thenAcceptAsyncIfNotDone(
CompletableFuture completableFuture,
Executor executor,
Consumer super IN> consumer) {
return completableFuture.isDone() ?
completableFuture.thenAccept(consumer) :
completableFuture.thenAcceptAsync(consumer, executor);
}
/**
* This function takes a {@link CompletableFuture} and a handler function for the result of this future. If the
* input future is already done, this function returns {@link CompletableFuture#handle(BiFunction)}. Otherwise,
* the return value is {@link CompletableFuture#handleAsync(BiFunction, Executor)} with the given executor.
*
* @param completableFuture the completable future for which we want to call #handle.
* @param executor the executor to run the handle function if the future is not yet done.
* @param handler the handler function to call when the future is completed.
* @param type of the handler input argument.
* @param type of the handler return value.
* @return the new completion stage.
*/
public static CompletableFuture handleAsyncIfNotDone(
CompletableFuture completableFuture,
Executor executor,
BiFunction super IN, Throwable, ? extends OUT> handler) {
return completableFuture.isDone() ?
completableFuture.handle(handler) :
completableFuture.handleAsync(handler, executor);
}
/**
* Gets the result of a completable future without any exception thrown.
*
* @param future the completable future specified.
* @param the type of result
* @return the result of completable future,
* or null if it's unfinished or finished exceptionally
*/
@Nullable
public static T getWithoutException(CompletableFuture future) {
if (future.isDone() && !future.isCompletedExceptionally()) {
try {
return future.get();
} catch (InterruptedException | ExecutionException ignored) {
}
}
return null;
}
/**
* Runnable to complete the given future with a {@link TimeoutException}.
*/
private static final class Timeout implements Runnable {
private final CompletableFuture> future;
private Timeout(CompletableFuture> future) {
this.future = checkNotNull(future);
}
@Override
public void run() {
future.completeExceptionally(new TimeoutException());
}
}
/**
* Delay scheduler used to timeout futures.
*
* This class creates a singleton scheduler used to run the provided actions.
*/
private enum Delayer {
;
static final ScheduledThreadPoolExecutor DELAYER = new ScheduledThreadPoolExecutor(
1,
new ExecutorThreadFactory("FlinkCompletableFutureDelayScheduler"));
/**
* Delay the given action by the given delay.
*
* @param runnable to execute after the given delay
* @param delay after which to execute the runnable
* @param timeUnit time unit of the delay
* @return Future of the scheduled action
*/
private static ScheduledFuture> delay(Runnable runnable, long delay, TimeUnit timeUnit) {
checkNotNull(runnable);
checkNotNull(timeUnit);
return DELAYER.schedule(runnable, delay, timeUnit);
}
}
/**
* Asserts that the given {@link CompletableFuture} is not completed exceptionally. If the future
* is completed exceptionally, then it will call the {@link FatalExitExceptionHandler}.
*
* @param completableFuture to assert for no exceptions
*/
public static void assertNoException(CompletableFuture> completableFuture) {
handleUncaughtException(completableFuture, FatalExitExceptionHandler.INSTANCE);
}
/**
* Checks that the given {@link CompletableFuture} is not completed exceptionally. If the future
* is completed exceptionally, then it will call the given uncaught exception handler.
*
* @param completableFuture to assert for no exceptions
* @param uncaughtExceptionHandler to call if the future is completed exceptionally
*/
public static void handleUncaughtException(CompletableFuture> completableFuture, Thread.UncaughtExceptionHandler uncaughtExceptionHandler) {
checkNotNull(completableFuture).whenComplete((ignored, throwable) -> {
if (throwable != null) {
uncaughtExceptionHandler.uncaughtException(Thread.currentThread(), throwable);
}
});
}
/**
* Forwards the value from the source future to the target future.
*
* @param source future to forward the value from
* @param target future to forward the value to
* @param type of the value
*/
public static void forward(CompletableFuture source, CompletableFuture target) {
source.whenComplete(forwardTo(target));
}
/**
* Forwards the value from the source future to the target future using the provided executor.
*
* @param source future to forward the value from
* @param target future to forward the value to
* @param executor executor to forward the source value to the target future
* @param type of the value
*/
public static void forwardAsync(CompletableFuture source, CompletableFuture target, Executor executor) {
source.whenCompleteAsync(
forwardTo(target),
executor);
}
/**
* Throws the causing exception if the given future is completed exceptionally, otherwise do nothing.
*
* @param future the future to check.
* @throws Exception when the future is completed exceptionally.
*/
public static void throwIfCompletedExceptionally(CompletableFuture> future) throws Exception {
if (future.isCompletedExceptionally()) {
future.get();
}
}
private static BiConsumer forwardTo(CompletableFuture target) {
return (value, throwable) -> {
if (throwable != null) {
target.completeExceptionally(throwable);
} else {
target.complete(value);
}
};
}
}