From bcc479e29c57f5e24066b2971a911e1e0aab02c7 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 | 77 +++++++++++++++++- 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, 196 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 ced4bd4..15acc97 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); @@ -25,12 +30,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) { @@ -193,4 +207,63 @@ 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", ""); + 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 54e28db..e04ebce 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-rc2