Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
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 super List> subscriber) {
batchToStream(elements, batchSize, 0, subscriber, producer);
}
});
}
private static void batchToStream(final List elements, final int batchSize, final int index,
final Subscriber super List> 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 super T, ? extends S> 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