From 5a0d969c584b848bd8fcb62f4b3178c071801edb 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 | 76 +++++++++++++++++- src/main/java/org/spigotmc/RestartCommand.java | 23 ++++++ src/main/java/org/spigotmc/WatchdogThread.java | 93 ++++++++++++++++++++++ src/main/resources/configurations/bukkit.yml | 3 + 5 files changed, 195 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 aa6a14a..6005fac 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -421,6 +421,7 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IMo this.q(); SpigotTimings.serverTickTimer.stopTiming(); org.bukkit.CustomTimingsHandler.tick(); + org.spigotmc.WatchdogThread.tick(); } // Spigot end } else { @@ -448,6 +449,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 3171411..6f54f7e 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.*; @@ -8,9 +9,11 @@ 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 { static AxisAlignedBB maxBB = AxisAlignedBB.a(0,0,0,0,0,0); @@ -23,6 +26,7 @@ public class Spigot { 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); @@ -31,12 +35,21 @@ public class Spigot { server.commandComplete = configuration.getBoolean("settings.command-complete", true); server.spamGuardExclusions = configuration.getStringList("settings.spam-exclusions"); + 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) { @@ -265,4 +278,63 @@ public class Spigot { SpigotTimings.checkIfActiveTimer.stopTiming(); return isActive; } + + public static void restart() { + try { + String startupScript = MinecraftServer.getServer().server.configuration.getString("settings.restart-script-location", ""); + final 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) { + } + + // This will be done AFTER the server has completely halted + Thread shutdownHook = new Thread() { + @Override + public void run(){ + try { + String os = System.getProperty("os.name").toLowerCase(); + if (os.contains("win")) { + Runtime.getRuntime().exec("cmd /c start " + file.getPath()); + } else { + Runtime.getRuntime().exec(new String[] { "sh", file.getPath()}); + } + } catch (Exception e){ + e.printStackTrace(); + } + } + }; + + shutdownHook.setDaemon(true); + Runtime.getRuntime().addShutdownHook(shutdownHook); + 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(); + } + } + } +} diff --git a/src/main/resources/configurations/bukkit.yml b/src/main/resources/configurations/bukkit.yml index e568bf6..7c18391 100644 --- a/src/main/resources/configurations/bukkit.yml +++ b/src/main/resources/configurations/bukkit.yml @@ -31,6 +31,9 @@ settings: command-complete: true spam-exclusions: - /skill + timeout-time: 30 + restart-on-crash: false + restart-script-location: /path/to/server/start.sh world-settings: default: growth-chunks-per-tick: 650 -- 1.8.1.1