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

com.google.common.util.concurrent.ExecutionSequencer Maven / Gradle / Ivy

/*
 * Copyright (C) 2018 The Guava Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

package com.google.common.util.concurrent;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.util.concurrent.ExecutionSequencer.RunningState.CANCELLED;
import static com.google.common.util.concurrent.ExecutionSequencer.RunningState.NOT_RUN;
import static com.google.common.util.concurrent.ExecutionSequencer.RunningState.STARTED;
import static com.google.common.util.concurrent.Futures.immediateCancelledFuture;
import static com.google.common.util.concurrent.Futures.immediateFuture;
import static com.google.common.util.concurrent.Futures.immediateVoidFuture;
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;

import com.google.common.annotations.Beta;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;

/**
 * Serializes execution of tasks, somewhat like an "asynchronous {@code synchronized} block." Each
 * {@linkplain #submit enqueued} callable will not be submitted to its associated executor until the
 * previous callable has returned -- and, if the previous callable was an {@link AsyncCallable}, not
 * until the {@code Future} it returned is {@linkplain Future#isDone done} (successful, failed, or
 * cancelled).
 *
 * 

This class has limited support for cancellation and other "early completion": * *

    *
  • While calls to {@code submit} and {@code submitAsync} return a {@code Future} that can be * cancelled, cancellation never propagates to a task that has started to run -- neither to * the callable itself nor to any {@code Future} returned by an {@code AsyncCallable}. * (However, cancellation can prevent an unstarted task from running.) Therefore, the * next task will wait for any running callable (or pending {@code Future} returned by an * {@code AsyncCallable}) to complete, without interrupting it (and without calling {@code * cancel} on the {@code Future}). So beware: Even if you cancel every precededing {@code * Future} returned by this class, the next task may still have to wait.. *
  • Once an {@code AsyncCallable} returns a {@code Future}, this class considers that task to * be "done" as soon as that {@code Future} completes in any way. Notably, a {@code * Future} is "completed" even if it is cancelled while its underlying work continues on a * thread, an RPC, etc. The {@code Future} is also "completed" if it fails "early" -- for * example, if the deadline expires on a {@code Future} returned from {@link * Futures#withTimeout} while the {@code Future} it wraps continues its underlying work. So * beware: Your {@code AsyncCallable} should not complete its {@code Future} until it is * safe for the next task to start. *
* *

An additional limitation: this class serializes execution of tasks but not any * listeners of those tasks. * *

This class is similar to {@link MoreExecutors#newSequentialExecutor}. This class is different * in a few ways: * *

    *
  • Each task may be associated with a different executor. *
  • Tasks may be of type {@code AsyncCallable}. *
  • Running tasks cannot be interrupted. (Note that {@code newSequentialExecutor} does * not return {@code Future} objects, so it doesn't support interruption directly, either. * However, utilities that use that executor have the ability to interrupt tasks * running on it. This class, by contrast, does not expose an {@code Executor} API.) *
* *

If you don't need the features of this class, you may prefer {@code newSequentialExecutor} for * its simplicity and ability to accommodate interruption. * * @since 26.0 */ @Beta public final class ExecutionSequencer { private ExecutionSequencer() {} /** Creates a new instance. */ public static ExecutionSequencer create() { return new ExecutionSequencer(); } /** This reference acts as a pointer tracking the head of a linked list of ListenableFutures. */ private final AtomicReference> ref = new AtomicReference<>(immediateVoidFuture()); private ThreadConfinedTaskQueue latestTaskQueue = new ThreadConfinedTaskQueue(); /** * This object is unsafely published, but avoids problematic races by relying exclusively on the * identity equality of its Thread field so that the task field is only accessed by a single * thread. */ private static final class ThreadConfinedTaskQueue { /** * This field is only used for identity comparisons with the current thread. Field assignments * are atomic, but do not provide happens-before ordering; however: * *

    *
  • If this field's value == currentThread, we know that it's up to date, because write * operations in a thread always happen-before subsequent read operations in the same * thread *
  • If this field's value == null because of unsafe publication, we know that it isn't the * object associated with our thread, because if it was the publication wouldn't have been * unsafe and we'd have seen our thread as the value. This state is also why a new * ThreadConfinedTaskQueue object must be created for each inline execution, because * observing a null thread does not mean the object is safe to reuse. *
  • If this field's value is some other thread object, we know that it's not our thread. *
  • If this field's value == null because it originally belonged to another thread and that * thread cleared it, we still know that it's not associated with our thread *
  • If this field's value == null because it was associated with our thread and was * cleared, we know that we're not executing inline any more *
* * All the states where thread != currentThread are identical for our purposes, and so even * though it's racy, we don't care which of those values we get, so no need to synchronize. */ Thread thread; /** Only used by the thread associated with this object */ Runnable nextTask; /** Only used by the thread associated with this object */ Executor nextExecutor; } /** * Enqueues a task to run when the previous task (if any) completes. * *

Cancellation does not propagate from the output future to a callable that has begun to * execute, but if the output future is cancelled before {@link Callable#call()} is invoked, * {@link Callable#call()} will not be invoked. */ public ListenableFuture submit(final Callable callable, Executor executor) { checkNotNull(callable); checkNotNull(executor); return submitAsync( new AsyncCallable() { @Override public ListenableFuture call() throws Exception { return immediateFuture(callable.call()); } @Override public String toString() { return callable.toString(); } }, executor); } /** * Enqueues a task to run when the previous task (if any) completes. * *

Cancellation does not propagate from the output future to the future returned from {@code * callable} or a callable that has begun to execute, but if the output future is cancelled before * {@link AsyncCallable#call()} is invoked, {@link AsyncCallable#call()} will not be invoked. */ public ListenableFuture submitAsync( final AsyncCallable callable, final Executor executor) { checkNotNull(callable); checkNotNull(executor); final TaskNonReentrantExecutor taskExecutor = new TaskNonReentrantExecutor(executor, this); final AsyncCallable task = new AsyncCallable() { @Override public ListenableFuture call() throws Exception { if (!taskExecutor.trySetStarted()) { return immediateCancelledFuture(); } return callable.call(); } @Override public String toString() { return callable.toString(); } }; /* * Four futures are at play here: * taskFuture is the future tracking the result of the callable. * newFuture is a future that completes after this and all prior tasks are done. * oldFuture is the previous task's newFuture. * outputFuture is the future we return to the caller, a nonCancellationPropagating taskFuture. * * newFuture is guaranteed to only complete once all tasks previously submitted to this instance * have completed - namely after oldFuture is done, and taskFuture has either completed or been * cancelled before the callable started execution. */ final SettableFuture newFuture = SettableFuture.create(); final ListenableFuture oldFuture = ref.getAndSet(newFuture); // Invoke our task once the previous future completes. final TrustedListenableFutureTask taskFuture = TrustedListenableFutureTask.create(task); oldFuture.addListener(taskFuture, taskExecutor); final ListenableFuture outputFuture = Futures.nonCancellationPropagating(taskFuture); // newFuture's lifetime is determined by taskFuture, which can't complete before oldFuture // unless taskFuture is cancelled, in which case it falls back to oldFuture. This ensures that // if the future we return is cancelled, we don't begin execution of the next task until after // oldFuture completes. Runnable listener = new Runnable() { @Override public void run() { if (taskFuture.isDone()) { // Since the value of oldFuture can only ever be immediateFuture(null) or setFuture of // a future that eventually came from immediateFuture(null), this doesn't leak // throwables or completion values. newFuture.setFuture(oldFuture); } else if (outputFuture.isCancelled() && taskExecutor.trySetCancelled()) { // If this CAS succeeds, we know that the provided callable will never be invoked, // so when oldFuture completes it is safe to allow the next submitted task to // proceed. Doing this immediately here lets the next task run without waiting for // the cancelled task's executor to run the noop AsyncCallable. // // --- // // If the CAS fails, the provided callable already started running (or it is about // to). Our contract promises: // // 1. not to execute a new callable until the old one has returned // // If we were to cancel taskFuture, that would let the next task start while the old // one is still running. // // Now, maybe we could tweak our implementation to not start the next task until the // callable actually completes. (We could detect completion in our wrapper // `AsyncCallable task`.) However, our contract also promises: // // 2. not to cancel any Future the user returned from an AsyncCallable // // We promise this because, once we cancel that Future, we would no longer be able to // tell when any underlying work it is doing is done. Thus, we might start a new task // while that underlying work is still running. // // So that is why we cancel only in the case of CAS success. taskFuture.cancel(false); } } }; // Adding the listener to both futures guarantees that newFuture will aways be set. Adding to // taskFuture guarantees completion if the callable is invoked, and adding to outputFuture // propagates cancellation if the callable has not yet been invoked. outputFuture.addListener(listener, directExecutor()); taskFuture.addListener(listener, directExecutor()); return outputFuture; } enum RunningState { NOT_RUN, CANCELLED, STARTED, } /** * This class helps avoid a StackOverflowError when large numbers of tasks are submitted with * {@link MoreExecutors#directExecutor}. Normally, when the first future completes, all the other * tasks would be called recursively. Here, we detect that the delegate executor is executing * inline, and maintain a queue to dispatch tasks iteratively. There is one instance of this class * per call to submit() or submitAsync(), and each instance supports only one call to execute(). * *

This class would certainly be simpler and easier to reason about if it were built with * ThreadLocal; however, ThreadLocal is not well optimized for the case where the ThreadLocal is * non-static, and is initialized/removed frequently - this causes churn in the Thread specific * hashmaps. Using a static ThreadLocal to avoid that overhead would mean that different * ExecutionSequencer objects interfere with each other, which would be undesirable, in addition * to increasing the memory footprint of every thread that interacted with it. In order to release * entries in thread-specific maps when the ThreadLocal object itself is no longer referenced, * ThreadLocal is usually implemented with a WeakReference, which can have negative performance * properties; for example, calling WeakReference.get() on Android will block during an * otherwise-concurrent GC cycle. */ @SuppressWarnings("ShouldNotSubclass") // Saving an allocation here is worth it private static final class TaskNonReentrantExecutor extends AtomicReference implements Executor, Runnable { /** * Used to update and read the latestTaskQueue field. Set to null once the runnable has been run * or queued. */ ExecutionSequencer sequencer; /** * Executor the task was set to run on. Set to null when the task has been queued, run, or * cancelled. */ Executor delegate; /** * Set before calling delegate.execute(); set to null once run, so that it can be GCed; this * object may live on after, if submitAsync returns an incomplete future. */ Runnable task; /** Thread that called execute(). Set in execute, cleared when delegate.execute() returns. */ Thread submitting; private TaskNonReentrantExecutor(Executor delegate, ExecutionSequencer sequencer) { super(NOT_RUN); this.delegate = delegate; this.sequencer = sequencer; } @Override public void execute(Runnable task) { // If this operation was successfully cancelled already, calling the runnable will be a noop. // This also avoids a race where if outputFuture is cancelled, it will call taskFuture.cancel, // which will call newFuture.setFuture(oldFuture), to allow the next task in the queue to run // without waiting for the user's executor to run our submitted Runnable. However, this can // interact poorly with the reentrancy-avoiding behavior of this executor - when the operation // before the cancelled future completes, it will synchronously complete both the newFuture // from the cancelled operation and its own. This can cause one runnable to queue two tasks, // breaking the invariant this method relies on to iteratively run the next task after the // previous one completes. if (get() == RunningState.CANCELLED) { delegate = null; sequencer = null; return; } submitting = Thread.currentThread(); try { ThreadConfinedTaskQueue submittingTaskQueue = sequencer.latestTaskQueue; if (submittingTaskQueue.thread == submitting) { sequencer = null; // Submit from inside a reentrant submit. We don't know if this one will be reentrant (and // can't know without submitting something to the executor) so queue to run iteratively. // Task must be null, since each execution on this executor can only produce one more // execution. checkState(submittingTaskQueue.nextTask == null); submittingTaskQueue.nextTask = task; submittingTaskQueue.nextExecutor = delegate; delegate = null; } else { Executor localDelegate = delegate; delegate = null; this.task = task; localDelegate.execute(this); } } finally { // Important to null this out here - if we did *not* execute inline, we might still // run() on the same thread that called execute() - such as in a thread pool, and think // that it was happening inline. As a side benefit, avoids holding on to the Thread object // longer than necessary. submitting = null; } } @SuppressWarnings("ShortCircuitBoolean") @Override public void run() { Thread currentThread = Thread.currentThread(); if (currentThread != submitting) { Runnable localTask = task; task = null; localTask.run(); return; } // Executor called reentrantly! Make sure that further calls don't overflow stack. Further // reentrant calls will see that their current thread is the same as the one set in // latestTaskQueue, and queue rather than calling execute() directly. ThreadConfinedTaskQueue executingTaskQueue = new ThreadConfinedTaskQueue(); executingTaskQueue.thread = currentThread; // Unconditionally set; there is no risk of throwing away a queued task from another thread, // because in order for the current task to run on this executor the previous task must have // already started execution. Because each task on a TaskNonReentrantExecutor can only produce // one execute() call to another instance from the same ExecutionSequencer, we know by // induction that the task that launched this one must not have added any other runnables to // that thread's queue, and thus we cannot be replacing a TaskAndThread object that would // otherwise have another task queued on to it. Note the exception to this, cancellation, is // specially handled in execute() - execute() calls triggered by cancellation are no-ops, and // thus don't count. sequencer.latestTaskQueue = executingTaskQueue; sequencer = null; try { Runnable localTask = task; task = null; localTask.run(); // Now check if our task attempted to reentrantly execute the next task. Runnable queuedTask; Executor queuedExecutor; // Intentionally using non-short-circuit operator while ((queuedTask = executingTaskQueue.nextTask) != null & (queuedExecutor = executingTaskQueue.nextExecutor) != null) { executingTaskQueue.nextTask = null; executingTaskQueue.nextExecutor = null; queuedExecutor.execute(queuedTask); } } finally { // Null out the thread field, so that we don't leak a reference to Thread, and so that // future `thread == currentThread()` calls from this thread don't incorrectly queue instead // of executing. Don't null out the latestTaskQueue field, because the work done here // may have scheduled more operations on another thread, and if those operations then // trigger reentrant calls that thread will have updated the latestTaskQueue field, and // we'd be interfering with their operation. executingTaskQueue.thread = null; } } private boolean trySetStarted() { return compareAndSet(NOT_RUN, STARTED); } private boolean trySetCancelled() { return compareAndSet(NOT_RUN, CANCELLED); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy