From 27d58a299c38613b9aeed403c35166c31f2d6025 Mon Sep 17 00:00:00 2001 From: Wesley Wolfe Date: Sun, 9 Sep 2012 00:34:41 -0500 Subject: [PATCH] Add information about async tasks to CrashReports. Addresses BUKKIT-2491 Async tasks are notorious for causing CMEs and corrupted data when accessing the API. This change makes a linked list to track recent tasks that may no longer be running. It is accessed via the toString method on the scheduler. This behavior is not guaranteed, but it is accessible as such currently. Although toString is located in the scheduler, its contract does not guarantee an accurate or up to date call when accessed from a second thread. --- .../bukkit/craftbukkit/CraftCrashReport.java | 2 +- .../scheduler/CraftAsyncDebugger.java | 37 +++++++++++++++++++ .../craftbukkit/scheduler/CraftScheduler.java | 17 +++++++++ .../craftbukkit/scheduler/CraftTask.java | 4 ++ 4 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncDebugger.java diff --git a/src/main/java/org/bukkit/craftbukkit/CraftCrashReport.java b/src/main/java/org/bukkit/craftbukkit/CraftCrashReport.java index 72938bbb5b..cb6dc22969 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftCrashReport.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftCrashReport.java @@ -28,7 +28,7 @@ public class CraftCrashReport implements Callable { for (Map.Entry entry : Thread.getAllStackTraces().entrySet()) { value.append(' ').append(entry.getKey().getState().name()).append(' ').append(entry.getKey().getName()).append(": ").append(Arrays.toString(entry.getValue())).append(','); } - value.append('}'); + value.append("}\n ").append(Bukkit.getScheduler().toString()); } catch (Throwable t) { value.append("\n Failed to handle CraftCrashReport:\n"); PrintWriter writer = new PrintWriter(value); diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncDebugger.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncDebugger.java new file mode 100644 index 0000000000..2899bacf57 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncDebugger.java @@ -0,0 +1,37 @@ +package org.bukkit.craftbukkit.scheduler; + +import org.bukkit.plugin.Plugin; + + +class CraftAsyncDebugger { + private CraftAsyncDebugger next = null; + private final int expiry; + private final Plugin plugin; + private final Class clazz; + + CraftAsyncDebugger(final int expiry, final Plugin plugin, final Class clazz) { + this.expiry = expiry; + this.plugin = plugin; + this.clazz = clazz; + + } + + final CraftAsyncDebugger getNextHead(final int time) { + CraftAsyncDebugger next, current = this; + while (time > current.expiry && (next = current.next) != null) { + current = next; + } + return current; + } + + final CraftAsyncDebugger setNext(final CraftAsyncDebugger next) { + return this.next = next; + } + + StringBuilder debugTo(final StringBuilder string) { + for (CraftAsyncDebugger next = this; next != null; next = next.next) { + string.append(plugin.getDescription().getName()).append(':').append(clazz.getName()).append('@').append(expiry).append(','); + } + return string; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java index 6b70a78bc1..f2dbfbadd6 100644 --- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java +++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java @@ -71,6 +71,13 @@ public class CraftScheduler implements BukkitScheduler { private final ConcurrentHashMap runners = new ConcurrentHashMap(); private volatile int currentTick = -1; private final Executor executor = Executors.newCachedThreadPool(); + private CraftAsyncDebugger debugHead = new CraftAsyncDebugger(-1, null, null) {@Override StringBuilder debugTo(StringBuilder string) {return string;}}; + private CraftAsyncDebugger debugTail = debugHead; + private static final int RECENT_TICKS; + + static { + RECENT_TICKS = 30; + } public int scheduleSyncDelayedTask(final Plugin plugin, final Runnable task) { return this.scheduleSyncDelayedTask(plugin, task, 0l); @@ -325,6 +332,7 @@ public class CraftScheduler implements BukkitScheduler { } parsePending(); } else { + debugTail = debugTail.setNext(new CraftAsyncDebugger(currentTick + RECENT_TICKS, task.getOwner(), task.getTaskClass())); executor.execute(task); // 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) @@ -339,6 +347,7 @@ public class CraftScheduler implements BukkitScheduler { } pending.addAll(temp); temp.clear(); + debugHead = debugHead.getNextHead(currentTick); } private void addTask(final CraftTask task) { @@ -418,4 +427,12 @@ public class CraftScheduler implements BukkitScheduler { } return true; } + + @Override + public String toString() { + 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(); + } } diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java index 8e56766d2a..cfd6fb45b9 100644 --- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java +++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftTask.java @@ -75,4 +75,8 @@ class CraftTask implements BukkitTask, Runnable { void setNext(CraftTask next) { this.next = next; } + + Class getTaskClass() { + return task.getClass(); + } }