/* __ __ __ __ __ ___
* \ \ / / \ \ / / __/
* \ \/ / /\ \ \/ / /
* \____/__/ \__\____/__/.ɪᴏ
* ᶜᵒᵖʸʳᶦᵍʰᵗ ᵇʸ ᵛᵃᵛʳ ⁻ ˡᶦᶜᵉⁿˢᵉᵈ ᵘⁿᵈᵉʳ ᵗʰᵉ ᵃᵖᵃᶜʰᵉ ˡᶦᶜᵉⁿˢᵉ ᵛᵉʳˢᶦᵒⁿ ᵗʷᵒ ᵈᵒᵗ ᶻᵉʳᵒ
*/
package io.vavr.concurrent;
import io.vavr.CheckedFunction0;
import io.vavr.collection.Queue;
import io.vavr.control.Try;
import io.vavr.control.Option;
import java.util.Objects;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutorService;
import java.util.function.Consumer;
/**
* <strong>INTERNAL API - This class is subject to change.</strong>
* <p>
* Once a {@code FutureImpl} is created, one (and only one) of the following methods is called
* to complete it with a result:
* <ul>
* <li>{@link #run(CheckedFunction0)} - typically called within a {@code Future} factory method</li>
* <li>{@link #tryComplete(Try)} - explicit write operation, typically called by {@code Promise}</li>
* </ul>
* <p>
* <strong>Lifecycle of a {@code FutureImpl}:</strong>
* <p>
* 1) Creation
* <ul>
* <li>{@code value = None}</li>
* <li>{@code actions = Queue.empty()}</li>
* <li>{@code job = null}</li>
* </ul>
* 2) Run
* <ul>
* <li>{@code value = None}</li>
* <li>{@code actions = Queue(...)}</li>
* <li>{@code job = java.util.concurrent.Future}</li>
* </ul>
* 3) Complete
* <ul>
* <li>{@code value = Some(Try)}</li>
* <li>{@code actions = null}</li>
* <li>{@code job = null}</li>
* </ul>
* 4) Cancel
* <ul>
* <li>{@code value = Some(Failure(CancellationException))}</li>
* <li>{@code actions = null}</li>
* <li>{@code job = null}</li>
* </ul>
*
* @param <T> Result of the computation.
* @author Daniel Dietrich
*/
final class FutureImpl<T> implements Future<T> {
/**
* Used to start new threads.
*/
private final ExecutorService executorService;
/**
* Used to synchronize state changes.
*/
private final Object lock = new Object();
/**
* Once the Future is completed, the value is defined.
*/
@GuardedBy("lock")
private volatile Option<Try<T>> value = Option.none();
/**
* The queue of actions is filled when calling onComplete() before the Future is completed or cancelled.
* Otherwise actions = null.
*/
@GuardedBy("lock")
private Queue<Consumer<? super Try<T>>> actions = Queue.empty();
/**
* Once a computation is started via run(), job is defined and used to control the lifecycle of the computation.
* <p>
* The {@code java.util.concurrent.Future} is not intended to store the result of the computation, it is stored in
* {@code value} instead.
*/
@GuardedBy("lock")
private java.util.concurrent.Future<?> job = null;
/**
* Creates a Future, {@link #run(CheckedFunction0)} has to be called separately.
*
* @param executorService An {@link ExecutorService} to run and control the computation and to perform the actions.
*/
FutureImpl(ExecutorService executorService) {
Objects.requireNonNull(executorService, "executorService is null");
this.executorService = executorService;
}
@Override
public Future<T> await() {
if (!isCompleted()) {
final Object monitor = new Object();
onComplete(ignored -> {
synchronized (monitor) {
monitor.notify();
}
});
synchronized (monitor) {
if (!isCompleted()) {
Try.run(monitor::wait);
}
}
}
return this;
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
synchronized (lock) {
if (isCompleted()) {
return false;
} else {
return Try.of(() -> job == null || job.cancel(mayInterruptIfRunning)).onSuccess(cancelled -> {
if (cancelled) {
value = Option.some(Try.failure(new CancellationException()));
actions = null;
job = null;
}
}).getOrElse(false);
}
}
}
@Override
public ExecutorService executorService() {
return executorService;
}
@Override
public Option<Try<T>> getValue() {
return value;
}
@Override
public boolean isCompleted() {
return value.isDefined();
}
@Override
public Future<T> onComplete(Consumer<? super Try<T>> action) {
Objects.requireNonNull(action, "action is null");
if (isCompleted()) {
perform(action);
} else {
synchronized (lock) {
if (isCompleted()) {
perform(action);
} else {
actions = actions.enqueue(action);
}
}
}
return this;
}
// This class is MUTABLE and therefore CANNOT CHANGE DEFAULT equals() and hashCode() behavior.
// See http://stackoverflow.com/questions/4718009/mutable-objects-and-hashcode
@Override
public String toString() {
return stringPrefix() + "(" + value.map(String::valueOf).getOrElse("?") + ")";
}
/**
* Runs a computation using the underlying ExecutorService.
* <p>
* DEV-NOTE: Internally this method is called by the static {@code Future} factory methods.
*
* @throws IllegalStateException if the Future is pending, completed or cancelled
* @throws NullPointerException if {@code computation} is null.
*/
void run(CheckedFunction0<? extends T> computation) {
Objects.requireNonNull(computation, "computation is null");
synchronized (lock) {
if (job != null) {
throw new IllegalStateException("The Future is already running.");
}
if (isCompleted()) {
throw new IllegalStateException("The Future is completed.");
}
try {
// if the ExecutorService runs the computation
// - in a different thread, the lock ensures that the job is assigned before the computation completes
// - in the current thread, the job is already completed and the `job` variable remains null
final java.util.concurrent.Future<?> tmpJob = executorService.submit(() -> complete(Try.of(computation)));
if (!isCompleted()) {
job = tmpJob;
}
} catch (Throwable t) {
// ensures that the Future completes if the `executorService.submit()` method throws
if (!isCompleted()) {
complete(Try.failure(t));
}
}
}
}
boolean tryComplete(Try<? extends T> value) {
Objects.requireNonNull(value, "value is null");
synchronized (lock) {
if (isCompleted()) {
return false;
} else {
complete(value);
return true;
}
}
}
/**
* Completes this Future with a value.
* <p>
* DEV-NOTE: Internally this method is called by the {@code Future.run()} method and by {@code Promise}.
*
* @param value A Success containing a result or a Failure containing an Exception.
* @throws IllegalStateException if the Future is already completed or cancelled.
* @throws NullPointerException if the given {@code value} is null.
*/
private void complete(Try<? extends T> value) {
Objects.requireNonNull(value, "value is null");
final Queue<Consumer<? super Try<T>>> actions;
// it is essential to make the completed state public *before* performing the actions
synchronized (lock) {
if (isCompleted()) {
throw new IllegalStateException("The Future is completed.");
}
actions = this.actions;
this.value = Option.some(Try.narrow(value));
this.actions = null;
this.job = null;
}
actions.forEach(this::perform);
}
private void perform(Consumer<? super Try<T>> action) {
Try.run(() -> executorService.execute(() -> action.accept(value.get())));
}
}