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.
Dieser Commit ist enthalten in:
Wesley Wolfe 2012-09-09 00:34:41 -05:00
Ursprung 4d5894e4cb
Commit 27d58a299c
4 geänderte Dateien mit 59 neuen und 1 gelöschten Zeilen

Datei anzeigen

@ -28,7 +28,7 @@ public class CraftCrashReport implements Callable {
for (Map.Entry<Thread, ? extends Object[]> entry : Thread.getAllStackTraces().entrySet()) { for (Map.Entry<Thread, ? extends Object[]> 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(' ').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) { } catch (Throwable t) {
value.append("\n Failed to handle CraftCrashReport:\n"); value.append("\n Failed to handle CraftCrashReport:\n");
PrintWriter writer = new PrintWriter(value); PrintWriter writer = new PrintWriter(value);

Datei anzeigen

@ -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<? extends Runnable> clazz;
CraftAsyncDebugger(final int expiry, final Plugin plugin, final Class<? extends Runnable> 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;
}
}

Datei anzeigen

@ -71,6 +71,13 @@ public class CraftScheduler implements BukkitScheduler {
private final ConcurrentHashMap<Integer, CraftTask> runners = new ConcurrentHashMap<Integer, CraftTask>(); private final ConcurrentHashMap<Integer, CraftTask> runners = new ConcurrentHashMap<Integer, CraftTask>();
private volatile int currentTick = -1; private volatile int currentTick = -1;
private final Executor executor = Executors.newCachedThreadPool(); 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) { public int scheduleSyncDelayedTask(final Plugin plugin, final Runnable task) {
return this.scheduleSyncDelayedTask(plugin, task, 0l); return this.scheduleSyncDelayedTask(plugin, task, 0l);
@ -325,6 +332,7 @@ public class CraftScheduler implements BukkitScheduler {
} }
parsePending(); parsePending();
} else { } else {
debugTail = debugTail.setNext(new CraftAsyncDebugger(currentTick + RECENT_TICKS, task.getOwner(), task.getTaskClass()));
executor.execute(task); executor.execute(task);
// We don't need to parse pending // 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) // (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); pending.addAll(temp);
temp.clear(); temp.clear();
debugHead = debugHead.getNextHead(currentTick);
} }
private void addTask(final CraftTask task) { private void addTask(final CraftTask task) {
@ -418,4 +427,12 @@ public class CraftScheduler implements BukkitScheduler {
} }
return true; 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();
}
} }

Datei anzeigen

@ -75,4 +75,8 @@ class CraftTask implements BukkitTask, Runnable {
void setNext(CraftTask next) { void setNext(CraftTask next) {
this.next = next; this.next = next;
} }
Class<? extends Runnable> getTaskClass() {
return task.getClass();
}
} }