Rewrite scheduler. Fixes BUKKIT-1831, and BUKKIT-845

The new scheduler uses a non-blocking methodology. Combining volatile
references to make a linked reference chain, with the atomic reference
handling the tail, tasks are queued without waiting for locks. The main
thread will no longer limit the length of time spend for scheduled tasks,
but no task will run twice in the same tick. Scheduling a new task inside of
a synchronous task will always run the new task during the same tick,
assuming there is no supplied delay > 0.

Asynchronous tasks are now run using a thread pool. Any thread-local
implemenation should now account for threads being reused between
executions.

Race conditions were carefully examined and the order of logic is now very
important. Each task is placed in a secondary collection before removal from
primary collections. Thus, by reading tasks from the collections in the same
order they travel, it retains state-safety. This does make modifications
less responsive in some situations, as the task may be transitioning before
the modifier accesses it. This cost outweighs the requirement to synchronize
on the scheduler; previously any conflict would be first-come-first-serve,
with the main thread backing out arbitrarily.
This commit is contained in:
Wesley Wolfe
2012-08-14 06:09:51 -05:00
committed by feildmaster
parent 8fdb006143
commit dcd01bf0c0
7 changed files with 563 additions and 648 deletions

View File

@@ -7,100 +7,88 @@ import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class CraftFuture<T> implements Runnable, Future<T> {
import org.bukkit.plugin.Plugin;
class CraftFuture<T> extends CraftTask implements Future<T> {
private final CraftScheduler craftScheduler;
private final Callable<T> callable;
private final ObjectContainer<T> returnStore = new ObjectContainer<T>();
private boolean done = false;
private boolean running = false;
private boolean cancelled = false;
private Exception e = null;
private int taskId = -1;
private T value;
private Exception exception = null;
CraftFuture(CraftScheduler craftScheduler, Callable<T> callable) {
CraftFuture(final Callable<T> callable, final Plugin plugin, final int id) {
super(plugin, null, id, -1l);
this.callable = callable;
this.craftScheduler = craftScheduler;
}
public void run() {
synchronized (this) {
if (cancelled) {
return;
}
running = true;
}
try {
returnStore.setObject(callable.call());
} catch (Exception e) {
this.e = e;
}
synchronized (this) {
running = false;
done = true;
this.notify();
}
}
public T get() throws InterruptedException, ExecutionException {
try {
return get(0L, TimeUnit.MILLISECONDS);
} catch (TimeoutException te) {}
return null;
}
public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
synchronized (this) {
if (isDone()) {
return getResult();
}
this.wait(TimeUnit.MILLISECONDS.convert(timeout, unit));
return getResult();
}
}
public T getResult() throws ExecutionException {
if (cancelled) {
throw new CancellationException();
}
if (e != null) {
throw new ExecutionException(e);
}
return returnStore.getObject();
}
public boolean isDone() {
synchronized (this) {
return done;
public synchronized boolean cancel(final boolean mayInterruptIfRunning) {
if (getPeriod() != -1l) {
return false;
}
setPeriod(-2l);
return true;
}
public boolean isCancelled() {
synchronized (this) {
return cancelled;
return getPeriod() == -2l;
}
public boolean isDone() {
final long period = this.getPeriod();
return period != -1l && period != -3l;
}
public T get() throws CancellationException, InterruptedException, ExecutionException {
try {
return get(0, TimeUnit.MILLISECONDS);
} catch (final TimeoutException e) {
throw new Error(e);
}
}
public boolean cancel(boolean mayInterruptIfRunning) {
synchronized (this) {
if (cancelled) {
return false;
public synchronized T get(long timeout, final TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
timeout = unit.toMillis(timeout);
long period = this.getPeriod();
while (true) {
if (period == -1l || period == -3l) {
this.wait(unit.toMillis(timeout));
period = this.getPeriod();
if (period == -1l || period == -3l) {
if (timeout == 0l) {
continue;
}
throw new TimeoutException();
}
}
cancelled = true;
if (taskId != -1) {
craftScheduler.cancelTask(taskId);
if (period == -2l) {
throw new CancellationException();
}
if (!running && !done) {
return true;
} else {
return false;
if (period == -4l) {
if (exception == null) {
return value;
}
throw new ExecutionException(exception);
}
throw new IllegalStateException("Expected " + -1l + " to " + -4l + ", got " + period);
}
}
public void setTaskId(int taskId) {
@Override
public void run() {
synchronized (this) {
this.taskId = taskId;
if (getPeriod() == -2l) {
return;
}
setPeriod(-3l);
}
try {
value = callable.call();
} catch (final Exception e) {
exception = e;
} finally {
synchronized (this) {
setPeriod(-4l);
this.notifyAll();
}
}
}
}