Paper/CraftBukkit-Patches/0020-Watchdog-Thread.patch
2013-06-02 11:33:49 +10:00

300 lines
12 KiB
Diff

From e6e61e6fecc68b7870230273a6510b751826bb21 Mon Sep 17 00:00:00 2001
From: md_5 <md_5@live.com.au>
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 4b222a3..4cbc796 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;
@@ -21,6 +22,7 @@ import net.minecraft.server.EntityHuman;
import net.minecraft.server.EntityItemFrame;
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");
@@ -294,6 +311,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 96d26d4..67b4fa9 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