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:
committed by
feildmaster
parent
8fdb006143
commit
dcd01bf0c0
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user