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

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

/**
 * Copyright 2015-2020 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.SharedFunctions.cancelPromise;

import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
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.CompletionException;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;


/**
 * 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);
    }

    
    public static  Promise maybe(Optional maybeValue) {
        return maybeValue.map(Promises::success)
                         .orElseGet(() -> Promises.failure(new NoSuchElementException()));
    }
    
    /**
     * 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);
    }
    
    public static Throwable unwrapCompletionException(Throwable ex) {
        return SharedFunctions.unwrapCompletionException(ex);
    }
    
    public static  Promise loop(T initialValue, 
                                      Predicate loopCondition,
                                      Function> loopBody) {
        AsyncLoop asyncLoop = new AsyncLoop<>(loopCondition, loopBody);
        asyncLoop.run(initialValue);
        return asyncLoop;
    }

    
    public static  Promise tryApply(CompletionStage resourcePromise,
                                                                   Function fn) {
        return tryApply(from(resourcePromise), fn);
    }
    
    public static  Promise tryApply(Promise resourcePromise,
                                                                   Function fn) {
        return 
        resourcePromise.dependent()
                       .thenApply(r -> {
                            try (R resource = r) {
                                return (T)fn.apply(resource);
                            } catch (RuntimeException | Error rte) {
                                throw rte;
                            } catch (Throwable ex) {
                                throw new CompletionException(ex);
                            }
                        }, true)
                       .unwrap();
    }
    

    public static  Promise tryApplyEx(CompletionStage resourcePromise,
                                                                      Function fn) {
        return tryApplyEx(from(resourcePromise), fn);
    }
    
    public static  Promise tryApplyEx(Promise resourcePromise,
                                                                      Function fn) {
        return 
        resourcePromise.dependent()
                       .thenCompose(resource -> {
                           T result;
                           try {
                               result = fn.apply(resource);
                           } catch (Throwable actionException) {
                               try {
                                   // Use dependent here?
                                   return resource.close().thenCompose(__ -> failure(actionException));
                               } catch (Throwable onClose) {
                                   actionException.addSuppressed(onClose);
                                   return failure(onClose);
                               }
                           }
                           
                           try {
                               // Use dependent here?
                               return resource.close().thenApply(__ -> result);
                           } catch (Throwable onClose) {
                               return failure(onClose);
                           }

                       }, true)
                       .unwrap();
    }
    
    public static  Promise tryCompose(CompletionStage resourcePromise,
                                                                     Function> fn) {
        return tryCompose(from(resourcePromise), fn);
    }

    public static  Promise tryCompose(Promise resourcePromise,
                                                                     Function> fn) {
        return
        resourcePromise.dependent()
                       .thenCompose(resource -> {
                           CompletionStage action;
                           try {
                               action = fn.apply(resource);
                           } catch (Throwable composeException) {
                               try {
                                   resource.close();
                               } catch (Exception onClose) {
                                   composeException.addSuppressed(onClose);
                               }
                               return failure(composeException);
                           }

                           CompletablePromise result = new CompletablePromise<>();
                           action.whenComplete((actionResult, actionException) -> {
                               try {
                                   resource.close();
                               } catch (Throwable onClose) {
                                   if (null != actionException) {
                                       actionException.addSuppressed(onClose);
                                       result.onFailure(actionException);
                                   } else {
                                       result.onFailure(onClose);
                                   }
                                   // DONE WITH ERROR ON CLOSE
                                   return;
                               }
                               // CLOSE OK
                               if (null == actionException) {
                                   result.onSuccess(actionResult);
                               } else {
                                   result.onFailure(actionException);
                               }
                           });
                           return result.onCancel(() -> cancelPromise(action, true));
                       }, true)
                       .unwrap();        
    }
    
    
    public static  Promise tryComposeEx(Promise resourcePromise,
                                                                      Function> fn) {
        return
        resourcePromise.dependent()
                       .thenCompose(resource -> {
                           CompletionStage action;
                           try {
                               action = fn.apply(resource);
                           } catch (Throwable composeException) {
                               try {
                                   // Use dependent here?
                                   return resource.close().thenCompose(__ -> failure(composeException));
                               } catch (Throwable onClose) {
                                   composeException.addSuppressed(onClose);
                                   return failure(onClose);
                               }                               
                           }

                           CompletablePromise result = new CompletablePromise<>();
                           action.whenComplete((actionResult, actionException) -> {
                               CompletionStage afterClose;
                               try {
                                   afterClose = resource.close();
                               } catch (Throwable onClose) {
                                   if (null != actionException) {
                                       actionException.addSuppressed(onClose);
                                       result.onFailure(actionException);
                                   } else {
                                       result.onFailure(onClose);
                                   }
                                   // DONE WITH ERROR ON ASYNC CLOSE
                                   return;
                               }
                               // ASYNC CLOSE INVOKE OK
                               afterClose.whenComplete((__, onClose) -> {
                                   if (null != actionException) {
                                       if (null != onClose) {
                                           actionException.addSuppressed(onClose);
                                       }
                                       result.onFailure(actionException);
                                   } else if (null != onClose) {
                                       result.onFailure(onClose);
                                   } else {
                                       result.onSuccess(actionResult);
                                   }
                               });
                           });
                           return result.onCancel(() -> cancelPromise(action, true));
                       }, true)
                       .unwrap();
    }
    
    public static  Promise partitioned(Iterable values, 
                                                   int batchSize, 
                                                   Function> spawner, 
                                                   Collector downstream) {
        return partitioned1(values.iterator(), batchSize, spawner, downstream);
    }
    
    public static  Promise partitioned(Iterable values, 
                                                   int batchSize, 
                                                   Function> spawner, 
                                                   Collector downstream,
                                                   Executor downstreamExecutor) {
        return partitioned2(values.iterator(), batchSize, spawner, downstream, downstreamExecutor);
    }
    
    public static  Promise partitioned(Stream values, 
                                                   int batchSize, 
                                                   Function> spawner, 
                                                   Collector downstream) {
        return partitioned1(values.iterator(), batchSize, spawner, downstream);
    }
    
    public static  Promise partitioned(Stream values, 
                                                   int batchSize, 
                                                   Function> spawner, 
                                                   Collector downstream,
                                                   Executor downstreamExecutor) {
        return partitioned2(values.iterator(), batchSize, spawner, downstream, downstreamExecutor);
    }
    
    
    private static  Promise partitioned1(Iterator values, 
                                                    int batchSize, 
                                                    Function> spawner, 
                                                    Collector downstream) {
        return
            parallelStep1(values, batchSize, spawner, downstream)
            .dependent()
            .thenApply(downstream.finisher(), true)
            .as(onCloseSource(values))
            .unwrap();
    }
    

    private static  Promise partitioned2(Iterator values, 
                                                    int batchSize, 
                                                    Function> spawner, 
                                                    Collector downstream,
                                                    Executor downstreamExecutor) {
        return 
            parallelStep2(values, batchSize, spawner, downstream, downstreamExecutor)
            .dependent()
            .thenApplyAsync(downstream.finisher(), downstreamExecutor, true)
            .as(onCloseSource(values))
            .unwrap();
    }
    
    private static  Function, Promise> onCloseSource(Object source) {
        if (source instanceof AutoCloseable) {
            return p -> p.dependent().whenComplete((r, e) -> {
                try (AutoCloseable o = (AutoCloseable)source) {
                    
                } catch (RuntimeException | Error ex) {
                    if (null != e) {
                        e.addSuppressed(ex);
                    } else {
                        throw ex;
                    }
                } catch (Exception ex) {
                    if (null != e) {
                        e.addSuppressed(ex);
                    } else {
                        throw new CompletionException(ex);
                    }
                }
            }, true);
        } else {
            return Function.identity();
        }
    }
    
    private static  Promise parallelStep1(Iterator values, 
                                                     int batchSize,
                                                     Function> spawner,                                                        
                                                     Collector downstream) {

        int[] step = {0};
        return loop(null, __ -> step[0] == 0 || values.hasNext(), current -> {
            List valuesBatch = drainBatch(values, batchSize);
            if (valuesBatch.isEmpty()) {
                // Over
                return Promises.success(step[0] == 0 ? downstream.supplier().get() : current);
            } else {
                List> promisesBatch = 
                    valuesBatch.stream()
                               .map(spawner)
                               .collect(Collectors.toList());
               
                boolean initial = step[0] == 0;
                step[0]++;
                return 
                Promises.all(promisesBatch)
                        .dependent()
                        .thenApply(vals -> accumulate(vals, initial, current, downstream), true);
            }
        });
    }

    private static  Promise parallelStep2(Iterator values, 
                                                     int batchSize,
                                                     Function> spawner,                                                        
                                                     Collector downstream,
                                                     Executor downstreamExecutor) {

        int[] step = {0};
        return loop(null, __ -> step[0] == 0 || values.hasNext(), current -> {
            List valuesBatch = drainBatch(values, batchSize);
            if (valuesBatch.isEmpty()) {
                // Over
                Promise result;
                if (step[0] == 0) {
                    result = CompletableTask.supplyAsync(downstream.supplier(), downstreamExecutor);
                } else {
                    result = Promises.success(current);
                }
                return result;
            } else {
                List> promisesBatch = 
                        valuesBatch.stream()
                                   .map(spawner)
                                   .collect(Collectors.toList());
                boolean initial = step[0] == 0;
                step[0]++;
                return 
                Promises.all(promisesBatch)
                        .dependent()
                        .thenApplyAsync(vals -> accumulate(vals, initial, current, downstream), downstreamExecutor, true);                
            }
        });
    }
    
    private static  List drainBatch(Iterator values, int batchSize) {
        List valuesBatch = new ArrayList<>(batchSize);
        for (int count = 0; values.hasNext() && count < batchSize; count++) {
            valuesBatch.add(values.next());
        }        
        return valuesBatch;
    }
    
    private static  A accumulate(List vals, boolean initial, A current, Collector downstream) {
        A insertion = downstream.supplier().get();
        vals.stream()
            .forEach(v -> downstream.accumulator().accept(insertion, v));
        
        return initial ? insertion : downstream.combiner().apply(current, insertion);
    }

    
    /**
     * 

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); } public static Promise> all(Map> promises) { return all(true, promises); } public static Promise> all(boolean cancelRemaining, Map> promises) { Map result = new ConcurrentHashMap<>(); return all(cancelRemaining, collectKeyedResults(result, promises)) .dependent() .thenApply(__ -> Collections.unmodifiableMap(result), true) .unwrap(); } /** *

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 */ ); } } public static Promise> any(Map> promises) { return any(true, promises); } public static Promise> any(boolean cancelRemaining, Map> promises) { Map result = new ConcurrentHashMap<>(); return any(cancelRemaining, collectKeyedResults(result, promises)) .dependent() .thenApply(__ -> Collections.unmodifiableMap(result), true) .unwrap(); } /** *

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 ); } } public static Promise> anyStrict(Map> promises) { return anyStrict(true, promises); } public static Promise> anyStrict(boolean cancelRemaining, Map> promises) { Map result = new ConcurrentHashMap<>(); return anyStrict(cancelRemaining, collectKeyedResults(result, promises)) .dependent() .thenApply(__ -> Collections.unmodifiableMap(result), true) .unwrap(); } /** *

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); } public static Promise> atLeast(int minResultsCount, Map> promises) { return atLeast(minResultsCount, true, promises); } public static Promise> atLeast(int minResultsCount, boolean cancelRemaining, Map> promises) { Map result = new ConcurrentHashMap<>(); return atLeast(minResultsCount, cancelRemaining, collectKeyedResults(result, promises)) .dependent() .thenApply(__ -> Collections.unmodifiableMap(result), true) .unwrap(); } /** *

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); } public static Promise> atLeastStrict(int minResultsCount, Map> promises) { return atLeastStrict(minResultsCount, true, promises); } public static Promise> atLeastStrict(int minResultsCount, boolean cancelRemaining, Map> promises) { Map result = new ConcurrentHashMap<>(); return atLeastStrict(minResultsCount, cancelRemaining, collectKeyedResults(result, promises)) .dependent() .thenApply(__ -> Collections.unmodifiableMap(result), true) .unwrap(); } /** *

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> atLeast(int minResultsCount, int maxErrorsCount, boolean cancelRemaining, Map> promises) { Map result = new ConcurrentHashMap<>(); return atLeast(minResultsCount, maxErrorsCount, cancelRemaining, collectKeyedResults(result, promises)) .dependent() .thenApply(__ -> Collections.unmodifiableMap(result), true) .unwrap(); } 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 retryImpl((RetryContext ctx) -> { try { return CompletableTask.submit(() -> codeBlock.call(ctx), executor); } catch (Throwable ex) { return failure(ex); } }, RetryContext.initial(retryPolicy), true); } 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 retryImpl((RetryContext ctx) -> { try { // Not sure how many time futureFactory.call will take return from(futureFactory.call(ctx)); } catch (Throwable ex) { return failure(ex); } }, RetryContext.initial(retryPolicy), true); } private static Promise retryImpl(Function, ? extends Promise> futureFactory, RetryContext initialCtx, boolean usePrevAsync) { @SuppressWarnings("unchecked") RetryContext[] ctxRef = new RetryContext[] {initialCtx}; DependentPromise[] prevRef = new DependentPromise[1]; return loop(null, v -> null == v || !v.isSuccess() , (Try v) -> { RetryContext ctx = ctxRef[0]; RetryPolicy.Verdict verdict = ctx.shouldContinue(); if (!verdict.shouldExecute()) { return failure(ctx.asFailure()); } Supplier>> callSupplier = () -> { long startTime = System.nanoTime(); Promise target = futureFactory.apply(ctx); DependentPromise withTimeout = target.dependent(); Duration timeout = verdict.timeout(); if (DelayPolicy.isValid(timeout)) { withTimeout = withTimeout.orTimeout(timeout, true, true); } DependentPromise> result = withTimeout.handle((value, ex) -> { if (null == ex && ctx.isValidResult(value)) { return Try.success(value); } else { long finishTime = System.nanoTime(); ctxRef[0] = ctx.nextRetry( duration(startTime, finishTime), unwrapCompletionException(ex) ); return Try.failure(ex != null ? ex : new IllegalAccessException("Result not accepted by policy")); } }, true); if (usePrevAsync) { prevRef[0] = result; } return result; }; Duration backoffDelay = verdict.backoffDelay(); if (DelayPolicy.isValid(backoffDelay)) { DependentPromise delay = prevRef[0] == null ? Timeouts.delay(backoffDelay).dependent() : prevRef[0].delay(backoffDelay, true, false); return delay.thenCompose(__ -> callSupplier.get(), true) .exceptionally(ex -> { // May be thrown when backoff delay is interrupted (canceled) ctxRef[0] = ctxRef[0].nextRetry(duration(0, 0), ex); return Try.failure(ex); }, true); } else { return callSupplier.get(); } }) .dependent() .thenApply(Try::done, true); // Don't unwrap - internal use only } private static Promise transform(CompletionStage original, Function resultMapper, Function errorMapper) { CompletablePromise result = new CompletablePromise<>(); original.whenComplete((r, e) -> { if (null == e) { result.onSuccess( resultMapper.apply(r) ); } else { result.onFailure( errorMapper.apply(e) ); } }); return result.onCancel(() -> cancelPromise(original, true)); } 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 List> collectKeyedResults(Map result, Map> promises) { return promises.entrySet() .stream() .map(e -> { CompletionStage promise = e.getValue(); return from(promise).dependent() .thenApply(value -> { result.put(e.getKey(), value); return value; }, true); }) .collect(Collectors.toList()) ; } private static RetryCallable toRetryCallable(Callable callable) { return ctx -> callable.call(); } private static Duration duration(long startTime, long finishTime) { return Duration.ofNanos(finishTime - startTime); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy