Improved the Scheduler.
Adds nag message when async tasks are not properly shut down and adds a limiter for sync tasks. Once they use 35ms in a single tick, any remaining tasks are not executed until later ticks. Adds a method to report the pending tasks and one to report active worker threads
Dieser Commit ist enthalten in:
Ursprung
aa70240917
Commit
fdb077e814
@ -21,6 +21,7 @@ import java.util.Map;
|
|||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
import java.lang.InterruptedException;
|
||||||
import jline.ConsoleReader;
|
import jline.ConsoleReader;
|
||||||
import net.minecraft.server.ChunkCoordinates;
|
import net.minecraft.server.ChunkCoordinates;
|
||||||
import net.minecraft.server.ConvertProgressUpdater;
|
import net.minecraft.server.ConvertProgressUpdater;
|
||||||
@ -45,6 +46,8 @@ import org.bukkit.craftbukkit.inventory.CraftFurnaceRecipe;
|
|||||||
import org.bukkit.craftbukkit.inventory.CraftRecipe;
|
import org.bukkit.craftbukkit.inventory.CraftRecipe;
|
||||||
import org.bukkit.craftbukkit.inventory.CraftShapedRecipe;
|
import org.bukkit.craftbukkit.inventory.CraftShapedRecipe;
|
||||||
import org.bukkit.craftbukkit.inventory.CraftShapelessRecipe;
|
import org.bukkit.craftbukkit.inventory.CraftShapelessRecipe;
|
||||||
|
import org.bukkit.scheduler.BukkitWorker;
|
||||||
|
import org.bukkit.scheduler.BukkitTask;
|
||||||
import org.bukkit.craftbukkit.scheduler.CraftScheduler;
|
import org.bukkit.craftbukkit.scheduler.CraftScheduler;
|
||||||
import org.bukkit.util.config.Configuration;
|
import org.bukkit.util.config.Configuration;
|
||||||
|
|
||||||
@ -292,6 +295,32 @@ public final class CraftServer implements Server {
|
|||||||
|
|
||||||
pluginManager.clearPlugins();
|
pluginManager.clearPlugins();
|
||||||
commandMap.clearCommands();
|
commandMap.clearCommands();
|
||||||
|
|
||||||
|
int pollCount = 0;
|
||||||
|
|
||||||
|
// Wait for at most 2.5 seconds for plugins to close their threads
|
||||||
|
while(pollCount < 50 && getScheduler().getActiveWorkers().size() > 0) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(50);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
}
|
||||||
|
pollCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<BukkitWorker> overdueWorkers = getScheduler().getActiveWorkers();
|
||||||
|
for(BukkitWorker worker : overdueWorkers) {
|
||||||
|
Plugin plugin = worker.getOwner();
|
||||||
|
String author = "<NoAuthorGiven>";
|
||||||
|
if (plugin.getDescription().getAuthors().size() > 0) {
|
||||||
|
author = plugin.getDescription().getAuthors().get(0);
|
||||||
|
}
|
||||||
|
getLogger().log(Level.SEVERE, String.format(
|
||||||
|
"Nag author: '%s' of '%s' about the following: %s",
|
||||||
|
author,
|
||||||
|
plugin.getDescription().getName(),
|
||||||
|
"This plugin is not properly shutting down its async tasks when it is being reloaded. This may cause conflicts with the newly loaded version of the plugin"
|
||||||
|
));
|
||||||
|
}
|
||||||
loadPlugins();
|
loadPlugins();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,8 @@ package org.bukkit.craftbukkit.scheduler;
|
|||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import java.util.concurrent.locks.Lock;
|
import java.util.concurrent.locks.Lock;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
@ -12,6 +14,9 @@ import java.util.logging.Level;
|
|||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import org.bukkit.scheduler.BukkitScheduler;
|
import org.bukkit.scheduler.BukkitScheduler;
|
||||||
|
import org.bukkit.scheduler.BukkitWorker;
|
||||||
|
import org.bukkit.scheduler.BukkitTask;
|
||||||
|
|
||||||
import org.bukkit.plugin.Plugin;
|
import org.bukkit.plugin.Plugin;
|
||||||
|
|
||||||
import org.bukkit.craftbukkit.CraftServer;
|
import org.bukkit.craftbukkit.CraftServer;
|
||||||
@ -35,6 +40,7 @@ public class CraftScheduler implements BukkitScheduler, Runnable {
|
|||||||
|
|
||||||
// This lock locks the mainThreadQueue and the currentTick value
|
// This lock locks the mainThreadQueue and the currentTick value
|
||||||
private final Lock mainThreadLock = new ReentrantLock();
|
private final Lock mainThreadLock = new ReentrantLock();
|
||||||
|
private final Lock syncedTasksLock = new ReentrantLock();
|
||||||
|
|
||||||
public void run() {
|
public void run() {
|
||||||
|
|
||||||
@ -113,6 +119,8 @@ public class CraftScheduler implements BukkitScheduler, Runnable {
|
|||||||
|
|
||||||
// If the main thread cannot obtain the lock, it doesn't wait
|
// If the main thread cannot obtain the lock, it doesn't wait
|
||||||
public void mainThreadHeartbeat(long currentTick) {
|
public void mainThreadHeartbeat(long currentTick) {
|
||||||
|
if (syncedTasksLock.tryLock()) {
|
||||||
|
try {
|
||||||
if (mainThreadLock.tryLock()) {
|
if (mainThreadLock.tryLock()) {
|
||||||
try {
|
try {
|
||||||
this.currentTick = currentTick;
|
this.currentTick = currentTick;
|
||||||
@ -122,20 +130,23 @@ public class CraftScheduler implements BukkitScheduler, Runnable {
|
|||||||
} finally {
|
} finally {
|
||||||
mainThreadLock.unlock();
|
mainThreadLock.unlock();
|
||||||
}
|
}
|
||||||
while(!syncedTasks.isEmpty()) {
|
}
|
||||||
|
long breakTime = System.currentTimeMillis() + 35; // max time spent in loop = 35ms
|
||||||
|
while (!syncedTasks.isEmpty() && System.currentTimeMillis() <= breakTime) {
|
||||||
CraftTask task = syncedTasks.removeFirst();
|
CraftTask task = syncedTasks.removeFirst();
|
||||||
try {
|
try {
|
||||||
task.getTask().run();
|
task.getTask().run();
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
// Bad plugin!
|
// Bad plugin!
|
||||||
logger.log(Level.WARNING,
|
logger.log(Level.WARNING, "Task of '" + task.getOwner().getDescription().getName() + "' generated an exception", t);
|
||||||
"Task of '" + task.getOwner().getDescription().getName()
|
|
||||||
+ "' generated an exception", t);
|
|
||||||
synchronized (schedulerQueue) {
|
synchronized (schedulerQueue) {
|
||||||
schedulerQueue.remove(task);
|
schedulerQueue.remove(task);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
syncedTasksLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,6 +170,24 @@ public class CraftScheduler implements BukkitScheduler, Runnable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void wipeSyncedTasks() {
|
||||||
|
syncedTasksLock.lock();
|
||||||
|
try {
|
||||||
|
syncedTasks.clear();
|
||||||
|
} finally {
|
||||||
|
syncedTasksLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void wipeMainThreadQueue() {
|
||||||
|
mainThreadLock.lock();
|
||||||
|
try {
|
||||||
|
mainThreadQueue.clear();
|
||||||
|
} finally {
|
||||||
|
mainThreadLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public int scheduleSyncDelayedTask(Plugin plugin, Runnable task, long delay) {
|
public int scheduleSyncDelayedTask(Plugin plugin, Runnable task, long delay) {
|
||||||
return scheduleSyncRepeatingTask(plugin, task, delay, -1);
|
return scheduleSyncRepeatingTask(plugin, task, delay, -1);
|
||||||
}
|
}
|
||||||
@ -168,9 +197,15 @@ public class CraftScheduler implements BukkitScheduler, Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int scheduleSyncRepeatingTask(Plugin plugin, Runnable task, long delay, long period) {
|
public int scheduleSyncRepeatingTask(Plugin plugin, Runnable task, long delay, long period) {
|
||||||
if (plugin == null) throw new IllegalArgumentException("Plugin can not null");
|
if (plugin == null) {
|
||||||
if (task == null) throw new IllegalArgumentException("Task can not null");
|
throw new IllegalArgumentException("Plugin cannot be null");
|
||||||
if (delay < 0) throw new IllegalArgumentException("Delay cannot be less than 0");
|
}
|
||||||
|
if (task == null) {
|
||||||
|
throw new IllegalArgumentException("Task cannot be null");
|
||||||
|
}
|
||||||
|
if (delay < 0) {
|
||||||
|
throw new IllegalArgumentException("Delay cannot be less than 0");
|
||||||
|
}
|
||||||
|
|
||||||
CraftTask newTask = new CraftTask(plugin, task, true, getCurrentTick()+delay, period);
|
CraftTask newTask = new CraftTask(plugin, task, true, getCurrentTick()+delay, period);
|
||||||
synchronized (schedulerQueue) {
|
synchronized (schedulerQueue) {
|
||||||
@ -189,9 +224,15 @@ public class CraftScheduler implements BukkitScheduler, Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int scheduleAsyncRepeatingTask(Plugin plugin, Runnable task, long delay, long period) {
|
public int scheduleAsyncRepeatingTask(Plugin plugin, Runnable task, long delay, long period) {
|
||||||
if (plugin == null) throw new IllegalArgumentException("Plugin can not null");
|
if (plugin == null) {
|
||||||
if (task == null) throw new IllegalArgumentException("Task can not null");
|
throw new IllegalArgumentException("Plugin cannot be null");
|
||||||
if (delay < 0) throw new IllegalArgumentException("Delay cannot be less than 0");
|
}
|
||||||
|
if (task == null) {
|
||||||
|
throw new IllegalArgumentException("Task cannot be null");
|
||||||
|
}
|
||||||
|
if (delay < 0) {
|
||||||
|
throw new IllegalArgumentException("Delay cannot be less than 0");
|
||||||
|
}
|
||||||
|
|
||||||
CraftTask newTask = new CraftTask(plugin, task, false, getCurrentTick()+delay, period);
|
CraftTask newTask = new CraftTask(plugin, task, false, getCurrentTick()+delay, period);
|
||||||
synchronized (schedulerQueue) {
|
synchronized (schedulerQueue) {
|
||||||
@ -203,7 +244,7 @@ public class CraftScheduler implements BukkitScheduler, Runnable {
|
|||||||
|
|
||||||
public <T> Future<T> callSyncMethod(Plugin plugin, Callable<T> task) {
|
public <T> Future<T> callSyncMethod(Plugin plugin, Callable<T> task) {
|
||||||
CraftFuture<T> craftFuture = new CraftFuture<T>(this, task);
|
CraftFuture<T> craftFuture = new CraftFuture<T>(this, task);
|
||||||
synchronized(craftFuture) {
|
synchronized (craftFuture) {
|
||||||
int taskId = scheduleSyncDelayedTask(plugin, craftFuture);
|
int taskId = scheduleSyncDelayedTask(plugin, craftFuture);
|
||||||
craftFuture.setTaskId(taskId);
|
craftFuture.setTaskId(taskId);
|
||||||
}
|
}
|
||||||
@ -224,7 +265,11 @@ public class CraftScheduler implements BukkitScheduler, Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void cancelTasks(Plugin plugin) {
|
public void cancelTasks(Plugin plugin) {
|
||||||
synchronized (schedulerQueue) {
|
syncedTasksLock.lock();
|
||||||
|
try {
|
||||||
|
synchronized(schedulerQueue) {
|
||||||
|
mainThreadLock.lock();
|
||||||
|
try {
|
||||||
Iterator<CraftTask> itr = schedulerQueue.keySet().iterator();
|
Iterator<CraftTask> itr = schedulerQueue.keySet().iterator();
|
||||||
while (itr.hasNext()) {
|
while (itr.hasNext()) {
|
||||||
CraftTask current = itr.next();
|
CraftTask current = itr.next();
|
||||||
@ -232,7 +277,28 @@ public class CraftScheduler implements BukkitScheduler, Runnable {
|
|||||||
itr.remove();
|
itr.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
itr = mainThreadQueue.iterator();
|
||||||
|
while (itr.hasNext()) {
|
||||||
|
CraftTask current = itr.next();
|
||||||
|
if (current.getOwner().equals(plugin)) {
|
||||||
|
itr.remove();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
itr = syncedTasks.iterator();
|
||||||
|
while (itr.hasNext()) {
|
||||||
|
CraftTask current = itr.next();
|
||||||
|
if (current.getOwner().equals(plugin)) {
|
||||||
|
itr.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
mainThreadLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
syncedTasksLock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
craftThreadManager.interruptTasks(plugin);
|
craftThreadManager.interruptTasks(plugin);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -240,6 +306,9 @@ public class CraftScheduler implements BukkitScheduler, Runnable {
|
|||||||
synchronized (schedulerQueue) {
|
synchronized (schedulerQueue) {
|
||||||
schedulerQueue.clear();
|
schedulerQueue.clear();
|
||||||
}
|
}
|
||||||
|
wipeMainThreadQueue();
|
||||||
|
wipeSyncedTasks();
|
||||||
|
|
||||||
craftThreadManager.interruptAllTasks();
|
craftThreadManager.interruptAllTasks();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,4 +329,40 @@ public class CraftScheduler implements BukkitScheduler, Runnable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<BukkitWorker> getActiveWorkers() {
|
||||||
|
synchronized (craftThreadManager.workers) {
|
||||||
|
List<BukkitWorker> workerList = new ArrayList<BukkitWorker>(craftThreadManager.workers.size());
|
||||||
|
Iterator<CraftWorker> itr = craftThreadManager.workers.iterator();
|
||||||
|
while (itr.hasNext()) {
|
||||||
|
workerList.add((BukkitWorker)itr.next());
|
||||||
|
}
|
||||||
|
return workerList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<BukkitTask> getPendingTasks() {
|
||||||
|
List<CraftTask> taskList = null;
|
||||||
|
syncedTasksLock.lock();
|
||||||
|
try {
|
||||||
|
synchronized (schedulerQueue) {
|
||||||
|
mainThreadLock.lock();
|
||||||
|
try {
|
||||||
|
taskList = new ArrayList<CraftTask>(mainThreadQueue.size() + syncedTasks.size() + schedulerQueue.size());
|
||||||
|
taskList.addAll(mainThreadQueue);
|
||||||
|
taskList.addAll(syncedTasks);
|
||||||
|
taskList.addAll(schedulerQueue.keySet());
|
||||||
|
} finally {
|
||||||
|
mainThreadLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
syncedTasksLock.unlock();
|
||||||
|
}
|
||||||
|
List<BukkitTask> newTaskList = new ArrayList<BukkitTask>(taskList.size());
|
||||||
|
for (CraftTask craftTask : taskList) {
|
||||||
|
newTaskList.add((BukkitTask)craftTask);
|
||||||
|
}
|
||||||
|
return newTaskList;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,9 @@ package org.bukkit.craftbukkit.scheduler;
|
|||||||
import java.lang.Comparable;
|
import java.lang.Comparable;
|
||||||
|
|
||||||
import org.bukkit.plugin.Plugin;
|
import org.bukkit.plugin.Plugin;
|
||||||
|
import org.bukkit.scheduler.BukkitTask;
|
||||||
|
|
||||||
public class CraftTask implements Comparable<Object> {
|
public class CraftTask implements Comparable<Object>, BukkitTask {
|
||||||
|
|
||||||
private final Runnable task;
|
private final Runnable task;
|
||||||
private final boolean syncTask;
|
private final boolean syncTask;
|
||||||
@ -44,7 +45,7 @@ public class CraftTask implements Comparable<Object> {
|
|||||||
return task;
|
return task;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isSync() {
|
public boolean isSync() {
|
||||||
return syncTask;
|
return syncTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,7 +57,7 @@ public class CraftTask implements Comparable<Object> {
|
|||||||
return period;
|
return period;
|
||||||
}
|
}
|
||||||
|
|
||||||
Plugin getOwner() {
|
public Plugin getOwner() {
|
||||||
return owner;
|
return owner;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,6 +65,10 @@ public class CraftTask implements Comparable<Object> {
|
|||||||
executionTick += period;
|
executionTick += period;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getTaskId() {
|
||||||
|
return getIdNumber();
|
||||||
|
}
|
||||||
|
|
||||||
int getIdNumber() {
|
int getIdNumber() {
|
||||||
return idNumber;
|
return idNumber;
|
||||||
}
|
}
|
||||||
@ -104,6 +109,4 @@ public class CraftTask implements Comparable<Object> {
|
|||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return getIdNumber();
|
return getIdNumber();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
package org.bukkit.craftbukkit.scheduler;
|
package org.bukkit.craftbukkit.scheduler;
|
||||||
|
|
||||||
import org.bukkit.plugin.Plugin;
|
import org.bukkit.plugin.Plugin;
|
||||||
|
import org.bukkit.scheduler.BukkitWorker;
|
||||||
|
|
||||||
public class CraftWorker implements Runnable {
|
public class CraftWorker implements Runnable, BukkitWorker {
|
||||||
|
|
||||||
private static int hashIdCounter = 1;
|
private static int hashIdCounter = 1;
|
||||||
private static Object hashIdCounterSync = new Object();
|
private static Object hashIdCounterSync = new Object();
|
||||||
@ -49,6 +50,10 @@ public class CraftWorker implements Runnable {
|
|||||||
return owner;
|
return owner;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Thread getThread() {
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
public void interrupt() {
|
public void interrupt() {
|
||||||
t.interrupt();
|
t.interrupt();
|
||||||
}
|
}
|
||||||
@ -70,7 +75,6 @@ public class CraftWorker implements Runnable {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals( Object other ) {
|
public boolean equals( Object other ) {
|
||||||
|
|
||||||
if (other == null) {
|
if (other == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
In neuem Issue referenzieren
Einen Benutzer sperren