From f7687b0c2028ed4a8b0964b695f14e2a9921122b Mon Sep 17 00:00:00 2001 From: md_5 Date: Sat, 23 Feb 2013 12:37:58 +1100 Subject: [PATCH] [[Experimental]] Rewrite Watchdog thread / crash detector. --- .../0030-Watchdog-Thread.patch | 272 ++++++++++++++++++ 1 file changed, 272 insertions(+) create mode 100644 CraftBukkit-Patches/0030-Watchdog-Thread.patch diff --git a/CraftBukkit-Patches/0030-Watchdog-Thread.patch b/CraftBukkit-Patches/0030-Watchdog-Thread.patch new file mode 100644 index 000000000..8f3dcfec4 --- /dev/null +++ b/CraftBukkit-Patches/0030-Watchdog-Thread.patch @@ -0,0 +1,272 @@ +From 6c8c769a97f22a7d9d5f8fa7692ae087dd717497 Mon Sep 17 00:00:00 2001 +From: md_5 +Date: Sat, 23 Feb 2013 12:33:20 +1100 +Subject: [PATCH] Watchdog Thread. + +--- + .../java/net/minecraft/server/MinecraftServer.java | 2 + + src/main/java/org/bukkit/craftbukkit/Spigot.java | 64 ++++++++++++++- + src/main/java/org/spigotmc/RestartCommand.java | 23 ++++++ + src/main/java/org/spigotmc/WatchdogThread.java | 93 ++++++++++++++++++++++ + 4 files changed, 180 insertions(+), 2 deletions(-) + create mode 100644 src/main/java/org/spigotmc/RestartCommand.java + create mode 100644 src/main/java/org/spigotmc/WatchdogThread.java + +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 128016f..3a6b620 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -418,6 +418,7 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IMo + lastTick = curTime; + MinecraftServer.currentTick++; + this.q(); ++ org.spigotmc.WatchdogThread.tick(); + } + // Spigot end + } else { +@@ -445,6 +446,7 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IMo + this.a(crashreport); + } finally { + try { ++ org.spigotmc.WatchdogThread.doStop(); + this.stop(); + this.isStopped = true; + } catch (Throwable throwable1) { +diff --git a/src/main/java/org/bukkit/craftbukkit/Spigot.java b/src/main/java/org/bukkit/craftbukkit/Spigot.java +index 5729cd6..6a4cbac 100644 +--- a/src/main/java/org/bukkit/craftbukkit/Spigot.java ++++ b/src/main/java/org/bukkit/craftbukkit/Spigot.java +@@ -1,5 +1,6 @@ + package org.bukkit.craftbukkit; + ++import java.io.File; + import java.io.IOException; + import java.util.ArrayList; + import net.minecraft.server.*; +@@ -7,16 +8,20 @@ import org.bukkit.command.SimpleCommandMap; + import org.bukkit.configuration.file.YamlConfiguration; + import java.util.List; + import java.util.logging.Level; +-import java.util.logging.Logger; + import org.bukkit.Bukkit; ++import org.bukkit.entity.Player; + import org.spigotmc.Metrics; ++import org.spigotmc.RestartCommand; ++import org.spigotmc.WatchdogThread; + + public class Spigot { ++ + public static boolean tabPing = false; + private static Metrics metrics; + + public static void initialize(CraftServer server, SimpleCommandMap commandMap, YamlConfiguration configuration) { + commandMap.register("bukkit", new org.bukkit.craftbukkit.command.TicksPerSecondCommand("tps")); ++ commandMap.register("restart", new RestartCommand("restart")); + + server.whitelistMessage = configuration.getString("settings.whitelist-message", server.whitelistMessage); + server.stopMessage = configuration.getString("settings.stop-message", server.stopMessage); +@@ -26,12 +31,21 @@ public class Spigot { + server.spamGuardExclusions = configuration.getStringList("settings.spam-exclusions"); + server.mapSendInterval = configuration.getInt("settings.map-send-interval", server.mapSendInterval); + ++ int configVersion = configuration.getInt("config-version"); ++ switch (configVersion) { ++ case 0: ++ configuration.set("settings.timeout-time", 30); ++ } ++ configuration.set("config-version", 1); ++ ++ WatchdogThread.doStart(configuration.getInt("settings.timeout-time", 30), configuration.getBoolean("settings.restart-on-crash", false)); ++ + server.orebfuscatorEnabled = configuration.getBoolean("orebfuscator.enable", false); + server.orebfuscatorEngineMode = configuration.getInt("orebfuscator.engine-mode", 1); + server.orebfuscatorUpdateRadius = configuration.getInt("orebfuscator.update-radius", 2); + server.orebfuscatorDisabledWorlds = configuration.getStringList("orebfuscator.disabled-worlds"); + if (server.orebfuscatorEngineMode != 1 && server.orebfuscatorEngineMode != 2) { +- server.orebfuscatorEngineMode = 1; ++ server.orebfuscatorEngineMode = 1; + } + + if (server.chunkGCPeriod == 0) { +@@ -194,4 +208,50 @@ public class Spigot { + return (entity instanceof EntityArrow && !((EntityArrow) entity).inGround); + + } ++ ++ public static void restart() { ++ try { ++ String startupScript = MinecraftServer.getServer().server.configuration.getString("settings.restart-script-location", ""); ++ File file = new File(startupScript); ++ if (file.isFile()) { ++ System.out.println("Attempting to restart with " + startupScript); ++ ++ // Kick all players ++ for (Player p : Bukkit.getServer().getOnlinePlayers()) { ++ ((org.bukkit.craftbukkit.entity.CraftPlayer) p).kickPlayer("Server is restarting", true); ++ } ++ // Give the socket a chance to send the packets ++ try { ++ Thread.sleep(100); ++ } catch (InterruptedException ex) { ++ } ++ // Close the socket so we can rebind with the new process ++ MinecraftServer.getServer().ae().a(); ++ ++ // Give time for it to kick in ++ try { ++ Thread.sleep(100); ++ } catch (InterruptedException ex) { ++ } ++ ++ // Actually shutdown ++ try { ++ MinecraftServer.getServer().stop(); ++ } catch (Throwable t) { ++ } ++ ++ String os = System.getProperty("os.name").toLowerCase(); ++ if (os.contains("win")) { ++ Runtime.getRuntime().exec("cmd /c start " + file.getPath()); ++ } else { ++ Runtime.getRuntime().exec(file.getPath()); ++ } ++ System.exit(0); ++ } else { ++ System.out.println("Startup script '" + startupScript + "' does not exist!"); ++ } ++ } catch (Exception ex) { ++ ex.printStackTrace(); ++ } ++ } + } +diff --git a/src/main/java/org/spigotmc/RestartCommand.java b/src/main/java/org/spigotmc/RestartCommand.java +new file mode 100644 +index 0000000..2d5c89f +--- /dev/null ++++ b/src/main/java/org/spigotmc/RestartCommand.java +@@ -0,0 +1,23 @@ ++package org.spigotmc; ++ ++import org.bukkit.command.Command; ++import org.bukkit.command.CommandSender; ++import org.bukkit.craftbukkit.Spigot; ++ ++public class RestartCommand extends Command { ++ ++ public RestartCommand(String name) { ++ super(name); ++ this.description = "Restarts the server"; ++ this.usageMessage = "/restart"; ++ this.setPermission("bukkit.command.restart"); ++ } ++ ++ @Override ++ public boolean execute(CommandSender sender, String currentAlias, String[] args) { ++ if (testPermission(sender)) { ++ Spigot.restart(); ++ } ++ return true; ++ } ++} +diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java +new file mode 100644 +index 0000000..10390b8 +--- /dev/null ++++ b/src/main/java/org/spigotmc/WatchdogThread.java +@@ -0,0 +1,93 @@ ++package org.spigotmc; ++ ++import java.lang.management.ManagementFactory; ++import java.lang.management.MonitorInfo; ++import java.lang.management.ThreadInfo; ++import java.util.logging.Level; ++import java.util.logging.Logger; ++import org.bukkit.Bukkit; ++import org.bukkit.craftbukkit.Spigot; ++ ++public class WatchdogThread extends Thread { ++ ++ private static WatchdogThread instance; ++ private final long timeoutTime; ++ private final boolean restart; ++ private volatile long lastTick; ++ private volatile boolean stopping; ++ ++ private WatchdogThread(long timeoutTime, boolean restart) { ++ super("Spigot Watchdog Thread"); ++ this.timeoutTime = timeoutTime; ++ this.restart = restart; ++ } ++ ++ public static void doStart(int timeoutTime, boolean restart) { ++ if (instance == null) { ++ instance = new WatchdogThread(timeoutTime * 1000L, restart); ++ instance.start(); ++ } ++ } ++ ++ public static void tick() { ++ instance.lastTick = System.currentTimeMillis(); ++ } ++ ++ public static void doStop() { ++ if (instance != null) { ++ instance.stopping = true; ++ } ++ } ++ ++ @Override ++ public void run() { ++ while (!stopping) { ++ // ++ if (lastTick != 0 && System.currentTimeMillis() > lastTick + timeoutTime) { ++ Logger log = Bukkit.getServer().getLogger(); ++ log.log(Level.SEVERE, "The server has stopped responding!"); ++ log.log(Level.SEVERE, "Please report this to http://www.spigotmc.org/"); ++ log.log(Level.SEVERE, "Be sure to include ALL relevant console errors and Minecraft crash reports"); ++ log.log(Level.SEVERE, "Spigot version: " + Bukkit.getServer().getVersion()); ++ // ++ log.log(Level.SEVERE, "Current Thread State:"); ++ ThreadInfo[] threads = ManagementFactory.getThreadMXBean().dumpAllThreads(true, true); ++ for (ThreadInfo thread : threads) { ++ if (thread.getThreadState() != State.WAITING) { ++ log.log(Level.SEVERE, "------------------------------"); ++ // ++ log.log(Level.SEVERE, "Current Thread: " + thread.getThreadName()); ++ log.log(Level.SEVERE, "\tPID: " + thread.getThreadId() ++ + " | Suspended: " + thread.isSuspended() ++ + " | Native: " + thread.isInNative() ++ + " | State: " + thread.getThreadState()); ++ if (thread.getLockedMonitors().length != 0) { ++ log.log(Level.SEVERE, "\tThread is waiting on monitor(s):"); ++ for (MonitorInfo monitor : thread.getLockedMonitors()) { ++ log.log(Level.SEVERE, "\t\tLocked on:" + monitor.getLockedStackFrame()); ++ } ++ } ++ log.log(Level.SEVERE, "\tStack:"); ++ // ++ StackTraceElement[] stack = thread.getStackTrace(); ++ for (int line = 0; line < stack.length; line++) { ++ log.log(Level.SEVERE, "\t\t" + stack[line].toString()); ++ } ++ } ++ } ++ log.log(Level.SEVERE, "------------------------------"); ++ ++ if (restart) { ++ Spigot.restart(); ++ } ++ break; ++ } ++ ++ try { ++ sleep(10000); ++ } catch (InterruptedException ex) { ++ interrupt(); ++ } ++ } ++ } ++} +-- +1.8.1-rc2 +