package org.bukkit.craftbukkit.scheduler; import java.util.LinkedList; import java.util.TreeMap; import java.util.Iterator; import java.util.List; import java.util.ArrayList; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.Callable; import java.util.concurrent.Future; import java.util.logging.Level; import java.util.logging.Logger; import org.bukkit.scheduler.BukkitScheduler; import org.bukkit.scheduler.BukkitWorker; import org.bukkit.scheduler.BukkitTask; import org.bukkit.plugin.Plugin; import org.bukkit.craftbukkit.CraftServer; import org.bukkit.craftbukkit.scheduler.CraftTask; public class CraftScheduler implements BukkitScheduler, Runnable { private static final Logger logger = Logger.getLogger("Minecraft"); private final CraftServer server; private final CraftThreadManager craftThreadManager = new CraftThreadManager(); private final LinkedList mainThreadQueue = new LinkedList(); private final LinkedList syncedTasks = new LinkedList(); private final TreeMap schedulerQueue = new TreeMap(); private final Object currentTickSync = new Object(); private Long currentTick = 0L; // This lock locks the mainThreadQueue and the currentTick value private final Lock mainThreadLock = new ReentrantLock(); private final Lock syncedTasksLock = new ReentrantLock(); public void run() { while (true) { boolean stop = false; long firstTick = -1; long currentTick = -1; CraftTask first = null; do { synchronized (schedulerQueue) { first = null; if (!schedulerQueue.isEmpty()) { first = schedulerQueue.firstKey(); if (first != null) { currentTick = getCurrentTick(); firstTick = first.getExecutionTick(); if (currentTick >= firstTick) { schedulerQueue.remove(first); processTask(first); if (first.getPeriod() >= 0) { first.updateExecution(); schedulerQueue.put(first, first.isSync()); } } else { stop = true; } } else { stop = true; } } else { stop = true; } } } while (!stop); long sleepTime = 0; if (first == null) { sleepTime = 60000L; } else { currentTick = getCurrentTick(); sleepTime = (firstTick - currentTick) * 50 + 25; } if (sleepTime < 50L) { sleepTime = 50L; } else if (sleepTime > 60000L) { sleepTime = 60000L; } synchronized (schedulerQueue) { try { schedulerQueue.wait(sleepTime); } catch (InterruptedException ie) {} } } } void processTask(CraftTask task) { if (task.isSync()) { addToMainThreadQueue(task); } else { craftThreadManager.executeTask(task.getTask(), task.getOwner(), task.getIdNumber()); } } public CraftScheduler(CraftServer server) { this.server = server; Thread t = new Thread(this); t.start(); } // If the main thread cannot obtain the lock, it doesn't wait public void mainThreadHeartbeat(long currentTick) { if (syncedTasksLock.tryLock()) { try { if (mainThreadLock.tryLock()) { try { this.currentTick = currentTick; while (!mainThreadQueue.isEmpty()) { syncedTasks.addLast(mainThreadQueue.removeFirst()); } } finally { mainThreadLock.unlock(); } } long breakTime = System.currentTimeMillis() + 35; // max time spent in loop = 35ms while (!syncedTasks.isEmpty() && System.currentTimeMillis() <= breakTime) { CraftTask task = syncedTasks.removeFirst(); try { task.getTask().run(); } catch (Throwable t) { // Bad plugin! logger.log(Level.WARNING, "Task of '" + task.getOwner().getDescription().getName() + "' generated an exception", t); synchronized (schedulerQueue) { schedulerQueue.remove(task); } } } } finally { syncedTasksLock.unlock(); } } } long getCurrentTick() { mainThreadLock.lock(); long tempTick = 0; try { tempTick = currentTick; } finally { mainThreadLock.unlock(); } return tempTick; } void addToMainThreadQueue(CraftTask task) { mainThreadLock.lock(); try { mainThreadQueue.addLast(task); } finally { mainThreadLock.unlock(); } } 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) { return scheduleSyncRepeatingTask(plugin, task, delay, -1); } public int scheduleSyncDelayedTask(Plugin plugin, Runnable task) { return scheduleSyncDelayedTask(plugin, task, 0L); } public int scheduleSyncRepeatingTask(Plugin plugin, Runnable task, long delay, long period) { if (plugin == null) { throw new IllegalArgumentException("Plugin cannot be null"); } 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); synchronized (schedulerQueue) { schedulerQueue.put(newTask, true); schedulerQueue.notify(); } return newTask.getIdNumber(); } public int scheduleAsyncDelayedTask(Plugin plugin, Runnable task, long delay) { return scheduleAsyncRepeatingTask(plugin, task, delay, -1); } public int scheduleAsyncDelayedTask(Plugin plugin, Runnable task) { return scheduleAsyncDelayedTask(plugin, task, 0L); } public int scheduleAsyncRepeatingTask(Plugin plugin, Runnable task, long delay, long period) { if (plugin == null) { throw new IllegalArgumentException("Plugin cannot be null"); } 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); synchronized (schedulerQueue) { schedulerQueue.put(newTask, false); schedulerQueue.notify(); } return newTask.getIdNumber(); } public Future callSyncMethod(Plugin plugin, Callable task) { CraftFuture craftFuture = new CraftFuture(this, task); synchronized (craftFuture) { int taskId = scheduleSyncDelayedTask(plugin, craftFuture); craftFuture.setTaskId(taskId); } return craftFuture; } public void cancelTask(int taskId) { syncedTasksLock.lock(); try { synchronized (schedulerQueue) { mainThreadLock.lock(); try { Iterator itr = schedulerQueue.keySet().iterator(); while (itr.hasNext()) { CraftTask current = itr.next(); if (current.getIdNumber() == taskId) { itr.remove(); } } itr = mainThreadQueue.iterator(); while (itr.hasNext()) { CraftTask current = itr.next(); if (current.getIdNumber() == taskId) { itr.remove(); } } itr = syncedTasks.iterator(); while (itr.hasNext()) { CraftTask current = itr.next(); if (current.getIdNumber() == taskId) { itr.remove(); } } } finally { mainThreadLock.unlock(); } } } finally { syncedTasksLock.unlock(); } craftThreadManager.interruptTask(taskId); } public void cancelTasks(Plugin plugin) { syncedTasksLock.lock(); try { synchronized (schedulerQueue) { mainThreadLock.lock(); try { Iterator itr = schedulerQueue.keySet().iterator(); while (itr.hasNext()) { CraftTask current = itr.next(); if (current.getOwner().equals(plugin)) { 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); } public void cancelAllTasks() { synchronized (schedulerQueue) { schedulerQueue.clear(); } wipeMainThreadQueue(); wipeSyncedTasks(); craftThreadManager.interruptAllTasks(); } public boolean isCurrentlyRunning(int taskId) { return craftThreadManager.isAlive(taskId); } public boolean isQueued(int taskId) { synchronized (schedulerQueue) { Iterator itr = schedulerQueue.keySet().iterator(); while (itr.hasNext()) { CraftTask current = itr.next(); if (current.getIdNumber() == taskId) { return true; } } return false; } } public List getActiveWorkers() { synchronized (craftThreadManager.workers) { List workerList = new ArrayList(craftThreadManager.workers.size()); Iterator itr = craftThreadManager.workers.iterator(); while (itr.hasNext()) { workerList.add((BukkitWorker) itr.next()); } return workerList; } } public List getPendingTasks() { List taskList = null; syncedTasksLock.lock(); try { synchronized (schedulerQueue) { mainThreadLock.lock(); try { taskList = new ArrayList(mainThreadQueue.size() + syncedTasks.size() + schedulerQueue.size()); taskList.addAll(mainThreadQueue); taskList.addAll(syncedTasks); taskList.addAll(schedulerQueue.keySet()); } finally { mainThreadLock.unlock(); } } } finally { syncedTasksLock.unlock(); } List newTaskList = new ArrayList(taskList.size()); for (CraftTask craftTask : taskList) { newTaskList.add((BukkitTask) craftTask); } return newTaskList; } }