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

com.outbrain.ob1k.concurrent.ComposableFutures Maven / Gradle / Ivy

package com.outbrain.ob1k.concurrent;

import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
import com.outbrain.ob1k.concurrent.combiners.*;
import com.outbrain.ob1k.concurrent.config.Configuration;
import com.outbrain.ob1k.concurrent.eager.ComposablePromise;
import com.outbrain.ob1k.concurrent.eager.EagerComposableFuture;
import com.outbrain.ob1k.concurrent.handlers.*;
import com.outbrain.ob1k.concurrent.lazy.LazyComposableFuture;
import com.outbrain.ob1k.concurrent.stream.FutureProviderToStreamHandler;
import rx.Observable;
import rx.Subscriber;
import rx.subjects.ReplaySubject;

import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * User: aronen
 * Date: 6/6/13
 * Time: 7:07 PM
 */
public class ComposableFutures {
    private ComposableFutures() {
    }

    private static class ExecutorServiceHolder {
        private static final ExecutorService INSTANCE =
            createExecutor(Configuration.getExecutorCoreSize(), Configuration.getExecutorMaxSize());

        private static ExecutorService createExecutor(final int coreSize, final int maxSize) {
            final ThreadPoolExecutor threadPool = new ThreadPoolExecutor(coreSize, maxSize,
                0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(),
              new PrefixBasedThreadFactory("ob1k-main"));
            threadPool.allowCoreThreadTimeOut(false);

            return threadPool;
        }
    }

    private static class SchedulerServiceHolder {
        private static final Scheduler INSTANCE =
            new ThreadPoolBasedScheduler(Configuration.getSchedulerCoreSize(),
              new PrefixBasedThreadFactory("ob1k-scheduler-service").withDaemonThreads());

    }

    public static  ComposableFuture recursive(final Supplier> creator, final Predicate stopCriteria) {
        return creator.get().continueOnSuccess(new FutureSuccessHandler() {
            @Override
            public ComposableFuture handle(final T result) {
                if (stopCriteria.apply(result)) {
                    return ComposableFutures.fromValue(result);
                }
                return recursive(creator, stopCriteria);
            }
        });
    }

    public static  ComposableFuture> all(final ComposableFuture f1, final ComposableFuture f2) {
        return all(false, Arrays.asList(f1, f2));
    }

    public static  ComposableFuture> all(final ComposableFuture f1, final ComposableFuture f2, final ComposableFuture f3) {
        return all(false, Arrays.asList(f1, f2, f3));
    }

    public static  ComposableFuture> all(final ComposableFuture f1, final ComposableFuture f2, final ComposableFuture f3, final ComposableFuture f4) {
        return all(false, Arrays.asList(f1, f2, f3, f4));
    }

    public static  ComposableFuture> all(final Iterable> futures) {
        return all(false, futures);
    }

    public static  ComposableFuture> all(final boolean failOnError, final Iterable> futures) {
        return Combiner.all(failOnError, futures);
    }

    public static  ComposableFuture> all(final boolean failOnError, final Map> futures) {
        return Combiner.all(failOnError, futures);
    }

    public static  ComposableFuture> first(final Map> futures, final int numOfSuccess) {
        return Combiner.first(futures, numOfSuccess, false, null, null);
    }

    public static  ComposableFuture> first(final Map> futures,
                                                           final int numOfSuccess, final long timeout, final TimeUnit timeUnit) {
        return Combiner.first(futures, numOfSuccess, false, timeout, timeUnit);
    }

    public static  ComposableFuture combine(final ComposableFuture left, final ComposableFuture right,
                                                          final BiFunction combiner) {
        return Combiner.combine(left, right, combiner);
    }

    public static  ComposableFuture combine(final ComposableFuture left, final ComposableFuture right,
                                                          final FutureBiFunction combiner) {
        return Combiner.combine(left, right, combiner);
    }

    public static  ComposableFuture combine(final ComposableFuture first, final ComposableFuture second,
                                                              final ComposableFuture third, final TriFunction combiner) {
        return Combiner.combine(first, second, third, combiner);
    }

    public static  ComposableFuture combine(final ComposableFuture first, final ComposableFuture second,
                                                              final ComposableFuture third, final FutureTriFunction combiner) {
        return Combiner.combine(first, second, third, combiner);
    }

    public static  ComposableFuture any(final ComposableFuture f1, final ComposableFuture f2) {
        return any(Arrays.asList(f1, f2));
    }

    public static  ComposableFuture any(final ComposableFuture f1, final ComposableFuture f2, final ComposableFuture f3) {
        return any(Arrays.asList(f1, f2, f3));
    }

    public static  ComposableFuture any(final List> futures) {
        return Combiner.any(futures);
    }

    public static  ComposableFuture foreach(final List elements, final R zero, final ForeachHandler handler) {
        ComposableFuture result = fromValue(zero);
        for (final T element : elements) {
            result = result.continueOnSuccess(new FutureSuccessHandler() {
                @Override
                public ComposableFuture handle(final R result) {
                    return handler.handle(element, result);
                }
            });
        }
        return result;
    }

    public static  ComposableFuture repeat(final int iterations, final R zero, final FutureSuccessHandler handler) {
        ComposableFuture result = fromValue(zero);
        for (int i = 0; i < iterations; ++i) {
            result = result.continueOnSuccess(new FutureSuccessHandler() {
                @Override
                public ComposableFuture handle(final R result) {
                    return handler.handle(result);
                }
            });
        }
        return result;
    }

    /**
     * Execute the producer on each element in the list in batches.
     * every batch is executed in parallel and the next batch begins only after the previous one ended.
     * An error in one of the futures produced by the producer will end the flow and return a future containing the error
     *
     * The batches are created in order i.e. the first batch is the first section of the list and so on.
     * The order within a single batch is undefined since it runs in parallel.
     *
     * @param elements the input to the producer
     * @param batchSize how many items will be processed in parallel
     * @param producer produces a future based on input from the element list
     * @param  the type of the elements in the input list
     * @param  the result type of the future returning from the producer
     * @return a future containing a list of all the results produced by the producer.
     */
    public static  ComposableFuture> batch(final List elements, final int batchSize,
                                                         final FutureSuccessHandler producer) {
        return batch(elements, 0, batchSize, producer);
    }

    private static  ComposableFuture> batch(final List elements, final int index, final int batchSize,
                                                         final FutureSuccessHandler producer) {
        if (index >= elements.size()) {
            return ComposableFutures.fromValue(Collections.emptyList());
        }

        final List> singleBatch = new ArrayList<>(batchSize);
        for (int i = index; i < index + batchSize && i < elements.size(); i++) {
            singleBatch.add(producer.handle(elements.get(i)));
        }

        final ComposableFuture> batchRes = all(true, singleBatch);
        return batchRes.continueOnSuccess(new FutureSuccessHandler, List>() {
            @Override
            public ComposableFuture> handle(final List batchResult) {
                final ComposableFuture> rest = batch(elements, index + batchSize, batchSize, producer);
                return rest.continueOnSuccess(new SuccessHandler, List>() {
                    @Override
                    public List handle(final List result) throws ExecutionException {
                        final ArrayList res = new ArrayList<>(result.size() + batchResult.size());
                        res.addAll(batchResult);
                        res.addAll(result);
                        return res;
                    }
                });
            }
        });
    }

    /**
     * Execute the producer on each element in the list in batches.
     * The batch size represent the max level of parallelism and each parallel flow will opportunistically try to process
     * The next available item on the list upon completion of the previous one.
     * Use this method when execution time for each element is highly irregular so that slow elements
     * In the beginning of the list won't necessarily hold up the rest of the execution.
     *
     * An error in one of the futures produced by the producer will end the flow and return a future containing the error
     *
     * @param elements the input to the producer
     * @param batchSize how many items will be processed in parallel
     * @param producer produces a future based on input from the element list
     * @param  the type of the elements in the input list
     * @param  the result type of the future returning from the producer
     * @return a future containing a list of all the results produced by the producer.
     */
    public static  ComposableFuture> batchUnordered(final List elements, final int batchSize,
                                                          final FutureSuccessHandler producer) {

        final AtomicInteger index = new AtomicInteger(0);
        final List>> futures = new ArrayList<>(batchSize);
        for (int i=0; i< batchSize; i++) {
            futures.add(seqUnordered(elements, index, producer));
        }

        return all(true, futures).continueOnSuccess(new SuccessHandler>, List>() {
            @Override
            public List handle(final List> result) throws ExecutionException {
                final ArrayList combined = new ArrayList<>(elements.size());
                for (final List lst : result) {
                    combined.addAll(lst);
                }

                return combined;
            }
        });
    }

    private static  ComposableFuture> seqUnordered(final List elements, final AtomicInteger index,
                                                                   final FutureSuccessHandler producer) {
        final int currentIndex = index.getAndIncrement();
        if (currentIndex >= elements.size()) {
            return ComposableFutures.fromValue(Collections.emptyList());
        } else {
            return producer.handle(elements.get(currentIndex)).continueOnSuccess(new FutureSuccessHandler>() {
                @Override
                public ComposableFuture> handle(final R result) {
                    final ComposableFuture> rest = seqUnordered(elements, index, producer);
                    return rest.continueOnSuccess(new SuccessHandler, List>() {
                        @Override
                        public List handle(final List restResult) throws ExecutionException {
                            final ArrayList combined = new ArrayList<>(restResult.size() + 1);
                            combined.addAll(restResult);
                            combined.add(result);
                            return combined;
                        }
                    });
                }
            });
        }
    }

    /**
     * Execute the producer on each element in the list in batches and return a stream of batch results.
     * Every batch is executed in parallel and the next batch begins only after the previous one ended.
     * The result of each batch is the next element in the stream.
     * An error in one of the futures produced by the producer will end the stream with the error
     *
     * @param elements the input to the producer
     * @param batchSize how many items will be processed in parallel
     * @param producer produces a future based on input from the element list
     * @param  the type of the elements in the input list
     * @param  the result type of the future returning from the producer
     * @return a stream containing the combined result of each batch
     */
    public static  Observable> batchToStream(final List elements, final int batchSize,
                                                           final FutureSuccessHandler producer) {
        return Observable.create(new Observable.OnSubscribe>() {
            @Override
            public void call(final Subscriber> subscriber) {
                batchToStream(elements, batchSize, 0, subscriber, producer);
            }
        });
    }

    private static  void batchToStream(final List elements, final int batchSize, final int index,
                                            final Subscriber> subscriber,
                                            final FutureSuccessHandler producer) {

        if (index >= elements.size()) {
            subscriber.onCompleted();
        } else {
            final List> singleBatch = new ArrayList<>(batchSize);
            for (int i = index; i < index + batchSize && i < elements.size(); i++) {
                singleBatch.add(producer.handle(elements.get(i)));
            }

            final ComposableFuture> batchRes = all(true, singleBatch);
            batchRes.consume(new Consumer>() {
                @Override
                public void consume(final Try> result) {
                    if (result.isSuccess()) {
                        subscriber.onNext(result.getValue());
                        batchToStream(elements, batchSize, index + batchSize, subscriber, producer);
                    } else {
                        subscriber.onError(result.getError());
                    }
                }
            });
        }
    }

    public static  ComposableFuture fromValue(final T value) {
        return fromValueEager(value);
    }

    public static  ComposableFuture fromValueEager(final T value) {
        return EagerComposableFuture.fromValue(value);
    }

    public static  ComposableFuture fromValueLazy(final T value) {
        return LazyComposableFuture.fromValue(value);
    }

    public static  ComposableFuture fromError(final Throwable error) {
        return fromErrorEager(error);
    }

    public static  ComposableFuture fromErrorEager(final Throwable error) {
        return EagerComposableFuture.fromError(error);
    }

    public static  ComposableFuture fromErrorLazy(final Throwable error) {
        return LazyComposableFuture.fromError(error);
    }

    public static  ComposableFuture fromTry(final Try tryValue) {
        if (tryValue.isSuccess()) {
            return fromValue(tryValue.getValue());
        } else {
            return fromError(tryValue.getError());
        }
    }

    public static  ComposableFuture fromNull() {
        return fromValue(null);
    }

    public static  ComposableFuture submitFuture(final Callable> task) {
        final ComposableFuture> submitRes = submit(false, task);
        return submitRes.continueOnSuccess(new FutureSuccessHandler, T>() {
            @Override
            public ComposableFuture handle(final ComposableFuture result) {
                return result;
            }
        });
    }

    /**
     * sends a callable task to the default thread pool and returns a ComposableFuture that represent the result.
     *
     * @param task the task to run.
     * @param   the future type
     * @return a future representing the result.
     */
    public static  ComposableFuture submit(final Callable task) {
        return submit(false, task);
    }

    public static  ComposableFuture submit(final Executor executor, final Callable task) {
        return EagerComposableFuture.submit(executor, task, false);
    }

    public static  ComposableFuture submit(final boolean delegateHandler, final Callable task) {
        return submitEager(delegateHandler, task);
    }

    public static  ComposableFuture submitEager(final boolean delegateHandler, final Callable task) {
        return EagerComposableFuture.submit(ExecutorServiceHolder.INSTANCE, task, delegateHandler);
    }

    public static  ComposableFuture submitLazy(final boolean delegateHandler, final Callable task) {
        return LazyComposableFuture.submit(ExecutorServiceHolder.INSTANCE, task, delegateHandler);
    }

    public static  ComposableFuture from(final T value, final Function function) {
        return submit(new Callable() {
            @Override
            public S call() throws Exception {
                return function.apply(value);
            }
        });
    }

    public static  ComposableFuture schedule(final Callable task, final long delay, final TimeUnit unit) {
        return scheduleEager(task, delay, unit);
    }

    public static  ComposableFuture scheduleLazy(final Callable task, final long delay, final TimeUnit unit) {
        return LazyComposableFuture.schedule(SchedulerServiceHolder.INSTANCE, task, delay, unit);
    }

    public static  ComposableFuture scheduleEager(final Callable task, final long delay, final TimeUnit unit) {
        return EagerComposableFuture.schedule(SchedulerServiceHolder.INSTANCE, task, delay, unit);
    }

    public static  ComposableFuture scheduleFuture(final Callable> task, final long delay, final TimeUnit unit) {
        final ComposableFuture> schedule = schedule(task, delay, unit);
        return schedule.continueOnSuccess(new FutureSuccessHandler, T>() {
            @Override
            public ComposableFuture handle(final ComposableFuture result) {
                return result;
            }
        });
    }

    /**
     * creates a new Promise. the promise can be used to create a single eager future.
     *
     * @param  the future type.
     * @return a promise
     */
    public static  ComposablePromise newPromise() {
        return newPromise(false);
    }

    public static  ComposablePromise newPromise(final Executor executor) {
        return new EagerComposableFuture<>(executor);
    }

    public static  ComposablePromise newPromise(final boolean delegateHandler) {
        if (delegateHandler) {
            return new EagerComposableFuture<>(ExecutorServiceHolder.INSTANCE);
        } else {
            return new EagerComposableFuture<>();
        }
    }

    public static  ComposableFuture build(final Producer producer) {
        return buildEager(producer);
    }

    /**
     * builds a lazy future from a producer. the producer itself is cached
     * and used afresh on every consumption.
     *
     * @param producer the result producer
     * @param       the future type
     * @return the future
     */
    public static  ComposableFuture buildLazy(final Producer producer) {
        return LazyComposableFuture.build(producer);
    }

    /**
     * builds a new eager future from a producer. the producer is consumed only once
     * abd the result(or error) is cached for future consumption.
     *
     * @param producer the result producer
     * @param       the future type
     * @return the future ;)
     */
    public static  ComposableFuture buildEager(final Producer producer) {
        return EagerComposableFuture.build(producer);
    }

    /**
     * adds a time cap to the provided future.
     * if response do not arrive after the specified time a TimeoutException is returned from the returned future.
     *
     * @param future   the source future
     * @param duration time duration before emitting a timeout
     * @param unit     the duration time unit
     * @param       the future type
     * @return a new future with a timeout
     */
    public static  ComposableFuture withTimeout(final ComposableFuture future, final long duration, final TimeUnit unit) {
        return future.withTimeout(SchedulerServiceHolder.INSTANCE, duration, unit);
    }

    /**
     * reties an eager future on failure "retries" times.
     *
     * @param retries max amount of retries
     * @param action  the eager future provider
     * @param      the future type
     * @return the composed result.
     */
    public static  ComposableFuture retry(final int retries, final FutureAction action) {
        return action.execute().continueOnError(new FutureErrorHandler() {
            @Override
            public ComposableFuture handle(final Throwable error) {
                if (retries < 1)
                    return ComposableFutures.fromError(error);
                else
                    return retry(retries - 1, action);
            }
        });
    }

    /**
     * reties an eager future on failure "retries" times. each try is time capped with the specified time limit.
     *
     * @param retries  max amount of retries
     * @param duration the max time duration allowed for each try
     * @param unit     the duration time unit
     * @param action   the eager future provider
     * @param       the future type
     * @return the composed result.
     */
    public static  ComposableFuture retry(final int retries, final long duration, final TimeUnit unit, final FutureAction action) {
        return action.execute().withTimeout(duration, unit).continueOnError(new FutureErrorHandler() {
            @Override
            public ComposableFuture handle(final Throwable error) {
                if (retries < 1)
                    return ComposableFutures.fromError(error);
                else
                    return retry(retries - 1, action);
            }
        });
    }

    /**
     * retries a lazy future on failure "retries" times.
     *
     * @param future  the lazy future
     * @param retries max amount of reties
     * @param      the future type
     * @return the composed result.
     */
    public static  ComposableFuture retryLazy(final ComposableFuture future, final int retries) {
        return future.continueOnError(new FutureErrorHandler() {
            @Override
            public ComposableFuture handle(final Throwable error) {
                if (retries < 1)
                    return ComposableFutures.fromError(error);
                else
                    return retryLazy(future, retries - 1);
            }
        });
    }

    public static  ComposableFuture retryLazy(final ComposableFuture future, final int retries, final long duration, final TimeUnit unit) {
        return future.withTimeout(duration, unit).continueOnError(new FutureErrorHandler() {
            @Override
            public ComposableFuture handle(final Throwable error) {
                if (retries < 1)
                    return ComposableFutures.fromError(error);
                else
                    return retryLazy(future, retries - 1, duration, unit);
            }
        });
    }

    /**
     * creates a future that fires the first future immediately and a second one after a specified time period
     * if result hasn't arrived yet.
     * should be used with eager futures.
     *
     * @param duration time to wait until the second future is fired
     * @param unit     the duration time unit
     * @param action   a provider of eager future
     * @param       the type of the future
     * @return the composed future
     */
    public static  ComposableFuture doubleDispatch(final long duration, final TimeUnit unit, final FutureAction action) {
        return EagerComposableFuture.doubleDispatch(action, duration, unit, getScheduler());
    }

    /**
     * creates a future that fires the first future immediately (after consumption) and a second one after a specified time period
     * if result hasn't arrived yet.
     * can only be used with lazy futures.
     *
     * @param future   the original lazy future
     * @param duration time duration before consuming the future the second time
     * @param unit     th4e duration time unit.
     * @param       the future type
     * @return the composed future
     */
    public static  ComposableFuture doubleDispatch(final ComposableFuture future, final long duration, final TimeUnit unit) {
        return ((LazyComposableFuture) future).doubleDispatch(getScheduler(), duration, unit);
    }

    public static  rx.Observable toColdObservable(final List> futures) {
        return toColdObservable(futures, true);
    }

    /**
     * translate a list of lazy futures to a cold Observable stream
     *
     * @param futures     the lazy list of futures
     * @param failOnError whether to close the stream upon a future error
     * @param          the stream type
     * @return the stream
     */
    public static  Observable toColdObservable(final List> futures, final boolean failOnError) {
        return Observable.create(new Observable.OnSubscribe() {
            @Override
            public void call(final Subscriber subscriber) {
                final AtomicInteger counter = new AtomicInteger(futures.size());
                final AtomicBoolean errorTrigger = new AtomicBoolean(false);

                for (final ComposableFuture future : futures) {
                    future.consume(new Consumer() {
                        @Override
                        public void consume(final Try result) {
                            if (result.isSuccess()) {
                                subscriber.onNext(result.getValue());
                                if (counter.decrementAndGet() == 0) {
                                    subscriber.onCompleted();
                                }
                            } else {
                                if (failOnError) {
                                    if (errorTrigger.compareAndSet(false, true)) {
                                        subscriber.onError(result.getError());
                                    }
                                    counter.set(0);
                                } else {
                                    if (counter.decrementAndGet() == 0) {
                                        subscriber.onCompleted();
                                    }
                                }
                            }
                        }
                    });
                }
            }
        });
    }

    /**
     * translate a list of eager futures into a hot Observable stream
     * the results of the futures will be stored in the stream for any future subscriber.
     *
     * @param futures     the list of eager futures
     * @param failOnError whether to close the stream upon a future error
     * @param          the stream type
     * @return the stream
     */
    public static  Observable toHotObservable(final List> futures, final boolean failOnError) {
        final ReplaySubject subject = ReplaySubject.create(futures.size());
        final AtomicInteger counter = new AtomicInteger(futures.size());
        final AtomicBoolean errorTrigger = new AtomicBoolean(false);

        for (final ComposableFuture future : futures) {
            future.consume(new Consumer() {
                @Override
                public void consume(final Try result) {
                    if (result.isSuccess()) {
                        subject.onNext(result.getValue());
                        if (counter.decrementAndGet() == 0) {
                            subject.onCompleted();
                        }
                    } else {
                        if (failOnError) {
                            if (errorTrigger.compareAndSet(false, true)) {
                                subject.onError(result.getError());
                            }
                            counter.set(0);
                        } else {
                            if (counter.decrementAndGet() == 0) {
                                subject.onCompleted();
                            }
                        }
                    }
                }
            });
        }

        return subject;
    }

    public static  Observable toObservable(final FutureProvider provider) {
        return Observable.create(new FutureProviderToStreamHandler<>(provider));
    }

    public static ExecutorService getExecutor() {
        return ExecutorServiceHolder.INSTANCE;
    }

    public static Scheduler getScheduler() {
        return SchedulerServiceHolder.INSTANCE;
    }
}