graphql.execution.reactive.NonBlockingMutexExecutor Maven / Gradle / Ivy
package graphql.execution.reactive;
import graphql.Internal;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;
import static graphql.Assert.assertNotNull;
/**
* Executor that provides mutual exclusion between the operations submitted to it,
* without blocking.
*
* If an operation is submitted to this executor while no other operation is
* running, it will run immediately.
*
* If an operation is submitted to this executor while another operation is
* running, it will be added to a queue of operations to run, and the executor will
* return. The thread currently running an operation will end up running the
* operation just submitted.
*
* Operations submitted to this executor should run fast, as they can end up running
* on other threads and interfere with the operation of other threads.
*
* This executor can also be used to address infinite recursion problems, as
* operations submitted recursively will run sequentially.
*
*
* Inspired by Public Domain CC0 code at h
* https://github.com/jroper/reactive-streams-servlet/tree/master/reactive-streams-servlet/src/main/java/org/reactivestreams/servlet
*/
@Internal
class NonBlockingMutexExecutor implements Executor {
private final AtomicReference last = new AtomicReference<>();
@Override
public void execute(final Runnable command) {
final RunNode newNode = new RunNode(assertNotNull(command, () -> "Runnable must not be null"));
final RunNode prevLast = last.getAndSet(newNode);
if (prevLast != null)
prevLast.lazySet(newNode);
else
runAll(newNode);
}
private void reportFailure(final Thread runner, final Throwable thrown) {
if (thrown instanceof InterruptedException) {
runner.interrupt();
} else {
final Thread.UncaughtExceptionHandler ueh = runner.getUncaughtExceptionHandler();
if (ueh != null) {
ueh.uncaughtException(runner, thrown);
}
}
}
// Runs a single RunNode and deals with any thing it throws
private void run(final RunNode current) {
try {
current.runnable.run();
} catch (final Throwable thrown) {
reportFailure(Thread.currentThread(), thrown);
}
}
// Runs all the RunNodes starting with `next`
private void runAll(RunNode next) {
for (; ; ) {
final RunNode current = next;
run(current);
if ((next = current.get()) == null) { // try advance, if we get null test
if (last.compareAndSet(current, null)) {
return; // end-of-queue: we're done.
} else {
//noinspection StatementWithEmptyBody
while ((next = current.get()) == null) {
// Thread.onSpinWait(); in Java 9
}
}
}
}
}
private static final class RunNode extends AtomicReference {
private final Runnable runnable;
private RunNode(final Runnable runnable) {
this.runnable = runnable;
}
}
}