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

com.outbrain.ob1k.concurrent.lazy.LazyComposableFuture Maven / Gradle / Ivy

package com.outbrain.ob1k.concurrent.lazy;

import com.google.common.base.Function;
import com.google.common.base.Supplier;
import com.outbrain.ob1k.concurrent.*;
import com.outbrain.ob1k.concurrent.handlers.*;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

/**
 * a future that contains a producer. each time the future is consumed the producer is activated
 * to deliver a value or an error to the consumer.
 * 

* the final result is never stored so the future is actually stateless as opposed to the eager future * that eventually holds the final result. *

* the lazy future represent a computation that eventually creates a new value. that value can be consumed many times * by calling consume and supplying a consumer. * * @author asy ronen */ public final class LazyComposableFuture implements ComposableFuture { private final Producer producer; private final Executor executor; private LazyComposableFuture(final Producer producer) { this(producer, null); } private LazyComposableFuture(final Producer producer, final Executor executor) { this.producer = producer; this.executor = executor; } public static LazyComposableFuture fromValue(final T value) { return new LazyComposableFuture<>(new Producer() { @Override public void produce(final Consumer consumer) { consumer.consume(Try.fromValue(value)); } }); } public static LazyComposableFuture fromError(final Throwable error) { return new LazyComposableFuture<>(new Producer() { @Override public void produce(final Consumer consumer) { consumer.consume(Try.fromError(error)); } }); } public static ComposableFuture build(final Producer producer) { return new LazyComposableFuture<>(producer); } public static LazyComposableFuture apply(final Supplier supplier) { return new LazyComposableFuture<>(new Producer() { @Override public void produce(final Consumer consumer) { try { consumer.consume(Try.fromValue(supplier.get())); } catch (final Exception e) { consumer.consume(Try.fromError(e)); } } }); } public static LazyComposableFuture submit(final Executor executor, final Callable task, final boolean delegateHandler) { return new LazyComposableFuture<>(new Producer() { @Override public void produce(final Consumer consumer) { executor.execute(new Runnable() { @Override public void run() { try { consumer.consume(new Try.Success<>(task.call())); } catch (final Exception e) { consumer.consume(Try.fromError(e)); } } }); } }, delegateHandler ? executor : null); } public static LazyComposableFuture schedule(final Scheduler scheduler, final Callable task, final long delay, final TimeUnit timeUnit) { return new LazyComposableFuture<>(new Producer() { @Override public void produce(final Consumer consumer) { scheduler.schedule(new Runnable() { @Override public void run() { try { consumer.consume(new Try.Success<>(task.call())); } catch (final Exception e) { consumer.consume(Try.fromError(e)); } } }, delay, timeUnit); } }); } public static LazyComposableFuture collectFirst(final List> futures) { return new LazyComposableFuture<>(new Producer() { @Override public void produce(final Consumer consumer) { final AtomicBoolean done = new AtomicBoolean(); for (final ComposableFuture future : futures) { future.consume(new Consumer() { @Override public void consume(final Try result) { if (done.compareAndSet(false, true)) { consumer.consume(result); } } }); } } }); } public static LazyComposableFuture> collectAll(final List> futures) { return new LazyComposableFuture<>(new Producer>() { @Override public void produce(final Consumer> consumer) { final AtomicInteger counter = new AtomicInteger(futures.size()); final AtomicBoolean errorTrigger = new AtomicBoolean(false); final ConcurrentMap> results = new ConcurrentHashMap<>(futures.size()); int index = 0; for (final ComposableFuture future : futures) { final int i = index++; future.consume(new Consumer() { @Override public void consume(final Try result) { results.put(i, result); if (result.isSuccess()) { final int count = counter.decrementAndGet(); if (count == 0) { consumer.consume(Try.fromValue(createResultList(results))); } } else { if (errorTrigger.compareAndSet(false, true)) { counter.set(0); consumer.consume(Try.>fromError(result.getError())); } } } }); } } }); } private static List createResultList(final ConcurrentMap> results) { final List list = new ArrayList<>(results.size()); for (int i = 0; i < results.size(); i++) { final Try tryValue = results.get(i); list.add(tryValue != null ? tryValue.getValue() : null); } return list; } @Override public void consume(final Consumer consumer) { if (executor != null) { executor.execute(new Runnable() { @Override public void run() { producer.produce(consumer); } }); } else { producer.produce(consumer); } } public void consumeSync(final Consumer consumer) throws InterruptedException { final CountDownLatch latch = new CountDownLatch(1); this.consume(new Consumer() { @Override public void consume(final Try result) { consumer.consume(result); latch.countDown(); } }); latch.await(); } @Override public ComposableFuture continueOnSuccess(final SuccessHandler handler) { final LazyComposableFuture outer = this; return new LazyComposableFuture<>(new Producer() { @Override public void produce(final Consumer consumer) { outer.consume(new Consumer() { @Override public void consume(final Try result) { if (result.isSuccess()) { try { consumer.consume(Try.fromValue(handler.handle(result.getValue()))); } catch (final ExecutionException e) { final Throwable error = e.getCause() != null ? e.getCause() : e; consumer.consume(Try.fromError(error)); } catch (final Exception e) { consumer.consume(Try.fromError(e)); } } else { consumer.consume(Try.fromError(result.getError())); } } }); } }); } @Override public ComposableFuture continueOnSuccess(final FutureSuccessHandler handler) { final LazyComposableFuture outer = this; return new LazyComposableFuture<>(new Producer() { @Override public void produce(final Consumer consumer) { outer.consume(new Consumer() { @Override public void consume(final Try result) { if (result.isSuccess()) { try { final ComposableFuture next = handler.handle(result.getValue()); if (next == null) { consumer.consume(Try.fromValue(null)); } else { next.consume(new Consumer() { @Override public void consume(final Try nextResult) { consumer.consume(nextResult); } }); } } catch (final Exception e) { consumer.consume(Try.fromError(e)); } } else { consumer.consume(Try.fromError(result.getError())); } } }); } }); } @Override public ComposableFuture continueOnError(final ErrorHandler handler) { final LazyComposableFuture outer = this; return new LazyComposableFuture<>(new Producer() { @Override public void produce(final Consumer consumer) { outer.consume(new Consumer() { @Override public void consume(final Try result) { if (result.isSuccess()) { consumer.consume(result); } else { try { consumer.consume(Try.fromValue(handler.handle(result.getError()))); } catch (final ExecutionException e) { consumer.consume(Try.fromError(e)); } } } }); } }); } @Override public ComposableFuture continueOnError(final FutureErrorHandler handler) { final LazyComposableFuture outer = this; return new LazyComposableFuture<>(new Producer() { @Override public void produce(final Consumer consumer) { outer.consume(new Consumer() { @Override public void consume(final Try result) { if (result.isSuccess()) { consumer.consume(result); } else { try { final ComposableFuture next = handler.handle(result.getError()); if (next == null) { consumer.consume(Try.fromValue(null)); } else { next.consume(new Consumer() { @Override public void consume(final Try nextResult) { consumer.consume(nextResult); } }); } } catch (final Exception e) { consumer.consume(Try.fromError(e)); } } } }); } }); } @Override public LazyComposableFuture withTimeout(final Scheduler scheduler, final long timeout, final TimeUnit unit, final String taskDescription) { final LazyComposableFuture deadline = new LazyComposableFuture<>(new Producer() { @Override public void produce(final Consumer consumer) { scheduler.schedule(new Runnable() { @Override public void run() { consumer.consume(Try.fromError(new TimeoutException("Timeout occurred on task ('" + taskDescription + "' " + timeout + " " + unit + ")"))); } }, timeout, unit); } }); return collectFirst(Arrays.>asList(this, deadline)); } @Override public LazyComposableFuture withTimeout(final Scheduler scheduler, final long timeout, final TimeUnit unit) { return withTimeout(scheduler, timeout, unit, "unspecified context"); } @Override public LazyComposableFuture withTimeout(final long timeout, final TimeUnit unit, final String taskDescription) { return withTimeout(ComposableFutures.getScheduler(), timeout, unit, taskDescription); } @Override public LazyComposableFuture withTimeout(final long timeout, final TimeUnit unit) { return withTimeout(ComposableFutures.getScheduler(), timeout, unit); } @Override public ComposableFuture continueWith(final ResultHandler handler) { final LazyComposableFuture outer = this; return new LazyComposableFuture<>(new Producer() { @Override public void produce(final Consumer consumer) { outer.consume(new Consumer() { @Override public void consume(final Try result) { try { consumer.consume(Try.fromValue(handler.handle(result))); } catch (final ExecutionException e) { final Throwable error = e.getCause() != null ? e.getCause() : e; consumer.consume(Try.fromError(error)); } catch (final Exception e) { consumer.consume(Try.fromError(e)); } } }); } }); } @Override public ComposableFuture continueWith(final FutureResultHandler handler) { final LazyComposableFuture outer = this; return new LazyComposableFuture<>(new Producer() { @Override public void produce(final Consumer consumer) { outer.consume(new Consumer() { @Override public void consume(final Try result) { try { final ComposableFuture next = handler.handle(result); if (next == null) { consumer.consume(Try.fromValue(null)); } else { next.consume(new Consumer() { @Override public void consume(final Try nextResult) { consumer.consume(nextResult); } }); } } catch (final Exception e) { consumer.consume(Try.fromError(e)); } } }); } }); } @Override public ComposableFuture transform(final Function function) { return continueOnSuccess(new SuccessHandler() { @Override public R handle(final T result) { return function.apply(result); } }); } @Override public ComposableFuture materialize() { return ComposableFutures.buildEager(producer); } public LazyComposableFuture doubleDispatch(final Scheduler scheduler, final long timeout, final TimeUnit unit) { final LazyComposableFuture outer = this; return new LazyComposableFuture<>(new Producer() { @Override public void produce(final Consumer consumer) { final AtomicBoolean done = new AtomicBoolean(); outer.consume(new Consumer() { @Override public void consume(final Try firstRes) { if (done.compareAndSet(false, true)) { consumer.consume(firstRes); } } }); scheduler.schedule(new Runnable() { @Override public void run() { if (!done.get()) { outer.consume(new Consumer() { @Override public void consume(final Try secondRes) { if (done.compareAndSet(false, true)) { consumer.consume(secondRes); } } }); } } }, timeout, unit); } }); } @Override public T get() throws ExecutionException, InterruptedException { final CountDownLatch latch = new CountDownLatch(1); final AtomicReference> box = new AtomicReference<>(); this.consume(new Consumer() { @Override public void consume(final Try result) { box.set(result); latch.countDown(); } }); latch.await(); final Try res = box.get(); if (res == null) { throw new ExecutionException(new NullPointerException("no result error.")); } else if (res.isSuccess()) { return res.getValue(); } else { throw new ExecutionException(res.getError()); } } @Override public T get(final long timeout, final TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { final CountDownLatch latch = new CountDownLatch(1); final AtomicReference> box = new AtomicReference<>(); this.consume(new Consumer() { @Override public void consume(final Try result) { box.set(result); latch.countDown(); } }); if (latch.await(timeout, unit)) { final Try res = box.get(); if (res.isSuccess()) { return res.getValue(); } else { throw new ExecutionException(res.getError()); } } else { throw new TimeoutException("Timeout occurred while waiting for value (" + timeout + unit + ")"); } } }