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

javaslang.concurrent.FutureImpl Maven / Gradle / Ivy

/*     / \____  _    _  ____   ______  / \ ____  __    _______
 *    /  /    \/ \  / \/    \ /  /\__\/  //    \/  \  //  /\__\   JΛVΛSLΛNG
 *  _/  /  /\  \  \/  /  /\  \\__\\  \  //  /\  \ /\\/ \ /__\ \   Copyright 2014-2016 Javaslang, http://javaslang.io
 * /___/\_/  \_/\____/\_/  \_/\__\/__/\__\_/  \_//  \__/\_____/   Licensed under the Apache License, Version 2.0
 */
package javaslang.concurrent;

import javaslang.collection.Queue;
import javaslang.control.Option;
import javaslang.control.Try;
import javaslang.control.Try.CheckedSupplier;

import java.util.Objects;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutorService;
import java.util.function.Consumer;

/**
 * INTERNAL API - This class is subject to change.
 *
 * {@link Future} implementation, for internal use only.
 * 

* Lifecycle of a {@code FutureImpl}: *

* 1) Creation *

    *
  • {@code value = None}
  • *
  • {@code actions = Queue.empty()}
  • *
  • {@code job = null}
  • *
* 2) Run *
    *
  • {@code value = None}
  • *
  • {@code actions = Queue(...)}
  • *
  • {@code job = java.util.concurrent.Future}
  • *
* 3) Complete *
    *
  • {@code value = Some(Try)}
  • *
  • {@code actions = null}
  • *
  • {@code job = null}
  • *
* 4) Cancel *
    *
  • {@code value = Some(Failure(CancellationException))}
  • *
  • {@code actions = null}
  • *
  • {@code job = null}
  • *
* * @param Result of the computation. * @author Daniel Dietrich * @since 2.0.0 */ final class FutureImpl implements Future { /** * 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> 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>> actions = Queue.empty(); /** * Once a computation is started via run(), job is defined and used to control the lifecycle of the computation. * * @@GuardedBy("lock") */ private java.util.concurrent.Future> job = null; /** * Creates a Future, {@link #run(CheckedSupplier)} 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 void await() { final Object monitor = new Object(); onComplete(ignored -> { synchronized (monitor) { monitor.notify(); } }); synchronized (monitor) { if (!isCompleted()) { Try.run(monitor::wait); } } } @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> getValue() { return value; } @Override public boolean isCompleted() { return value.isDefined(); } @Override public Future onComplete(Consumer> 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; } /** * Runs a computation using the underlying ExecutorService. *

* 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(CheckedSupplier 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."); } // 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 try { final java.util.concurrent.Future> tmpJob = executorService.submit(() -> complete(Try.of(computation))); if (!isCompleted()) { job = tmpJob; } } catch (Throwable t) { if (!isCompleted()) { complete(Try.failure(t)); } } } } /** * Completes this Future with a value. *

* 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. * @return The given {@code value} for convenience purpose. * @throws IllegalStateException if the Future is already completed or cancelled. * @throws NullPointerException if the given {@code value} is null. */ @SuppressWarnings("unchecked") Try complete(Try value) { Objects.requireNonNull(value, "value is null"); final Queue>> 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) value); this.actions = null; this.job = null; } actions.forEach(this::perform); return (Try) value; } boolean tryComplete(Try value) { Objects.requireNonNull(value, "value is null"); synchronized (lock) { if (isCompleted()) { return false; } else { complete(value); return true; } } } private void perform(Consumer> action) { Try.run(() -> executorService.execute(() -> action.accept(value.get()))); } // 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("?") + ")"; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy