flink源码分析 - retryWithDelay(固定延迟后再次执行某个方法)

flink版本: flink-1.12.1

调用位置: org.apache.flink.client.program.rest.RestClusterClient#retry

代码核心位置: org.apache.flink.runtime.concurrent.FutureUtils#retryWithDelay,org.apache.flink.runtime.concurrent.FutureUtils#retryOperationWithDelay

调用位置: org.apache.flink.client.program.rest.RestClusterClient

java 复制代码
    private <M extends MessageHeaders<R, P, U>, U extends MessageParameters, R extends RequestBody, P extends ResponseBody>
            CompletableFuture<P> sendRetriableRequest(M messageHeaders, U messageParameters, R request, Collection<FileUpload> filesToUpload,
            Predicate<Throwable> retryPredicate) {
        return retry(() -> getWebMonitorBaseUrl().thenCompose(webMonitorBaseUrl -> {
            try {

                /*************************************************
                 * TODO_MA 马中华 https://blog.csdn.net/zhongqi2513
                 *  注释:
                 */
                return restClient.sendRequest(webMonitorBaseUrl.getHost(), webMonitorBaseUrl.getPort(),
                        messageHeaders, messageParameters, request, filesToUpload);
            } catch(IOException e) {
                throw new CompletionException(e);
            }
        }), retryPredicate);
    }

源码:

java 复制代码
/*
 * 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.checkCompletedNormally;
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<Void> 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<Void> completedVoidFuture() {
        return COMPLETED_VOID_FUTURE;
    }

    /**
     * Fakes asynchronous execution by immediately executing the operation and completing the
     * supplied future either noramlly or exceptionally.
     *
     * @param operation to executed
     * @param <T> type of the result
     */
    public static <T> void completeFromCallable(
            CompletableFuture<T> future, Callable<T> operation) {
        try {
            future.complete(operation.call());
        } catch (Exception e) {
            future.completeExceptionally(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 <T> type of the result
     * @return Future containing either the result of the operation or a {@link RetryException}
     */
    public static <T> CompletableFuture<T> retry(
            final Supplier<CompletableFuture<T>> operation,
            final int retries,
            final Executor executor) {

        return retry(operation, retries, ignore -> true, executor);
    }

    /**
     * Retry the given operation the given number of times in case of a failure only when an
     * exception is retryable.
     *
     * @param operation to executed
     * @param retries if the operation failed
     * @param retryPredicate Predicate to test whether an exception is retryable
     * @param executor to use to run the futures
     * @param <T> type of the result
     * @return Future containing either the result of the operation or a {@link RetryException}
     */
    public static <T> CompletableFuture<T> retry(
            final Supplier<CompletableFuture<T>> operation,
            final int retries,
            final Predicate<Throwable> retryPredicate,
            final Executor executor) {

        final CompletableFuture<T> resultFuture = new CompletableFuture<>();

        retryOperation(resultFuture, operation, retries, retryPredicate, 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 retryPredicate Predicate to test whether an exception is retryable
     * @param executor to run the futures
     * @param <T> type of the future's result
     */
    private static <T> void retryOperation(
            final CompletableFuture<T> resultFuture,
            final Supplier<CompletableFuture<T>> operation,
            final int retries,
            final Predicate<Throwable> retryPredicate,
            final Executor executor) {

        if (!resultFuture.isDone()) {
            final CompletableFuture<T> operationFuture = operation.get();

            operationFuture.whenCompleteAsync(
                    (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(
                                            new RetryException(
                                                    "Stopped retrying the operation because the error is not "
                                                            + "retryable.",
                                                    throwable));
                                } else {
                                    if (retries > 0) {
                                        retryOperation(
                                                resultFuture,
                                                operation,
                                                retries - 1,
                                                retryPredicate,
                                                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 <T> 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 <T> CompletableFuture<T> retryWithDelay(
            final Supplier<CompletableFuture<T>> operation,
            final int retries,
            final Time retryDelay,
            final Predicate<Throwable> retryPredicate,
            final ScheduledExecutor scheduledExecutor) {
        return retryWithDelay(
                operation,
                new FixedRetryStrategy(retries, Duration.ofMillis(retryDelay.toMilliseconds())),
                retryPredicate,
                scheduledExecutor);
    }

    /**
     * Retry the given operation with the given delay in between failures.
     *
     * @param operation to retry
     * @param retryStrategy the RetryStrategy
     * @param retryPredicate Predicate to test whether an exception is retryable
     * @param scheduledExecutor executor to be used for the retry operation
     * @param <T> 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 <T> CompletableFuture<T> retryWithDelay(
            final Supplier<CompletableFuture<T>> operation,
            final RetryStrategy retryStrategy,
            final Predicate<Throwable> retryPredicate,
            final ScheduledExecutor scheduledExecutor) {

        final CompletableFuture<T> resultFuture = new CompletableFuture<>();

        retryOperationWithDelay(
                resultFuture, operation, retryStrategy, 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 <T> 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 <T> CompletableFuture<T> retryWithDelay(
            final Supplier<CompletableFuture<T>> operation,
            final int retries,
            final Time retryDelay,
            final ScheduledExecutor scheduledExecutor) {
        return retryWithDelay(
                operation,
                new FixedRetryStrategy(retries, Duration.ofMillis(retryDelay.toMilliseconds())),
                scheduledExecutor);
    }

    /**
     * Retry the given operation with the given delay in between failures.
     *
     * @param operation to retry
     * @param retryStrategy the RetryStrategy
     * @param scheduledExecutor executor to be used for the retry operation
     * @param <T> 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 <T> CompletableFuture<T> retryWithDelay(
            final Supplier<CompletableFuture<T>> operation,
            final RetryStrategy retryStrategy,
            final ScheduledExecutor scheduledExecutor) {
        return retryWithDelay(operation, retryStrategy, (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<Void> scheduleWithDelay(
            final Runnable operation, final Time delay, final ScheduledExecutor scheduledExecutor) {
        Supplier<Void> 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 <T> type of the result
     * @return Future which schedules the given operation with given delay.
     */
    public static <T> CompletableFuture<T> scheduleWithDelay(
            final Supplier<T> operation,
            final Time delay,
            final ScheduledExecutor scheduledExecutor) {
        final CompletableFuture<T> 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 <T> void retryOperationWithDelay(
            final CompletableFuture<T> resultFuture,
            final Supplier<CompletableFuture<T>> operation,
            final RetryStrategy retryStrategy,
            final Predicate<Throwable> retryPredicate,
            final ScheduledExecutor scheduledExecutor) {

        if (!resultFuture.isDone()) {
            final CompletableFuture<T> 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 (retryStrategy.getNumRemainingRetries() > 0) {
                                    long retryDelayMillis =
                                            retryStrategy.getRetryDelay().toMillis();
                                    final ScheduledFuture<?> scheduledFuture =
                                            scheduledExecutor.schedule(
                                                    (Runnable)
                                                            () ->
                                                                    retryOperationWithDelay(
                                                                            resultFuture,
                                                                            operation,
                                                                            retryStrategy
                                                                                    .getNextRetryStrategy(),
                                                                            retryPredicate,
                                                                            scheduledExecutor),
                                                    retryDelayMillis,
                                                    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 <T> 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 <T> CompletableFuture<T> retrySuccessfulWithDelay(
            final Supplier<CompletableFuture<T>> operation,
            final Time retryDelay,
            final Deadline deadline,
            final Predicate<T> acceptancePredicate,
            final ScheduledExecutor scheduledExecutor) {

        final CompletableFuture<T> resultFuture = new CompletableFuture<>();

        retrySuccessfulOperationWithDelay(
                resultFuture,
                operation,
                retryDelay,
                deadline,
                acceptancePredicate,
                scheduledExecutor);

        return resultFuture;
    }

    private static <T> void retrySuccessfulOperationWithDelay(
            final CompletableFuture<T> resultFuture,
            final Supplier<CompletableFuture<T>> operation,
            final Time retryDelay,
            final Deadline deadline,
            final Predicate<T> acceptancePredicate,
            final ScheduledExecutor scheduledExecutor) {

        if (!resultFuture.isDone()) {
            final CompletableFuture<T> 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 <T> type of the given future
     * @return The timeout enriched future
     */
    public static <T> CompletableFuture<T> orTimeout(
            CompletableFuture<T> future, long timeout, TimeUnit timeUnit) {
        return orTimeout(future, timeout, timeUnit, Executors.directExecutor(), null);
    }

    /**
     * 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 timeoutMsg timeout message for exception
     * @param <T> type of the given future
     * @return The timeout enriched future
     */
    public static <T> CompletableFuture<T> orTimeout(
            CompletableFuture<T> future,
            long timeout,
            TimeUnit timeUnit,
            @Nullable String timeoutMsg) {
        return orTimeout(future, timeout, timeUnit, Executors.directExecutor(), timeoutMsg);
    }

    /**
     * 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 <T> type of the given future
     * @return The timeout enriched future
     */
    public static <T> CompletableFuture<T> orTimeout(
            CompletableFuture<T> future,
            long timeout,
            TimeUnit timeUnit,
            Executor timeoutFailExecutor) {
        return orTimeout(future, timeout, timeUnit, timeoutFailExecutor, null);
    }

    /**
     * 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 timeoutMsg timeout message for exception
     * @param <T> type of the given future
     * @return The timeout enriched future
     */
    public static <T> CompletableFuture<T> orTimeout(
            CompletableFuture<T> future,
            long timeout,
            TimeUnit timeUnit,
            Executor timeoutFailExecutor,
            @Nullable String timeoutMsg) {

        if (!future.isDone()) {
            final ScheduledFuture<?> timeoutFuture =
                    Delayer.delay(
                            () -> timeoutFailExecutor.execute(new Timeout(future, timeoutMsg)),
                            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 <T> 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> T runIfNotDoneAndGet(RunnableFuture<T> 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<Void> 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<Void> 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<Void> runAfterwardsAsync(
            CompletableFuture<?> future, RunnableWithException runnable, Executor executor) {
        final CompletableFuture<Void> 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<Void> composeAfterwards(
            CompletableFuture<?> future, Supplier<CompletableFuture<?>> composedAction) {
        final CompletableFuture<Void> 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.
     *
     * <p>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 <T> ConjunctFuture<Collection<T>> 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.
     *
     * <p>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<Void> 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.
     *
     * <p>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<T> extends CompletableFuture<T> {

        /**
         * 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<T> extends ConjunctFuture<Collection<T>> {

        /** 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<Void> {

        /** 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<Void> 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<Void> {

        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 <T> type of the future
     * @return An exceptionally completed CompletableFuture
     */
    public static <T> CompletableFuture<T> completedExceptionally(Throwable cause) {
        CompletableFuture<T> 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 <T> type of the result
     * @return Future which is completed with the value of the supplier
     */
    public static <T> CompletableFuture<T> supplyAsync(
            SupplierWithException<T, ?> 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 <T> type of the future value
     * @param <U> type of the original future
     * @return Java 8 CompletableFuture
     */
    public static <T, U extends T> CompletableFuture<T> toJava(Future<U> scalaFuture) {
        final CompletableFuture<T> result = new CompletableFuture<>();

        scalaFuture.onComplete(
                new OnComplete<U>() {
                    @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 <IN> type of the input future.
     * @param <OUT> type of the output future.
     * @return a completable future that is applying the given function to the input future.
     */
    public static <IN, OUT> CompletableFuture<OUT> thenApplyAsyncIfNotDone(
            CompletableFuture<IN> 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 <IN> type of the input future.
     * @param <OUT> type of the output future.
     * @return a completable future that is a composition of the input future and the function.
     */
    public static <IN, OUT> CompletableFuture<OUT> thenComposeAsyncIfNotDone(
            CompletableFuture<IN> completableFuture,
            Executor executor,
            Function<? super IN, ? extends CompletionStage<OUT>> 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 <IN> type of the input future.
     * @return the new completion stage.
     */
    public static <IN> CompletableFuture<IN> whenCompleteAsyncIfNotDone(
            CompletableFuture<IN> 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 <IN> type of the input future.
     * @return the new completion stage.
     */
    public static <IN> CompletableFuture<Void> thenAcceptAsyncIfNotDone(
            CompletableFuture<IN> 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 <IN> type of the handler input argument.
     * @param <OUT> type of the handler return value.
     * @return the new completion stage.
     */
    public static <IN, OUT> CompletableFuture<OUT> handleAsyncIfNotDone(
            CompletableFuture<IN> completableFuture,
            Executor executor,
            BiFunction<? super IN, Throwable, ? extends OUT> handler) {
        return completableFuture.isDone()
                ? completableFuture.handle(handler)
                : completableFuture.handleAsync(handler, executor);
    }

    /** @return true if future has completed normally, false otherwise. */
    public static boolean isCompletedNormally(CompletableFuture<?> future) {
        return future.isDone() && !future.isCompletedExceptionally();
    }

    /**
     * Perform check state that future has completed normally and return the result.
     *
     * @return the result of completable future.
     * @throws IllegalStateException Thrown, if future has not completed or it has completed
     *     exceptionally.
     */
    public static <T> T checkStateAndGet(CompletableFuture<T> future) {
        checkCompletedNormally(future);
        return getWithoutException(future);
    }

    /**
     * Gets the result of a completable future without any exception thrown.
     *
     * @param future the completable future specified.
     * @param <T> the type of result
     * @return the result of completable future, or null if it's unfinished or finished
     *     exceptionally
     */
    @Nullable
    public static <T> T getWithoutException(CompletableFuture<T> future) {
        if (isCompletedNormally(future)) {
            try {
                return future.get();
            } catch (InterruptedException | ExecutionException ignored) {
            }
        }
        return null;
    }

    /**
     * @return the result of completable future, or the defaultValue if it has not yet completed.
     */
    public static <T> T getOrDefault(CompletableFuture<T> future, T defaultValue) {
        T value = getWithoutException(future);
        return value == null ? defaultValue : value;
    }

    /** Runnable to complete the given future with a {@link TimeoutException}. */
    private static final class Timeout implements Runnable {

        private final CompletableFuture<?> future;
        private final String timeoutMsg;

        private Timeout(CompletableFuture<?> future, @Nullable String timeoutMsg) {
            this.future = checkNotNull(future);
            this.timeoutMsg = timeoutMsg;
        }

        @Override
        public void run() {
            future.completeExceptionally(new TimeoutException(timeoutMsg));
        }
    }

    /**
     * Delay scheduler used to timeout futures.
     *
     * <p>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 <T> type of the value
     */
    public static <T> void forward(CompletableFuture<T> source, CompletableFuture<T> 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 <T> type of the value
     */
    public static <T> void forwardAsync(
            CompletableFuture<T> source, CompletableFuture<T> 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 <T> BiConsumer<T, Throwable> forwardTo(CompletableFuture<T> target) {
        return (value, throwable) -> {
            if (throwable != null) {
                target.completeExceptionally(throwable);
            } else {
                target.complete(value);
            }
        };
    }
}
相关推荐
IT小哥哥呀7 小时前
电池制造行业数字化实施
大数据·制造·智能制造·数字化·mom·电池·信息化
Xi xi xi7 小时前
苏州唯理科技近期也正式发布了国内首款神经腕带产品
大数据·人工智能·经验分享·科技
yumgpkpm8 小时前
华为鲲鹏 Aarch64 环境下多 Oracle 、mysql数据库汇聚到Cloudera CDP7.3操作指南
大数据·数据库·mysql·华为·oracle·kafka·cloudera
UMI赋能企业9 小时前
制造业流程自动化提升生产力的全面分析
大数据·人工智能
TDengine (老段)9 小时前
TDengine 数学函数 FLOOR 用户手册
大数据·数据库·物联网·时序数据库·iot·tdengine·涛思数据
派可数据BI可视化12 小时前
商业智能BI 浅谈数据孤岛和数据分析的发展
大数据·数据库·数据仓库·信息可视化·数据挖掘·数据分析
jiedaodezhuti12 小时前
Flink性能调优基石:资源配置与内存优化实践
大数据·flink
Lx35213 小时前
Flink窗口机制详解:如何处理无界数据流
大数据
Lx35213 小时前
深入理解Flink的流处理模型
大数据
Lx35213 小时前
Flink vs Spark Streaming:谁更适合你的实时处理需求?
大数据