From 047eb8fcd54deba4c619979f82c098dae103470d Mon Sep 17 00:00:00 2001 From: md_5 Date: Sat, 23 Feb 2013 12:33:20 +1100 Subject: [PATCH] Watchdog Thread. diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java index db396b3..d197f06 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -404,6 +404,7 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IMo this.q(); SpigotTimings.serverTickTimer.stopTiming(); org.spigotmc.CustomTimingsHandler.tick(); + org.spigotmc.WatchdogThread.tick(); } // Spigot end } else { @@ -431,6 +432,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 2532aa2..6a164ca 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.net.InetAddress; import java.util.ArrayList; @@ -20,6 +21,7 @@ import net.minecraft.server.EntityFireworks; import net.minecraft.server.EntityHuman; import net.minecraft.server.EntityLiving; import net.minecraft.server.EntityMonster; +import net.minecraft.server.EntityPlayer; import net.minecraft.server.EntityProjectile; import net.minecraft.server.EntitySheep; import net.minecraft.server.EntitySlime; @@ -29,12 +31,15 @@ import net.minecraft.server.EntityWeather; import net.minecraft.server.EntityWither; import net.minecraft.server.MathHelper; import net.minecraft.server.MinecraftServer; +import net.minecraft.server.Packet255KickDisconnect; import net.minecraft.server.PendingConnection; import net.minecraft.server.World; import org.bukkit.Bukkit; import org.bukkit.command.SimpleCommandMap; import org.bukkit.configuration.file.YamlConfiguration; import org.spigotmc.Metrics; +import org.spigotmc.RestartCommand; +import org.spigotmc.WatchdogThread; public class Spigot { @@ -48,6 +53,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); @@ -56,6 +62,17 @@ public class Spigot { server.spamGuardExclusions = configuration.getStringList("settings.spam-exclusions"); filterIps = configuration.getBoolean("settings.filter-unsafe-ips", false); + int configVersion = configuration.getInt("config-version"); + switch (configVersion) { + case 0: + configuration.set("settings.timeout-time", 30); + case 1: + configuration.set("settings.timeout-time", 60); + } + configuration.set("config-version", 2); + + WatchdogThread.doStart(configuration.getInt("settings.timeout-time", 60), configuration.getBoolean("settings.restart-on-crash", false)); + server.orebfuscatorEnabled = configuration.getBoolean("orebfuscator.enable", false); server.orebfuscatorEngineMode = configuration.getInt("orebfuscator.engine-mode", 1); server.orebfuscatorDisabledWorlds = configuration.getStringList("orebfuscator.disabled-worlds"); @@ -297,6 +314,66 @@ public class Spigot { 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 (EntityPlayer p : (List< EntityPlayer>) MinecraftServer.getServer().getPlayerList().players) { + p.playerConnection.networkManager.queue(new Packet255KickDisconnect("Server is restarting")); + p.playerConnection.networkManager.d(); + } + // 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); + } else { + System.out.println("Startup script '" + startupScript + "' does not exist! Stopping server."); + } + System.exit(0); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + public static boolean filterIp(PendingConnection con) { if (filterIps) { try { 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 9c64871..3d4272b 100644 --- a/src/main/resources/configurations/bukkit.yml +++ b/src/main/resources/configurations/bukkit.yml @@ -31,6 +31,9 @@ settings: spam-exclusions: - /skill filter-unsafe-ips: false + 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.2