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

net.tascalate.concurrent.Promises Maven / Gradle / Ivy

Go to download

Implementation of blocking (IO-Bound) cancellable java.util.concurrent.CompletionStage and related extensions to java.util.concurrent.ExecutorService-s

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

import static net.tascalate.concurrent.LinkedCompletion.StageCompletion;
import static net.tascalate.concurrent.SharedFunctions.cancelPromise;
import static net.tascalate.concurrent.SharedFunctions.selectFirst;
import static net.tascalate.concurrent.SharedFunctions.unwrapCompletionException;

import java.time.Duration;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;


/**
 * Utility class to create a resolved (either successfully or faulty) {@link Promise}-s; 
 * to wrap an arbitrary {@link CompletionStage} interface to the {@link Promise} API;
 * to combine several {@link CompletionStage}-s into aggregating promise.
 *    
 * @author vsilaev
 *
 */
public final class Promises {

    private Promises() {}
    
    /**
     * Method to create a successfully resolved {@link Promise} with a value provided 
     * @param 
     *   a type of the value
     * @param value
     *   a value to wrap
     * @return 
     *   a successfully resolved {@link Promise} with a value provided
     */
    public static  Promise success(T value) {
        return new CompletableFutureWrapper<>(
            CompletableFuture.completedFuture(value)
        );
    }
    
    /**
     * Method to create a faulty resolved {@link Promise} with an exception provided 
     * @param 
     *   a type of the value 
     * @param exception
     *   an exception to wrap
     * @return 
     *   a faulty resolved {@link Promise} with an exception provided
     */    
    public static  Promise failure(Throwable exception) {
        CompletableFuture delegate = new CompletableFuture<>();
        delegate.completeExceptionally(exception);
        return new CompletableFutureWrapper<>(delegate);
    }

    /**
     * Adapts a stage passed to the {@link Promise} API
     * @param 
     *   a type of the value
     * @param stage
     *   a {@link CompletionStage} to be wrapped
     * @return
     *   a {@link Promise}
     */
    public static  Promise from(CompletionStage stage) {
        if (stage instanceof Promise) {
            return (Promise) stage;
        }

        if (stage instanceof CompletableFuture) {
            return new CompletableFutureWrapper<>((CompletableFuture)stage);
        }
        
        /*
        return transform(stage, Function.identity(), Function.identity());
         */
        return CompletionStageWrapper.from(stage);
    }
    
    public static  CompletionStage withDefaultExecutor(CompletionStage stage, Executor executor) {
        return new ExecutorBoundCompletionStage<>(stage, executor);
    }
    
    /**
     * 

Returns a promise that is resolved successfully when all {@link CompletionStage}-s passed as parameters * are completed normally; if any promise completed exceptionally, then resulting promise is resolved faulty * as well. *

The resolved result of this promise contains a list of the resolved results of the * {@link CompletionStage}-s passed as an argument at corresponding positions. *

When resulting promise is resolved faulty, all remaining incomplete {@link CompletionStage}-s are * cancelled. * @param * a common supertype of the resulting values * @param promises * an array of {@link CompletionStage}-s to combine * @return * a combined promise */ @SafeVarargs public static Promise> all(CompletionStage... promises) { return all(Arrays.asList(promises)); } public static Promise> all(List> promises) { return all(true, promises); } /** *

Returns a promise that is resolved successfully when all {@link CompletionStage}-s passed as parameters * are completed normally; if any promise completed exceptionally, then resulting promise is resolved faulty * as well. *

The resolved result of this promise contains a list of the resolved results of the * {@link CompletionStage}-s passed as an argument at corresponding positions. *

When resulting promise is resolved faulty and cancelRemaining parameter is * true, all remaining incomplete {@link CompletionStage}-s are cancelled. * @param * a common supertype of the resulting values * @param cancelRemaining * when true and resulting promise is resolved faulty all incomplete {@link CompletionStage}-s are cancelled * @param promises * an array of {@link CompletionStage}-s to combine * @return * a combined promise */ @SafeVarargs public static Promise> all(boolean cancelRemaining, CompletionStage... promises) { return all(cancelRemaining, Arrays.asList(promises)); } public static Promise> all(boolean cancelRemaining, List> promises) { return atLeast(null != promises ? promises.size() : 0, 0, cancelRemaining, promises); } /** *

Returns a promise that is resolved successfully when any {@link CompletionStage} passed as parameters * is completed normally (race is possible); if all promises completed exceptionally, then resulting promise * is resolved faulty as well. *

The resolved result of this promise contains a value of the first resolved result of the * {@link CompletionStage}-s passed as an argument. *

When resulting promise is resolved successfully, all remaining incomplete {@link CompletionStage}-s * are cancelled. * * @param * a common supertype of the resulting values * @param promises * an array of {@link CompletionStage}-s to combine * @return * a combined promise */ @SafeVarargs public static Promise any(CompletionStage... promises) { return any(Arrays.asList(promises)); } public static Promise any(List> promises) { return any(true, promises); } /** *

Returns a promise that is resolved successfully when any {@link CompletionStage} passed as parameters * is completed normally (race is possible); if all promises completed exceptionally, then resulting promise * is resolved faulty as well. *

The resolved result of this promise contains a value of the first resolved result of the * {@link CompletionStage}-s passed as an argument. *

When resulting promise is resolved successfully and cancelRemaining parameter is * true, all remaining incomplete {@link CompletionStage}-s are cancelled. * * @param * a common supertype of the resulting values * @param cancelRemaining * when true and resulting promise is resolved faulty all incomplete {@link CompletionStage}-s are cancelled * @param promises * an array of {@link CompletionStage}-s to combine * @return * a combined promise */ @SafeVarargs public static Promise any(boolean cancelRemaining, CompletionStage... promises) { return any(cancelRemaining, Arrays.asList(promises)); } public static Promise any(boolean cancelRemaining, List> promises) { int size = null == promises ? 0 : promises.size(); switch (size) { case 0: return insufficientNumberOfArguments(1, 0); case 1: @SuppressWarnings("unchecked") CompletionStage singleResult = (CompletionStage) promises.get(0); return transform(singleResult, Function.identity(), Promises::wrapMultitargetException); default: return transform( atLeast(1, size - 1, cancelRemaining, promises), Promises::extractFirstNonNull, Function.identity() /* DO NOT unwrap multitarget exception */ ); } } /** *

Returns a promise that is resolved successfully when any {@link CompletionStage} passed as parameters * is completed normally (race is possible); if any promise completed exceptionally before first result is * available, then resulting promise is resolved faulty as well (unlike non-Strict variant, where exceptions * are ignored if result is available at all). *

The resolved result of this promise contains a value of the first resolved result of the * {@link CompletionStage}-s passed as an argument. *

When resulting promise is resolved either successfully or faulty, all remaining incomplete * {@link CompletionStage}-s are cancelled. *

Unlike other methods to combine promises (any, all, atLeast, atLeastStrict), the {@link Promise} returns * from this method reports exact exception. All other methods wrap it to {@link MultitargetException}. * @param * a common supertype of the resulting values * @param promises * an array of {@link CompletionStage}-s to combine * @return * a combined promise */ @SafeVarargs public static Promise anyStrict(CompletionStage... promises) { return anyStrict(Arrays.asList(promises)); } public static Promise anyStrict(List> promises) { return anyStrict(true, promises); } /** *

Returns a promise that is resolved successfully when any {@link CompletionStage} passed as parameters * is completed normally (race is possible); if any promise completed exceptionally before first result is * available, then resulting promise is resolved faulty as well (unlike non-Strict variant, where exceptions * are ignored if result is available at all). *

The resolved result of this promise contains a value of the first resolved result of the * {@link CompletionStage}-s passed as an argument. *

When resulting promise is resolved either successfully or faulty and cancelRemaining * parameter is true, all remaining incomplete {@link CompletionStage}-s are cancelled. *

Unlike other methods to combine promises (any, all, atLeast, atLeastStrict), the {@link Promise} returns * from this method reports exact exception. All other methods wrap it to {@link MultitargetException}. * @param * a common supertype of the resulting values * @param cancelRemaining * when true and resulting promise is resolved faulty all incomplete {@link CompletionStage}-s are cancelled * @param promises * an array of {@link CompletionStage}-s to combine * @return * a combined promise */ @SafeVarargs public static Promise anyStrict(boolean cancelRemaining, CompletionStage... promises) { return anyStrict(cancelRemaining, Arrays.asList(promises)); } public static Promise anyStrict(boolean cancelRemaining, List> promises) { int size = null == promises ? 0 : promises.size(); switch (size) { case 0: return insufficientNumberOfArguments(1, 0); case 1: @SuppressWarnings("unchecked") CompletionStage singleResult = (CompletionStage) promises.get(0); return from(singleResult); default: return transform( atLeast(1, 0, cancelRemaining, promises), Promises::extractFirstNonNull, Promises::unwrapMultitargetException ); } } /** *

Generalization of the {@link Promises#any(CompletionStage...)} method.

*

Returns a promise that is resolved successfully when at least minResultCount of * {@link CompletionStage}-s passed as parameters are completed normally (race is possible); if less than * minResultCount of promises completed normally, then resulting promise is resolved faulty. *

The resolved result of this promise contains a list of the resolved results of the * {@link CompletionStage}-s passed as an argument at corresponding positions. Non-completed or completed * exceptionally promises have null values. *

When resulting promise is resolved successfully, all remaining incomplete {@link CompletionStage}-s * are cancelled. * * @param * a common supertype of the resulting values * @param minResultsCount * a minimum number of promises that should be completed normally to resolve resulting promise successfully * @param promises * an array of {@link CompletionStage}-s to combine * @return * a combined promise */ @SafeVarargs public static Promise> atLeast(int minResultsCount, CompletionStage... promises) { return atLeast(minResultsCount, Arrays.asList(promises)); } public static Promise> atLeast(int minResultsCount, List> promises) { return atLeast(minResultsCount, true, promises); } /** *

Generalization of the {@link Promises#any(CompletionStage...)} method.

*

Returns a promise that is resolved successfully when at least minResultCount of * {@link CompletionStage}-s passed as parameters are completed normally (race is possible); if less than * minResultCount of promises completed normally, then resulting promise is resolved faulty. *

The resolved result of this promise contains a list of the resolved results of the * {@link CompletionStage}-s passed as an argument at corresponding positions. Non-completed or completed * exceptionally promises have null values. *

When resulting promise is resolved successfully and cancelRemaining parameter * is true, all remaining incomplete {@link CompletionStage}-s are cancelled. * * @param * a common supertype of the resulting values * @param minResultsCount * a minimum number of promises that should be completed normally to resolve resulting promise successfully * @param cancelRemaining * when true and resulting promise is resolved faulty all incomplete {@link CompletionStage}-s are cancelled * @param promises * an array of {@link CompletionStage}-s to combine * @return * a combined promise */ @SafeVarargs public static Promise> atLeast(int minResultsCount, boolean cancelRemaining, CompletionStage... promises) { return atLeast(minResultsCount, cancelRemaining, Arrays.asList(promises)); } public static Promise> atLeast(int minResultsCount, boolean cancelRemaining, List> promises) { return atLeast(minResultsCount, (promises == null ? 0 : promises.size()) - minResultsCount, cancelRemaining, promises); } /** *

Generalization of the {@link Promises#anyStrict(CompletionStage...)} method.

*

Returns a promise that is resolved successfully when at least minResultCount of * {@link CompletionStage}-s passed as parameters are completed normally (race is possible); if less than * minResultCount of promises completed normally, then resulting promise is resolved faulty. * If any promise completed exceptionally before minResultCount of results are * available, then resulting promise is resolved faulty as well. *

The resolved result of this promise contains a list of the resolved results of the * {@link CompletionStage}-s passed as an argument at corresponding positions. Non-completed promises * have null values. *

When resulting promise is resolved either successfully or faulty, all remaining incomplete * {@link CompletionStage}-s are cancelled. * * @param * a common supertype of the resulting values * @param minResultsCount * a minimum number of promises that should be completed normally to resolve resulting promise successfully * @param promises * an array of {@link CompletionStage}-s to combine * @return * a combined promise */ @SafeVarargs public static Promise> atLeastStrict(int minResultsCount, CompletionStage... promises) { return atLeastStrict(minResultsCount, Arrays.asList(promises)); } public static Promise> atLeastStrict(int minResultsCount, List> promises) { return atLeastStrict(minResultsCount, true, promises); } /** *

Generalization of the {@link Promises#anyStrict(CompletionStage...)} method.

*

Returns a promise that is resolved successfully when at least minResultCount of * {@link CompletionStage}-s passed as parameters are completed normally (race is possible); if less than * minResultCount of promises completed normally, then resulting promise is resolved faulty. * If any promise completed exceptionally before minResultCount of results are available, * then resulting promise is resolved faulty as well. *

The resolved result of this promise contains a list of the resolved results of the * {@link CompletionStage}-s passed as an argument at corresponding positions. Non-completed promises have * null values. *

When resulting promise is resolved either successfully or faulty and cancelRemaining * parameter is true, all remaining incomplete {@link CompletionStage}-s are cancelled. * * @param * a common supertype of the resulting values * @param minResultsCount * a minimum number of promises that should be completed normally to resolve resulting promise successfully * @param cancelRemaining * when true and resulting promise is resolved faulty all incomplete {@link CompletionStage}-s are cancelled * @param promises * an array of {@link CompletionStage}-s to combine * @return * a combined promise */ @SafeVarargs public static Promise> atLeastStrict(int minResultsCount, boolean cancelRemaining, CompletionStage... promises) { return atLeast(minResultsCount, cancelRemaining, Arrays.asList(promises)); } public static Promise> atLeastStrict(int minResultsCount, boolean cancelRemaining, List> promises) { return atLeast(minResultsCount, 0, cancelRemaining, promises); } /** *

General method to combine several {@link CompletionStage}-s passed as arguments into single promise.

*

The resulting promise is resolved successfully when at least minResultCount of * {@link CompletionStage}-s passed as parameters are completed normally (race is possible). *

If less than minResultCount of promises completed normally, then resulting promise is * resolved faulty. *

If maxErrorsCount of promises completed exceptionally before minResultCount * of results are available, then resulting promise is resolved faulty as well. *

The resolved result of this promise contains a list of the resolved results of the {@link CompletionStage}-s * passed as an argument at corresponding positions. Non-completed promises and promises completed exceptionally * have null values. *

When resulting promise is resolved either successfully or faulty, all remaining incomplete * {@link CompletionStage}-s are cancelled if cancelRemaining parameter is true. * * @param * a common supertype of the resulting values * @param minResultsCount * a minimum number of promises that should be completed normally to resolve resulting promise successfully * @param maxErrorsCount * a maximum number of promises that may be completed exceptionally before resolving resulting promise faulty * @param cancelRemaining * a flag that indicates (if true) whether or not all remaining incomplete {@link CompletionStage}-s should * be cancelled once a resulting promise outcome is known. * @param promises * an array of {@link CompletionStage}-s to combine * @return * a combined promise */ @SafeVarargs public static Promise> atLeast(int minResultsCount, int maxErrorsCount, boolean cancelRemaining, CompletionStage... promises) { return atLeast(minResultsCount, maxErrorsCount, cancelRemaining, Arrays.asList(promises)); } public static Promise> atLeast(int minResultsCount, int maxErrorsCount, boolean cancelRemaining, List> promises) { int size = null == promises ? 0 : promises.size(); if (minResultsCount > size) { Promise> result = insufficientNumberOfArguments(minResultsCount, size); if (cancelRemaining && size > 0) { promises.stream().forEach( p -> cancelPromise(p, true) ); } return result; } else if (minResultsCount == 0) { return success(Collections.emptyList()); } else if (size == 1) { CompletionStage stage = promises.get(0); return transform(stage, Collections::singletonList, Promises::wrapMultitargetException); } else { return new AggregatingPromise<>(minResultsCount, maxErrorsCount, cancelRemaining, promises); } } public static Promise retry(Runnable codeBlock, Executor executor, RetryPolicy retryPolicy) { return retry(ctx -> { codeBlock.run(); }, executor, retryPolicy); } public static Promise retry(RetryRunnable codeBlock, Executor executor, RetryPolicy retryPolicy) { return retry(ctx -> { codeBlock.run(ctx); return null; }, executor, retryPolicy.acceptNullResult()); } public static Promise retry(Callable codeBlock, Executor executor, RetryPolicy retryPolicy) { return retry(toRetryCallable(codeBlock), executor, retryPolicy); } public static Promise retry(RetryCallable codeBlock, Executor executor, RetryPolicy retryPolicy) { return startRetry(retryPolicy, new RetryInitiator() { @Override public void run(RetryContext ctx, CompletableFuture result, Consumer> cancellation) { tryValueOnce(codeBlock, executor, ctx, result, cancellation); } }).defaultAsyncOn(executor); } public static Promise retryOptional(Callable> codeBlock, Executor executor, RetryPolicy retryPolicy) { return retryOptional(toRetryCallable(codeBlock), executor, retryPolicy); } public static Promise retryOptional(RetryCallable, C> codeBlock, Executor executor, RetryPolicy retryPolicy) { // Need explicit type on lambda param return retry((RetryContext ctx) -> codeBlock.call(ctx).orElse(null), executor, retryPolicy); } public static Promise retryFuture(Callable> invoker, RetryPolicy retryPolicy) { return retryFuture(toRetryCallable(invoker), retryPolicy); } public static Promise retryFuture(RetryCallable, C> futureFactory, RetryPolicy retryPolicy) { return startRetry(retryPolicy, new RetryInitiator() { @Override public void run(RetryContext ctx, CompletableFuture result, Consumer> cancellation) { tryFutureOnce(futureFactory, ctx, result, cancellation, null); } }); } private static Promise startRetry(RetryPolicy retryPolicy, RetryInitiator initiator) { final CompletableFuture result = new CompletableFuture<>(); final AtomicReference> callPromiseRef = new AtomicReference<>(); // Cleanup latest timeout on completion; result.whenComplete( (r, e) -> Optional .of(callPromiseRef) .map(AtomicReference::get) .ifPresent( p -> p.cancel(true) ) ); Consumer> cancellation = p -> { // If result promise is cancelled after callPromise was set need to stop; callPromiseRef.set( p ); if (result.isDone()) { p.cancel(true); } }; RetryContext ctx = RetryContext.initial(retryPolicy); initiator.run(ctx, result, cancellation); return new CompletableFutureWrapper<>(result); } private static void tryValueOnce(RetryCallable codeBlock, Executor executor, RetryContext ctx, CompletableFuture result, Consumer> cancellation) { // Promise may be cancelled outside of polling if (result.isDone()) { return; } RetryPolicy.Verdict verdict = ctx.shouldContinue(); if (verdict.shouldExecute()) { Supplier> callSupplier = () -> { long startTime = System.nanoTime(); Runnable call = () -> { try { T value = codeBlock.call(ctx); if (ctx.isValidResult(value)) { result.complete(value); } else { long finishTime = System.nanoTime(); RetryContext nextCtx = ctx.nextRetry(duration(startTime, finishTime), value); tryValueOnce(codeBlock, executor, nextCtx, result, cancellation); } } catch (Exception ex) { long finishTime = System.nanoTime(); RetryContext nextCtx = ctx.nextRetry(duration(startTime, finishTime), ex); tryValueOnce(codeBlock, executor, nextCtx, result, cancellation); } }; // Call should be done via CompletableTask to let it be interruptible Promise p = CompletableTask.runAsync(call, executor); return applyExecutionTimeout(p, verdict); }; Duration backoffDelay = verdict.backoffDelay(); if (DelayPolicy.isValid(backoffDelay)) { // Invocation after timeout, change cancellation target Promise later = Timeouts .delay(backoffDelay) .dependent() .thenRun(() -> cancellation.accept( callSupplier.get() ), true); cancellation.accept( later ); } else { // Immediately send to executor cancellation.accept( callSupplier.get() ); } } else { result.completeExceptionally(ctx.asFailure()); } } private static void tryFutureOnce(RetryCallable, C> futureFactory, RetryContext ctx, CompletableFuture result, Consumer> cancellation, Promise prev) { // Promise may be cancelled outside of polling if (result.isDone()) { return; } RetryPolicy.Verdict verdict = ctx.shouldContinue(); if (verdict.shouldExecute()) { Supplier> callSupplier = () -> { long startTime = System.nanoTime(); Promise target; try { target = Promises.from(futureFactory.call(ctx)); } catch (Exception ex) { target = Promises.failure(ex); } AtomicBoolean isRecursive = new AtomicBoolean(true); Thread invokerThread = Thread.currentThread(); Promise p = target; p.whenComplete((value, ex) -> { if (null == ex && ctx.isValidResult(value)) { result.complete(value); } else { long finishTime = System.nanoTime(); RetryContext nextCtx = ctx.nextRetry( duration(startTime, finishTime), unwrapCompletionException(ex) ); boolean callLater = isRecursive.get() && Thread.currentThread() == invokerThread; if (callLater) { // Call after minimal possible delay callLater( p, Duration.ofNanos(1), cancellation, () -> tryFutureOnce(futureFactory, nextCtx, result, cancellation, p) ); } else { tryFutureOnce(futureFactory, nextCtx, result, cancellation, p); } } }); isRecursive.set(false); return applyExecutionTimeout(p, verdict); }; Duration backoffDelay = verdict.backoffDelay(); if (null != prev && DelayPolicy.isValid(backoffDelay)) { callLater(prev, backoffDelay, cancellation, () -> cancellation.accept( callSupplier.get() )); } else { // Immediately send to executor cancellation.accept( callSupplier.get() ); } } else { result.completeExceptionally(ctx.asFailure()); } } private static void callLater(Promise completedPromise, Duration delay, Consumer> cancellation, Runnable code) { Promise later = completedPromise .dependent() .thenCombine(Timeouts.delay(delay), selectFirst(), PromiseOrigin.PARAM_ONLY) .whenCompleteAsync((r, e) -> code.run(), true); cancellation.accept(later); } private static Promise applyExecutionTimeout(Promise singleInvocationPromise, RetryPolicy.Verdict verdict) { Duration timeout = verdict.timeout(); if (DelayPolicy.isValid(timeout)) { singleInvocationPromise.orTimeout( timeout ); } return singleInvocationPromise; } private static Promise transform(CompletionStage original, Function resultMapper, Function errorMapper) { StageCompletion result = new StageCompletion().dependsOn(original); original.whenComplete((r, e) -> { if (null == e) { result.complete( resultMapper.apply(r) ); } else { result.completeExceptionally( errorMapper.apply(e) ); } }); return result.toPromise(); } private static T extractFirstNonNull(Collection collection) { return collection.stream().filter(Objects::nonNull).findFirst().get(); } private static Throwable unwrapMultitargetException(E exception) { Throwable targetException = unwrapCompletionException(exception); if (targetException instanceof MultitargetException) { return ((MultitargetException)targetException).getFirstException().get(); } else { return targetException; } } private static MultitargetException wrapMultitargetException(E exception) { if (exception instanceof MultitargetException) { return (MultitargetException)exception; } else { return MultitargetException.of(exception); } } private static Promise insufficientNumberOfArguments(int minResultCount, int size) { String message = String.format( "The number of futures supplied (%d) is less than a number of futures to await (%d)", size, minResultCount ); Exception ex = new NoSuchElementException(message); //TODO: exceptional completion vs runtime exception on combined promise construction? ex.fillInStackTrace(); return failure(ex); /* throw new IllegalArgumentException(message); */ } private static RetryCallable toRetryCallable(Callable callable) { return ctx -> callable.call(); } private static Duration duration(long startTime, long finishTime) { return Duration.ofNanos(finishTime - startTime); } private static abstract class RetryInitiator { abstract void run(RetryContext ctx, CompletableFuture result, Consumer> cancellation); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy