net.tascalate.concurrent.Promises Maven / Gradle / Ivy
Show all versions of net.tascalate.concurrent Show documentation
/**
* 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 extends T>... promises) {
return all(Arrays.asList(promises));
}
public static Promise> all(List extends CompletionStage extends T>> 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 extends T>... promises) {
return all(cancelRemaining, Arrays.asList(promises));
}
public static Promise> all(boolean cancelRemaining, List extends CompletionStage extends T>> 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 extends T>... promises) {
return any(Arrays.asList(promises));
}
public static Promise any(List extends CompletionStage extends T>> 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 extends T>... promises) {
return any(cancelRemaining, Arrays.asList(promises));
}
public static Promise any(boolean cancelRemaining, List extends CompletionStage extends T>> 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 extends T>... promises) {
return anyStrict(Arrays.asList(promises));
}
public static Promise anyStrict(List extends CompletionStage extends T>> 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 extends T>... promises) {
return anyStrict(cancelRemaining, Arrays.asList(promises));
}
public static Promise anyStrict(boolean cancelRemaining, List extends CompletionStage extends T>> 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 extends T>... promises) {
return atLeast(minResultsCount, Arrays.asList(promises));
}
public static Promise> atLeast(int minResultsCount, List extends CompletionStage extends T>> 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 extends T>... promises) {
return atLeast(minResultsCount, cancelRemaining, Arrays.asList(promises));
}
public static Promise> atLeast(int minResultsCount, boolean cancelRemaining,
List extends CompletionStage extends T>> 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 extends T>... promises) {
return atLeastStrict(minResultsCount, Arrays.asList(promises));
}
public static Promise> atLeastStrict(int minResultsCount,
List extends CompletionStage extends T>> 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 extends T>... promises) {
return atLeast(minResultsCount, cancelRemaining, Arrays.asList(promises));
}
public static Promise> atLeastStrict(int minResultsCount, boolean cancelRemaining,
List extends CompletionStage extends T>> 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 extends T>... promises) {
return atLeast(minResultsCount, maxErrorsCount, cancelRemaining, Arrays.asList(promises));
}
public static Promise> atLeast(int minResultsCount, int maxErrorsCount, boolean cancelRemaining,
List extends CompletionStage extends T>> 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 extends T> 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 super Void> retryPolicy) {
return retry(ctx -> { codeBlock.run(); }, executor, retryPolicy);
}
public static Promise retry(RetryRunnable codeBlock, Executor executor,
RetryPolicy super Void> retryPolicy) {
return retry(ctx -> { codeBlock.run(ctx); return null; }, executor, retryPolicy.acceptNullResult());
}
public static Promise retry(Callable codeBlock, Executor executor,
RetryPolicy super T> retryPolicy) {
return retry(toRetryCallable(codeBlock), executor, retryPolicy);
}
public static Promise retry(RetryCallable codeBlock, Executor executor,
RetryPolicy super C> 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 super T> retryPolicy) {
return retryOptional(toRetryCallable(codeBlock), executor, retryPolicy);
}
public static Promise retryOptional(RetryCallable, C> codeBlock, Executor executor,
RetryPolicy super C> retryPolicy) {
// Need explicit type on lambda param
return retry((RetryContext ctx) -> codeBlock.call(ctx).orElse(null), executor, retryPolicy);
}
public static Promise retryFuture(Callable extends CompletionStage> invoker,
RetryPolicy super T> retryPolicy) {
return retryFuture(toRetryCallable(invoker), retryPolicy);
}
public static Promise retryFuture(RetryCallable extends CompletionStage, C> futureFactory,
RetryPolicy super C> 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 super C> 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 extends CompletionStage, 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 extends T> 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 extends T> 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 super U, ? extends T> resultMapper,
Function super Throwable, ? extends Throwable> 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 extends T> 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 extends V> 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);
}
}