Optimize scheduler

This commit is contained in:
Sotr
2019-03-07 14:27:56 +08:00
parent eeced58274
commit 2b944e7af1
3 changed files with 623 additions and 2 deletions

View File

@@ -174,7 +174,7 @@ public final class CraftServer implements Server {
private final String bukkitVersion = Versioning.getBukkitVersion();
private final Logger logger = Logger.getLogger("Minecraft");
private final ServicesManager servicesManager = new SimpleServicesManager();
private final CraftScheduler scheduler = new CraftScheduler();
private final CraftScheduler scheduler = new org.bukkit.craftbukkit.scheduler.CraftSyncScheduler(); // Akarin
private final CraftCommandMap commandMap = new CraftCommandMap(this);
private final SimpleHelpMap helpMap = new SimpleHelpMap(this);
private final StandardMessenger messenger = new StandardMessenger();

View File

@@ -0,0 +1,621 @@
package org.bukkit.craftbukkit.scheduler;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.PriorityQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.logging.Level;
import co.aikar.timings.MinecraftTimings; // Paper
import com.destroystokyo.paper.event.server.ServerExceptionEvent;
import com.destroystokyo.paper.exception.ServerSchedulerException;
import org.apache.commons.lang.Validate;
import org.bukkit.plugin.IllegalPluginAccessException;
import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.scheduler.BukkitScheduler;
import org.bukkit.scheduler.BukkitTask;
import org.bukkit.scheduler.BukkitWorker;
/**
* The fundamental concepts for this implementation:
* <li>Main thread owns {@link #head} and {@link #currentTick}, but it may be read from any thread</li>
* <li>Main thread exclusively controls {@link #temp} and {@link #pending}.
* They are never to be accessed outside of the main thread; alternatives exist to prevent locking.</li>
* <li>{@link #head} to {@link #tail} act as a linked list/queue, with 1 consumer and infinite producers.
* Adding to the tail is atomic and very efficient; utility method is {@link #handle(CraftTask, long)} or {@link #addTask(CraftTask)}. </li>
* <li>Changing the period on a task is delicate.
* Any future task needs to notify waiting threads.
* Async tasks must be synchronized to make sure that any thread that's finishing will remove itself from {@link #runners}.
* Another utility method is provided for this, {@link #cancelTask(int)}</li>
* <li>{@link #runners} provides a moderately up-to-date view of active tasks.
* If the linked head to tail set is read, all remaining tasks that were active at the time execution started will be located in runners.</li>
* <li>Async tasks are responsible for removing themselves from runners</li>
* <li>Sync tasks are only to be removed from runners on the main thread when coupled with a removal from pending and temp.</li>
* <li>Most of the design in this scheduler relies on queuing special tasks to perform any data changes on the main thread.
* When executed from inside a synchronous method, the scheduler will be updated before next execution by virtue of the frequent {@link #parsePending()} calls.</li>
*/
public class CraftSyncScheduler extends CraftScheduler implements BukkitScheduler { // Akarin - fake extends, override all things
/**
* Counter for IDs. Order doesn't matter, only uniqueness.
*/
private int ids = 1; // Akarin - AtomicInteger -> int
/**
* Current head of linked-list. This reference is always stale, {@link CraftTask#next} is the live reference.
*/
private CraftTask head = new CraftTask(); // Akarin - remove volatile
/**
* Tail of a linked-list. AtomicReference only matters when adding to queue
*/
private CraftTask tail = new CraftTask(head); // Akarin - remove AtomicReference
/**
* Main thread logic only
*/
final PriorityQueue<CraftTask> pending = new PriorityQueue<CraftTask>(10, // Paper
new Comparator<CraftTask>() {
public int compare(final CraftTask o1, final CraftTask o2) {
int value = Long.compare(o1.getNextRun(), o2.getNextRun());
// If the tasks should run on the same tick they should be run FIFO
return value != 0 ? value : Integer.compare(o1.getTaskId(), o2.getTaskId());
}
});
/**
* Main thread logic only
*/
private final List<CraftTask> temp = new ArrayList<CraftTask>();
/**
* These are tasks that are currently active. It's provided for 'viewing' the current state.
*/
final java.util.HashMap<Integer, CraftTask> runners = new java.util.HashMap<Integer, CraftTask>(); // Paper // Akarin - ConcurrentHashMap -> HashMap
/**
* The sync task that is currently running on the main thread.
*/
private CraftTask currentTask = null; // Akarin - remove volatile
int currentTick = -1; // Paper // Akarin - remove volatile
//private final Executor executor = Executors.newCachedThreadPool(new ThreadFactoryBuilder().setNameFormat("Craft Scheduler Thread - %d").build()); // Paper - moved to AsyncScheduler
//private CraftAsyncDebugger debugHead = new CraftAsyncDebugger(-1, null, null) {@Override StringBuilder debugTo(StringBuilder string) {return string;}}; // Paper
//private CraftAsyncDebugger debugTail = debugHead; // Paper
private static final int RECENT_TICKS;
static {
RECENT_TICKS = 30;
}
// Paper start
private final CraftScheduler asyncScheduler = new CraftAsyncScheduler(); // Akarin
private final boolean isAsyncScheduler = false; // Akarin
public CraftSyncScheduler() {
this(false);
}
public CraftSyncScheduler(boolean isAsync) {
// Akarin start
assert !isAsync : "Unexpected Async Scheduler. Report this to Akarin";
//this.isAsyncScheduler = isAsync;
//if (isAsync) {
// this.asyncScheduler = new CraftAsyncScheduler();
//} else {
// this.asyncScheduler = new CraftAsyncScheduler();
//}
// Akarin end
}
// Paper end
@Override
public int scheduleSyncDelayedTask(final Plugin plugin, final Runnable task) {
return this.scheduleSyncDelayedTask(plugin, task, 0L);
}
@Override
public BukkitTask runTask(Plugin plugin, Runnable runnable) {
return runTaskLater(plugin, runnable, 0L);
}
@Override
public void runTask(Plugin plugin, Consumer<BukkitTask> task) throws IllegalArgumentException {
runTaskLater(plugin, task, 0L);
}
@Deprecated
@Override
public int scheduleAsyncDelayedTask(final Plugin plugin, final Runnable task) {
return this.scheduleAsyncDelayedTask(plugin, task, 0L);
}
@Override
public BukkitTask runTaskAsynchronously(Plugin plugin, Runnable runnable) {
return runTaskLaterAsynchronously(plugin, runnable, 0L);
}
@Override
public void runTaskAsynchronously(Plugin plugin, Consumer<BukkitTask> task) throws IllegalArgumentException {
runTaskLaterAsynchronously(plugin, task, 0L);
}
@Override
public int scheduleSyncDelayedTask(final Plugin plugin, final Runnable task, final long delay) {
return this.scheduleSyncRepeatingTask(plugin, task, delay, CraftTask.NO_REPEATING);
}
@Override
public BukkitTask runTaskLater(Plugin plugin, Runnable runnable, long delay) {
return runTaskTimer(plugin, runnable, delay, CraftTask.NO_REPEATING);
}
@Override
public void runTaskLater(Plugin plugin, Consumer<BukkitTask> task, long delay) throws IllegalArgumentException {
runTaskTimer(plugin, task, delay, CraftTask.NO_REPEATING);
}
@Deprecated
@Override
public int scheduleAsyncDelayedTask(final Plugin plugin, final Runnable task, final long delay) {
return this.scheduleAsyncRepeatingTask(plugin, task, delay, CraftTask.NO_REPEATING);
}
@Override
public BukkitTask runTaskLaterAsynchronously(Plugin plugin, Runnable runnable, long delay) {
return runTaskTimerAsynchronously(plugin, runnable, delay, CraftTask.NO_REPEATING);
}
@Override
public void runTaskLaterAsynchronously(Plugin plugin, Consumer<BukkitTask> task, long delay) throws IllegalArgumentException {
runTaskTimerAsynchronously(plugin, task, delay, CraftTask.NO_REPEATING);
}
@Override
public void runTaskTimerAsynchronously(Plugin plugin, Consumer<BukkitTask> task, long delay, long period) throws IllegalArgumentException {
runTaskTimerAsynchronously(plugin, (Object) task, delay, CraftTask.NO_REPEATING);
}
@Override
public int scheduleSyncRepeatingTask(final Plugin plugin, final Runnable runnable, long delay, long period) {
return runTaskTimer(plugin, runnable, delay, period).getTaskId();
}
@Override
public BukkitTask runTaskTimer(Plugin plugin, Runnable runnable, long delay, long period) {
return runTaskTimer(plugin, (Object) runnable, delay, period);
}
@Override
public void runTaskTimer(Plugin plugin, Consumer<BukkitTask> task, long delay, long period) throws IllegalArgumentException {
runTaskTimer(plugin, (Object) task, delay, period);
}
public BukkitTask runTaskTimer(Plugin plugin, Object runnable, long delay, long period) {
validate(plugin, runnable);
if (delay < 0L) {
delay = 0;
}
if (period == CraftTask.ERROR) {
period = 1L;
} else if (period < CraftTask.NO_REPEATING) {
period = CraftTask.NO_REPEATING;
}
return handle(new CraftTask(plugin, runnable, nextId(), period), delay);
}
@Deprecated
@Override
public int scheduleAsyncRepeatingTask(final Plugin plugin, final Runnable runnable, long delay, long period) {
return runTaskTimerAsynchronously(plugin, runnable, delay, period).getTaskId();
}
@Override
public BukkitTask runTaskTimerAsynchronously(Plugin plugin, Runnable runnable, long delay, long period) {
return runTaskTimerAsynchronously(plugin, (Object) runnable, delay, period);
}
public BukkitTask runTaskTimerAsynchronously(Plugin plugin, Object runnable, long delay, long period) {
validate(plugin, runnable);
if (delay < 0L) {
delay = 0;
}
if (period == CraftTask.ERROR) {
period = 1L;
} else if (period < CraftTask.NO_REPEATING) {
period = CraftTask.NO_REPEATING;
}
return handle(new CraftAsyncTask(this.asyncScheduler.runners, plugin, runnable, nextId(), period), delay); // Paper
}
@Override
public <T> Future<T> callSyncMethod(final Plugin plugin, final Callable<T> task) {
validate(plugin, task);
final CraftFuture<T> future = new CraftFuture<T>(task, plugin, nextId());
handle(future, 0L);
return future;
}
@Override
public void cancelTask(final int taskId) {
if (taskId <= 0) {
return;
}
// Paper start
if (!this.isAsyncScheduler) {
this.asyncScheduler.cancelTask(taskId);
}
// Paper end
CraftTask task = runners.get(taskId);
if (task != null) {
task.cancel0();
}
task = new CraftTask(
new Runnable() {
public void run() {
if (!check(CraftSyncScheduler.this.temp)) {
check(CraftSyncScheduler.this.pending);
}
}
private boolean check(final Iterable<CraftTask> collection) {
final Iterator<CraftTask> tasks = collection.iterator();
while (tasks.hasNext()) {
final CraftTask task = tasks.next();
if (task.getTaskId() == taskId) {
task.cancel0();
tasks.remove();
if (task.isSync()) {
runners.remove(taskId);
}
return true;
}
}
return false;
}}){{this.timings=co.aikar.timings.MinecraftTimings.getCancelTasksTimer();}}; // Paper
handle(task, 0L);
for (CraftTask taskPending = head.getNext(); taskPending != null; taskPending = taskPending.getNext()) {
if (taskPending == task) {
return;
}
if (taskPending.getTaskId() == taskId) {
taskPending.cancel0();
}
}
}
@Override
public void cancelTasks(final Plugin plugin) {
Validate.notNull(plugin, "Cannot cancel tasks of null plugin");
// Paper start
if (!this.isAsyncScheduler) {
this.asyncScheduler.cancelTasks(plugin);
}
// Paper end
final CraftTask task = new CraftTask(
new Runnable() {
public void run() {
check(CraftSyncScheduler.this.pending);
check(CraftSyncScheduler.this.temp);
}
void check(final Iterable<CraftTask> collection) {
final Iterator<CraftTask> tasks = collection.iterator();
while (tasks.hasNext()) {
final CraftTask task = tasks.next();
if (task.getOwner().equals(plugin)) {
task.cancel0();
tasks.remove();
if (task.isSync()) {
runners.remove(task.getTaskId());
}
}
}
}
}){{this.timings=co.aikar.timings.MinecraftTimings.getCancelTasksTimer(plugin);}}; // Paper
handle(task, 0L);
for (CraftTask taskPending = head.getNext(); taskPending != null; taskPending = taskPending.getNext()) {
if (taskPending == task) {
break;
}
if (taskPending.getTaskId() != -1 && taskPending.getOwner().equals(plugin)) {
taskPending.cancel0();
}
}
for (CraftTask runner : runners.values()) {
if (runner.getOwner().equals(plugin)) {
runner.cancel0();
}
}
}
@Override
public boolean isCurrentlyRunning(final int taskId) {
// Paper start
if (!isAsyncScheduler) {
if (this.asyncScheduler.isCurrentlyRunning(taskId)) {
return true;
}
}
// Paper end
final CraftTask task = runners.get(taskId);
if (task == null) {
return false;
}
if (task.isSync()) {
return (task == currentTask);
}
final CraftAsyncTask asyncTask = (CraftAsyncTask) task;
synchronized (asyncTask.getWorkers()) {
return !asyncTask.getWorkers().isEmpty();
}
}
@Override
public boolean isQueued(final int taskId) {
if (taskId <= 0) {
return false;
}
// Paper start
if (!this.isAsyncScheduler && this.asyncScheduler.isQueued(taskId)) {
return true;
}
// Paper end
for (CraftTask task = head.getNext(); task != null; task = task.getNext()) {
if (task.getTaskId() == taskId) {
return task.getPeriod() >= CraftTask.NO_REPEATING; // The task will run
}
}
CraftTask task = runners.get(taskId);
return task != null && task.getPeriod() >= CraftTask.NO_REPEATING;
}
@Override
public List<BukkitWorker> getActiveWorkers() {
// Paper start
if (!isAsyncScheduler) {
//noinspection TailRecursion
return this.asyncScheduler.getActiveWorkers();
}
// Paper end
final ArrayList<BukkitWorker> workers = new ArrayList<BukkitWorker>();
for (final CraftTask taskObj : runners.values()) {
// Iterator will be a best-effort (may fail to grab very new values) if called from an async thread
if (taskObj.isSync()) {
continue;
}
final CraftAsyncTask task = (CraftAsyncTask) taskObj;
synchronized (task.getWorkers()) {
// This will never have an issue with stale threads; it's state-safe
workers.addAll(task.getWorkers());
}
}
return workers;
}
@Override
public List<BukkitTask> getPendingTasks() {
final ArrayList<CraftTask> truePending = new ArrayList<CraftTask>();
for (CraftTask task = head.getNext(); task != null; task = task.getNext()) {
if (task.getTaskId() != -1) {
// -1 is special code
truePending.add(task);
}
}
final ArrayList<BukkitTask> pending = new ArrayList<BukkitTask>();
for (CraftTask task : runners.values()) {
if (task.getPeriod() >= CraftTask.NO_REPEATING) {
pending.add(task);
}
}
for (final CraftTask task : truePending) {
if (task.getPeriod() >= CraftTask.NO_REPEATING && !pending.contains(task)) {
pending.add(task);
}
}
// Paper start
if (!this.isAsyncScheduler) {
pending.addAll(this.asyncScheduler.getPendingTasks());
}
// Paper end
return pending;
}
/**
* This method is designed to never block or wait for locks; an immediate execution of all current tasks.
*/
public void mainThreadHeartbeat(final int currentTick) {
// Paper start
if (!this.isAsyncScheduler) {
this.asyncScheduler.mainThreadHeartbeat(currentTick);
}
// Paper end
this.currentTick = currentTick;
final List<CraftTask> temp = this.temp;
parsePending();
while (isReady(currentTick)) {
final CraftTask task = pending.remove();
if (task.getPeriod() < CraftTask.NO_REPEATING) {
if (task.isSync()) {
runners.remove(task.getTaskId(), task);
}
parsePending();
continue;
}
if (task.isSync()) {
currentTask = task;
try {
task.run();
} catch (final Throwable throwable) {
// Paper start
String msg = String.format(
"Task #%s for %s generated an exception",
task.getTaskId(),
task.getOwner().getDescription().getFullName());
task.getOwner().getLogger().log(
Level.WARNING,
msg,
throwable);
task.getOwner().getServer().getPluginManager().callEvent(
new ServerExceptionEvent(new ServerSchedulerException(msg, throwable, task))
);
// Paper end
} finally {
currentTask = null;
}
parsePending();
} else {
//debugTail = debugTail.setNext(new CraftAsyncDebugger(currentTick + RECENT_TICKS, task.getOwner(), task.getTaskClass())); // Paper
task.getOwner().getLogger().log(Level.SEVERE, "Unexpected Async Task in the Sync Scheduler. Report this to Paper"); // Paper
// We don't need to parse pending
// (async tasks must live with race-conditions if they attempt to cancel between these few lines of code)
}
final long period = task.getPeriod(); // State consistency
if (period > 0) {
task.setNextRun(currentTick + period);
temp.add(task);
} else if (task.isSync()) {
runners.remove(task.getTaskId());
}
}
MinecraftTimings.bukkitSchedulerFinishTimer.startTiming();
pending.addAll(temp);
temp.clear();
MinecraftTimings.bukkitSchedulerFinishTimer.stopTiming();
//debugHead = debugHead.getNextHead(currentTick); // Paper
}
protected void addTask(final CraftTask task) {
// Akarin start
//final AtomicReference<CraftTask> tail = this.tail;
//CraftTask tailTask = tail.get();
//while (!tail.compareAndSet(tailTask, task)) {
// tailTask = tail.get();
//}
(tail = task).setNext(task);
// Akarin end
}
protected CraftTask handle(final CraftTask task, final long delay) { // Paper
// Paper start
if (!this.isAsyncScheduler && !task.isSync()) {
this.asyncScheduler.handle(task, delay);
return task;
}
// Paper end
task.setNextRun(currentTick + delay);
addTask(task);
return task;
}
private static void validate(final Plugin plugin, final Object task) {
Validate.notNull(plugin, "Plugin cannot be null");
Validate.notNull(task, "Task cannot be null");
Validate.isTrue(task instanceof Runnable || task instanceof Consumer || task instanceof Callable, "Task must be Runnable, Consumer, or Callable");
if (!plugin.isEnabled()) {
throw new IllegalPluginAccessException("Plugin attempted to register task while disabled");
}
}
private int nextId() {
return ids++; // Akarin
}
void parsePending() { // Paper
if (!this.isAsyncScheduler) MinecraftTimings.bukkitSchedulerPendingTimer.startTiming(); // Paper
CraftTask head = this.head;
CraftTask task = head.getNext();
CraftTask lastTask = head;
for (; task != null; task = (lastTask = task).getNext()) {
if (task.getTaskId() == -1) {
task.run();
} else if (task.getPeriod() >= CraftTask.NO_REPEATING) {
pending.add(task);
runners.put(task.getTaskId(), task);
}
}
// We split this because of the way things are ordered for all of the async calls in CraftScheduler
// (it prevents race-conditions)
for (task = head; task != lastTask; task = head) {
head = task.getNext();
task.setNext(null);
}
this.head = lastTask;
if (!this.isAsyncScheduler) MinecraftTimings.bukkitSchedulerPendingTimer.stopTiming(); // Paper
}
private boolean isReady(final int currentTick) {
return !pending.isEmpty() && pending.peek().getNextRun() <= currentTick;
}
@Override
public String toString() {
// Paper start
return "";
/*
int debugTick = currentTick;
StringBuilder string = new StringBuilder("Recent tasks from ").append(debugTick - RECENT_TICKS).append('-').append(debugTick).append('{');
debugHead.debugTo(string);
return string.append('}').toString();
*/
// Paper end
}
@Deprecated
@Override
public int scheduleSyncDelayedTask(Plugin plugin, BukkitRunnable task, long delay) {
return scheduleSyncDelayedTask(plugin, (Runnable) task, delay);
}
@Deprecated
@Override
public int scheduleSyncDelayedTask(Plugin plugin, BukkitRunnable task) {
return scheduleSyncDelayedTask(plugin, (Runnable) task);
}
@Deprecated
@Override
public int scheduleSyncRepeatingTask(Plugin plugin, BukkitRunnable task, long delay, long period) {
return scheduleSyncRepeatingTask(plugin, (Runnable) task, delay, period);
}
@Deprecated
@Override
public BukkitTask runTask(Plugin plugin, BukkitRunnable task) throws IllegalArgumentException {
return runTask(plugin, (Runnable) task);
}
@Deprecated
@Override
public BukkitTask runTaskAsynchronously(Plugin plugin, BukkitRunnable task) throws IllegalArgumentException {
return runTaskAsynchronously(plugin, (Runnable) task);
}
@Deprecated
@Override
public BukkitTask runTaskLater(Plugin plugin, BukkitRunnable task, long delay) throws IllegalArgumentException {
return runTaskLater(plugin, (Runnable) task, delay);
}
@Deprecated
@Override
public BukkitTask runTaskLaterAsynchronously(Plugin plugin, BukkitRunnable task, long delay) throws IllegalArgumentException {
return runTaskLaterAsynchronously(plugin, (Runnable) task, delay);
}
@Deprecated
@Override
public BukkitTask runTaskTimer(Plugin plugin, BukkitRunnable task, long delay, long period) throws IllegalArgumentException {
return runTaskTimer(plugin, (Runnable) task, delay, period);
}
@Deprecated
@Override
public BukkitTask runTaskTimerAsynchronously(Plugin plugin, BukkitRunnable task, long delay, long period) throws IllegalArgumentException {
return runTaskTimerAsynchronously(plugin, (Runnable) task, delay, period);
}
}