From d80a8985f88927ef527f5959f32ec1d2c47ab765 Mon Sep 17 00:00:00 2001 From: md_5 Date: Tue, 5 Aug 2014 17:20:19 +0100 Subject: [PATCH] Watchdog Thread. diff --git a/src/main/java/net/minecraft/server/DedicatedServer.java b/src/main/java/net/minecraft/server/DedicatedServer.java index da295f3..113049a 100644 --- a/src/main/java/net/minecraft/server/DedicatedServer.java +++ b/src/main/java/net/minecraft/server/DedicatedServer.java @@ -270,7 +270,7 @@ public class DedicatedServer extends MinecraftServer implements IMinecraftServer } // CraftBukkit end - if (this.aS() > 0L) { + if (false && this.aS() > 0L) { // Spigot - disable Thread thread1 = new Thread(new ThreadWatchdog(this)); thread1.setName("Server Watchdog"); diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java index 2acf3bd..d5f58f1 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -566,6 +566,7 @@ public abstract class MinecraftServer implements Runnable, ICommandListener, IAs this.a(crashreport); } finally { try { + org.spigotmc.WatchdogThread.doStop(); this.isStopped = true; this.stop(); } catch (Throwable throwable1) { @@ -666,6 +667,7 @@ public abstract class MinecraftServer implements Runnable, ICommandListener, IAs this.methodProfiler.b(); this.methodProfiler.b(); + org.spigotmc.WatchdogThread.tick(); // Spigot SpigotTimings.serverTickTimer.stopTiming(); // Spigot org.spigotmc.CustomTimingsHandler.tick(); // Spigot } diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java index ff60351..63b5d67 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -1685,6 +1685,11 @@ public final class CraftServer implements Server { { return org.spigotmc.SpigotConfig.config; } + + @Override + public void restart() { + org.spigotmc.RestartCommand.restart(); + } }; public Spigot spigot() diff --git a/src/main/java/org/spigotmc/RestartCommand.java b/src/main/java/org/spigotmc/RestartCommand.java new file mode 100644 index 0000000..429c258 --- /dev/null +++ b/src/main/java/org/spigotmc/RestartCommand.java @@ -0,0 +1,124 @@ +package org.spigotmc; + +import java.io.File; +import java.util.List; +import net.minecraft.server.EntityPlayer; +import net.minecraft.server.MinecraftServer; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; + +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 ) ) + { + MinecraftServer.getServer().processQueue.add( new Runnable() + { + @Override + public void run() + { + restart(); + } + } ); + } + return true; + } + + public static void restart() + { + restart( new File( SpigotConfig.restartScript ) ); + } + + public static void restart(final File script) + { + AsyncCatcher.enabled = false; // Disable async catcher incase it interferes with us + try + { + if ( script.isFile() ) + { + System.out.println( "Attempting to restart with " + SpigotConfig.restartScript ); + + // Disable Watchdog + WatchdogThread.doStop(); + + // Kick all players + for ( EntityPlayer p : (List< EntityPlayer>) MinecraftServer.getServer().getPlayerList().players ) + { + p.playerConnection.disconnect(SpigotConfig.restartMessage); + } + // 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().getServerConnection().b(); + + // 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 " + script.getPath() ); + } else + { + Runtime.getRuntime().exec( new String[] + { + "sh", script.getPath() + } ); + } + } catch ( Exception e ) + { + e.printStackTrace(); + } + } + }; + + shutdownHook.setDaemon( true ); + Runtime.getRuntime().addShutdownHook( shutdownHook ); + } else + { + System.out.println( "Startup script '" + SpigotConfig.restartScript + "' does not exist! Stopping server." ); + } + System.exit( 0 ); + } catch ( Exception ex ) + { + ex.printStackTrace(); + } + } +} diff --git a/src/main/java/org/spigotmc/SpigotConfig.java b/src/main/java/org/spigotmc/SpigotConfig.java index 3726ea5..34def7b 100644 --- a/src/main/java/org/spigotmc/SpigotConfig.java +++ b/src/main/java/org/spigotmc/SpigotConfig.java @@ -197,4 +197,18 @@ public class SpigotConfig outdatedClientMessage = transform( getString( "messages.outdated-client", outdatedClientMessage ) ); outdatedServerMessage = transform( getString( "messages.outdated-server", outdatedServerMessage ) ); } + + public static int timeoutTime = 60; + public static boolean restartOnCrash = true; + public static String restartScript = "./start.sh"; + public static String restartMessage; + private static void watchdog() + { + timeoutTime = getInt( "settings.timeout-time", timeoutTime ); + restartOnCrash = getBoolean( "settings.restart-on-crash", restartOnCrash ); + restartScript = getString( "settings.restart-script", restartScript ); + restartMessage = transform( getString( "messages.restart", "Server is restarting" ) ); + commands.put( "restart", new RestartCommand( "restart" ) ); + WatchdogThread.doStart( timeoutTime, restartOnCrash ); + } } diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java new file mode 100644 index 0000000..de08ad6 --- /dev/null +++ b/src/main/java/org/spigotmc/WatchdogThread.java @@ -0,0 +1,117 @@ +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 net.minecraft.server.MinecraftServer; +import org.bukkit.Bukkit; + +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, "------------------------------" ); + log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Spigot!):" ); + dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServer().primaryThread.getId(), Integer.MAX_VALUE ), log ); + log.log( Level.SEVERE, "------------------------------" ); + // + log.log( Level.SEVERE, "Entire Thread Dump:" ); + ThreadInfo[] threads = ManagementFactory.getThreadMXBean().dumpAllThreads( true, true ); + for ( ThreadInfo thread : threads ) + { + dumpThread( thread, log ); + } + log.log( Level.SEVERE, "------------------------------" ); + + if ( restart ) + { + RestartCommand.restart(); + } + break; + } + + try + { + sleep( 10000 ); + } catch ( InterruptedException ex ) + { + interrupt(); + } + } + } + + private static void dumpThread(ThreadInfo thread, Logger log) + { + 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:" ); + // + for ( StackTraceElement stack : thread.getStackTrace() ) + { + log.log( Level.SEVERE, "\t\t" + stack ); + } + } +} -- 2.1.4