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 super T, ? extends U> 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 super T, U> 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 extends T> 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 super T, ? extends R> 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 + ")");
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy