swim.concurrent.Theater Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of swim-concurrent Show documentation
Show all versions of swim-concurrent Show documentation
Uploads all artifacts belonging to configuration ':swim-concurrent:archives'
// Copyright 2015-2019 SWIM.AI inc.
//
// 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 swim.concurrent;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinWorkerThread;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
/**
* {@link Stage} that executes timers, tasks, and continuations on a {@code
* ForkJoinPool}.
*/
public class Theater implements Stage, Thread.UncaughtExceptionHandler {
/**
* Prefix for worker thread names.
*/
final String name;
/**
* Schedule used to set timers.
*/
final Schedule schedule;
/**
* Thread pool on which to execute timers, tasks, and continuations.
*/
final ForkJoinPool pool;
/**
* Atomic bit field with {@link #STARTED} and {@link #STOPPED} flags.
*/
volatile int status;
public Theater(String name, int parallelism, Schedule schedule) {
this.name = name != null ? name : "SwimStage" + THEATER_COUNT.getAndIncrement() + ".";
this.schedule = schedule != null ? schedule : new StageClock(this);
this.pool = new ForkJoinPool(parallelism, new TheaterWorkerFactory(this), this, true);
}
public Theater(String name, int parallelism) {
this(name, parallelism, null);
}
public Theater(String name, Schedule schedule) {
this(name, Runtime.getRuntime().availableProcessors(), schedule);
}
public Theater(String name) {
this(name, Runtime.getRuntime().availableProcessors(), null);
}
public Theater(int parallelism, Schedule schedule) {
this(null, parallelism, schedule);
}
public Theater(int parallelism) {
this(null, parallelism, null);
}
public Theater(Schedule schedule) {
this(null, Runtime.getRuntime().availableProcessors(), schedule);
}
public Theater() {
this(null, Runtime.getRuntime().availableProcessors(), null);
}
/**
* Ensures that this {@code Theater} is up and running.
*
* @throws IllegalStateException if this {@code Theater} has been stopped.
*/
public void start() {
int oldStatus;
int newStatus;
do {
oldStatus = STATUS.get(this);
if ((oldStatus & STOPPED) == 0) {
newStatus = oldStatus | STARTED;
} else {
newStatus = oldStatus;
}
} while (!STATUS.compareAndSet(this, oldStatus, newStatus));
if (oldStatus != newStatus) {
didStart();
} else if ((oldStatus & STOPPED) != 0) {
throw new IllegalStateException("Can't restart stopped theater");
}
}
/**
* Ensures that this {@code Theater} has been permanently stopped, shutting
* down the thread pool, if it's currently running. Upon return, this
* {@code Theater} is guaranteed to be in the stopped state.
*/
public void stop() {
int oldStatus;
int newStatus;
do {
oldStatus = STATUS.get(this);
newStatus = oldStatus | STOPPED;
} while (!STATUS.compareAndSet(this, oldStatus, newStatus));
if (oldStatus != newStatus) {
this.pool.shutdown();
boolean interrupted = false;
while (!this.pool.isTerminated()) {
try {
this.pool.awaitTermination(100, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
interrupted = true;
}
}
didStop();
if (interrupted) {
Thread.currentThread().interrupt();
}
}
}
@Override
public void execute(Runnable runnable) {
start();
this.pool.execute(runnable);
}
@Override
public TaskRef task(TaskFunction task) {
start();
final TheaterTask context = new TheaterTask(this, task);
if (task instanceof Task) {
((Task) task).setTaskContext(context);
}
return context;
}
@Override
public Call call(Cont cont) {
start();
return new TheaterCall(this, cont);
}
@Override
public TimerRef timer(TimerFunction timer) {
start();
return this.schedule.timer(timer);
}
@Override
public TimerRef setTimer(long millis, TimerFunction timer) {
start();
return this.schedule.setTimer(millis, timer);
}
/**
* Lifecycle callback invoked before the thread pool starts up.
*/
protected void didStart() {
// stub
}
/**
* Lifecycle callback invoked after the thread pool shuts down.
*/
protected void didStop() {
// stub
}
/**
* Lifecycle callback invoked if this {@code Theater} encounters
* an internal {@code error}.
*/
protected void didFail(Throwable error) {
error.printStackTrace();
}
/**
* Introspection callback invoked before a {@code task} is cued for execution.
*/
protected void taskWillCue(TaskFunction task) {
// stub
}
/**
* Introspection callback invoked after a cued {@code task} is canceled.
*/
protected void taskDidCancel(TaskFunction task) {
// stub
}
/**
* Introspection callback invoked immediately before {@code task.runTask()}
* is executed.
*/
protected void taskWillRun(TaskFunction task) {
// stub
}
/**
* Introspection callback invoked immediately after {@code task.runTask()}
* returns nominally.
*/
protected void taskDidRun(TaskFunction task) {
// stub
}
/**
* Introspection callback invoked immediately after {@code task.runTask()}
* fails by throwing an {@code error}.
*/
protected void taskDidFail(TaskFunction task, Throwable error) {
// stub
}
/**
* Introspection callback invoked before a {@code cont} call is cued for
* execution.
*/
protected void callWillCue(Cont> cont) {
// stub
}
/**
* Introspection callback invoked immediately before a call to {@code
* cont.bind(value)}.
*/
protected void callWillBind(Cont cont, T value) {
// stub
}
/**
* Introspection callback invoked immediately after a call to {@code
* cont.bind(value)} returns nominally.
*/
protected void callDidBind(Cont> cont, T value) {
// stub
}
/**
* Introspection callback invoked immediately before a call to {@code
* cont.trap(error)}.
*/
protected void callWillTrap(Cont> cont, Throwable error) {
// stub
}
/**
* Introspection callback invoked immediately after a call to {@code
* cont.trap(error)} returns nominally.
*/
protected void callDidTrap(Cont> cont, Throwable error) {
// stub
}
/**
* Introspection callback invoked immediately after a call to {@code
* cont.bind(T)} fails by throwing an {@code error}.
*/
protected void callDidFail(Cont> cont, Throwable error) {
// stub
}
@Override
public void uncaughtException(Thread thead, Throwable error) {
didFail(error);
}
/**
* Atomic {@link #status} bit flag indicating that the theater has started,
* and is currently running.
*/
static final int STARTED = 1 << 0;
/**
* Atomic {@link #status} bit flag indicating that the theater had previously
* started, but is now permanently stopped.
*/
static final int STOPPED = 1 << 1;
/**
* Total number of theaters that have ever been instantiated. Used to
* uniquely name theater threads.
*/
static final AtomicInteger THEATER_COUNT = new AtomicInteger(0);
/**
* Atomic {@link #status} field updater, used to linearize theater startup
* and shutdown.
*/
static final AtomicIntegerFieldUpdater STATUS =
AtomicIntegerFieldUpdater.newUpdater(Theater.class, "status");
}
/**
* {@code TaskContext} that executes its sequential process on a {@code
* Theater} stage.
*/
class TheaterTask implements TaskContext, Runnable, ForkJoinPool.ManagedBlocker {
/**
* {@code Theater} to which the {@code task} is bound.
*/
final Theater theater;
/**
* {@code TaskFunction} to invoke when the cued task executes.
*/
final TaskFunction task;
/**
* Atomic bit field with {@link #CUED} and {@link #RUNNING} flags.
*/
volatile int status;
TheaterTask(Theater theater, TaskFunction task) {
this.theater = theater;
this.task = task;
}
@Override
public Stage stage() {
return this.theater;
}
@Override
public boolean isCued() {
return (this.status & CUED) != 0;
}
@Override
public boolean isReleasable() {
return (this.status & RUNNING) == 0;
}
@Override
public boolean cue() {
int oldStatus;
int newStatus;
do {
oldStatus = this.status;
if ((oldStatus & CUED) == 0) {
newStatus = oldStatus | CUED;
} else {
newStatus = oldStatus;
}
} while (!STATUS.compareAndSet(this, oldStatus, newStatus));
if (oldStatus != newStatus && (newStatus & RUNNING) == 0) {
this.theater.taskWillCue(this.task);
if (this.task instanceof Task) {
((Task) this.task).taskWillCue();
}
this.theater.execute(this);
return true;
} else {
return false;
}
}
@Override
public boolean cancel() {
int oldStatus;
int newStatus;
do {
oldStatus = this.status;
newStatus = oldStatus & ~CUED;
} while (!STATUS.compareAndSet(this, oldStatus, newStatus));
if (oldStatus != newStatus && (newStatus & RUNNING) == 0) {
if (this.task instanceof Task) {
((Task) this.task).taskDidCancel();
}
this.theater.taskDidCancel(this.task);
return true;
} else {
return false;
}
}
@Override
public void run() {
int oldStatus;
int newStatus;
do {
oldStatus = this.status;
newStatus = (oldStatus | RUNNING) & ~CUED;
} while (!STATUS.compareAndSet(this, oldStatus, newStatus));
if ((oldStatus & CUED) != 0) {
this.theater.taskWillRun(this.task);
try {
if (this.task instanceof Task && ((Task) this.task).taskWillBlock()) {
ForkJoinPool.managedBlock(this);
} else {
this.task.runTask();
}
this.theater.taskDidRun(this.task);
} catch (Throwable error) {
this.theater.taskDidFail(this.task, error);
}
}
do {
oldStatus = this.status;
newStatus = oldStatus & ~RUNNING;
} while (!STATUS.compareAndSet(this, oldStatus, newStatus));
if ((newStatus & CUED) != 0) {
this.theater.taskWillCue(this.task);
if (this.task instanceof Task) {
((Task) this.task).taskWillCue();
}
this.theater.execute(this);
}
}
@Override
public boolean block() {
this.task.runTask();
return true;
}
/**
* Atomic {@link #status} bit flag indicating that the task is currently
* cued for execution.
*/
static final int CUED = 1 << 0;
/**
* Atomic {@link #status} bit flag indicating that the task is currently
* executing.
*/
static final int RUNNING = 1 << 1;
/**
* Atomic {@link #status} field updater, used to linearize task cueing.
*/
static final AtomicIntegerFieldUpdater STATUS =
AtomicIntegerFieldUpdater.newUpdater(TheaterTask.class, "status");
}
/**
* {@code Call} that executes its {@code Cont}inuation on a {@code Theater}
* stage.
*/
final class TheaterCall implements Call, Runnable {
/**
* {@code Theater} stage on which the {@code cont}inuation executes.
*/
final Theater theater;
/**
* {@code Cont}inuation to invoke when the call is completed.
*/
final Cont cont;
/**
* Completed result of this call; either the bound value of type {@code T},
* or the trapped error of type {@code Throwable}. The type of {@code result}
* is decided by the flags of the {@code status} field.
*/
volatile Object result;
/**
* Atomic bit field with {@link #BIND}, {@link #TRAP}, {@link #CUED}, and
* {@link #DONE} flags.
*/
volatile int status;
TheaterCall(Theater theater, Cont cont) {
this.theater = theater;
this.cont = cont;
}
@Override
public Cont cont() {
return this.cont;
}
@Override
public void bind(T value) {
if (!tryBind(value)) {
throw new ContException("continuation already completed");
}
}
@Override
public void trap(Throwable error) {
if (!tryTrap(error)) {
throw new ContException("continuation already completed");
}
}
@Override
public boolean tryBind(T value) {
int oldStatus;
int newStatus;
do {
oldStatus = this.status;
if (oldStatus == 0) {
newStatus = oldStatus | BIND | CUED;
} else {
newStatus = oldStatus;
}
} while (!STATUS.compareAndSet(this, oldStatus, newStatus));
if (oldStatus != newStatus) {
this.result = value;
this.theater.callWillCue(this.cont);
this.theater.execute(this);
return true;
} else {
return false;
}
}
@Override
public boolean tryTrap(Throwable error) {
int oldStatus;
int newStatus;
do {
oldStatus = this.status;
if (oldStatus == 0) {
newStatus = oldStatus | TRAP | CUED;
} else {
newStatus = oldStatus;
}
} while (!STATUS.compareAndSet(this, oldStatus, newStatus));
if (oldStatus != newStatus) {
this.result = error;
this.theater.callWillCue(this.cont);
this.theater.execute(this);
return true;
} else {
return false;
}
}
@SuppressWarnings("unchecked")
@Override
public void run() {
int oldStatus;
int newStatus;
do {
oldStatus = this.status;
if ((oldStatus & CUED) != 0) {
newStatus = oldStatus & ~CUED;
} else {
newStatus = oldStatus;
}
} while (!STATUS.compareAndSet(this, oldStatus, newStatus));
if (oldStatus != newStatus) {
if ((newStatus & BIND) != 0) {
final T result = (T) this.result;
this.theater.callWillBind(this.cont, result);
try {
this.cont.bind(result);
this.theater.callDidBind(this.cont, result);
} catch (Throwable error) {
this.theater.callDidFail(this.cont, error);
this.cont.trap(error);
}
} else if ((newStatus & TRAP) != 0) {
final Throwable result = (Throwable) this.result;
this.theater.callWillTrap(this.cont, result);
try {
this.cont.trap(result);
this.theater.callDidTrap(this.cont, result);
} catch (Throwable error) {
this.theater.callDidFail(this.cont, error);
}
}
}
do {
oldStatus = this.status;
newStatus = oldStatus | DONE;
} while (!STATUS.compareAndSet(this, oldStatus, newStatus));
}
/**
* Atomic {@link #status} bit flag indicating that the call has completed
* with a value.
*/
static final int BIND = 1 << 0;
/**
* Atomic {@link #status} bit flag indicating that the call has failed with
* an error.
*/
static final int TRAP = 1 << 1;
/**
* Atomic {@link #status} bit flag indicating that the continuation has been
* cued for execution.
*/
static final int CUED = 1 << 2;
/**
* Atomic {@link #status} bit flag indicating that the continuation has
* finished executing, completing the continuation.
*/
static final int DONE = 1 << 3;
/**
* Atomic {@link #status} field updater, used to linearize cont completion.
*/
@SuppressWarnings("unchecked")
static final AtomicIntegerFieldUpdater> STATUS =
AtomicIntegerFieldUpdater.newUpdater((Class>) (Class>) TheaterCall.class, "status");
}
/**
* Factory for {@code TheaterWorker} threads.
*/
final class TheaterWorkerFactory implements ForkJoinPool.ForkJoinWorkerThreadFactory {
/**
* {@code Theater} for which this factory instantiates workers.
*/
final Theater theater;
/**
* Total number of worker threads ever started by this factory.
*/
volatile int workerCount;
TheaterWorkerFactory(Theater theater) {
this.theater = theater;
}
@Override
public ForkJoinWorkerThread newThread(ForkJoinPool pool) {
return new TheaterWorker(pool, this.theater, WORKER_COUNT.getAndIncrement(this));
}
/**
* Atomic {@link #workerCount} field updater, used to count instantiated
* worker threads.
*/
static final AtomicIntegerFieldUpdater WORKER_COUNT =
AtomicIntegerFieldUpdater.newUpdater(TheaterWorkerFactory.class, "workerCount");
}
/**
* {@code Theater} worker thread.
*/
final class TheaterWorker extends ForkJoinWorkerThread {
TheaterWorker(ForkJoinPool pool, Theater theater, int workerId) {
super(pool);
setName(theater.name + workerId);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy