From bb11344982c034e55e1aaaa5b08059704eb769ea 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 2a63e517b..d095266fb 100644 --- a/src/main/java/net/minecraft/server/DedicatedServer.java +++ b/src/main/java/net/minecraft/server/DedicatedServer.java @@ -228,7 +228,7 @@ public class DedicatedServer extends MinecraftServer implements IMinecraftServer this.remoteConsole = new org.bukkit.craftbukkit.command.CraftRemoteConsoleCommandSender(this.remoteControlCommandListener); // CraftBukkit } - if (this.getMaxTickTime() > 0L) { + if (false && this.getMaxTickTime() > 0L) { // Spigot - disable Thread thread1 = new Thread(new ThreadWatchdog(this)); thread1.setUncaughtExceptionHandler(new ThreadNamedUncaughtExceptionHandler(DedicatedServer.LOGGER)); diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java index 9fa36f269..c2903bdf4 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -849,6 +849,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant 0 && new File( split[0] ).isFile() ) + { + System.out.println( "Attempting to restart with " + 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().close(); + } 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(java.util.Locale.ENGLISH); + if ( os.contains( "win" ) ) + { + Runtime.getRuntime().exec( "cmd /c start " + restartScript ); + } else + { + Runtime.getRuntime().exec( "sh " + restartScript ); + } + } 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." ); + + // Actually shutdown + try + { + MinecraftServer.getServer().close(); + } catch ( Throwable t ) + { + } + } + 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 60d14fa25..37bf0cec0 100644 --- a/src/main/java/org/spigotmc/SpigotConfig.java +++ b/src/main/java/org/spigotmc/SpigotConfig.java @@ -200,4 +200,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 000000000..4734d4ae8 --- /dev/null +++ b/src/main/java/org/spigotmc/WatchdogThread.java @@ -0,0 +1,131 @@ +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 long timeoutTime; + private 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; + } + + private static long monotonicMillis() + { + return System.nanoTime() / 1000000L; + } + + public static void doStart(int timeoutTime, boolean restart) + { + if ( instance == null ) + { + instance = new WatchdogThread( timeoutTime * 1000L, restart ); + instance.start(); + } else + { + instance.timeoutTime = timeoutTime * 1000L; + instance.restart = restart; + } + } + + public static void tick() + { + instance.lastTick = monotonicMillis(); + } + + public static void doStop() + { + if ( instance != null ) + { + instance.stopping = true; + } + } + + @Override + public void run() + { + while ( !stopping ) + { + // + if ( lastTick != 0 && timeoutTime > 0 && monotonicMillis() > lastTick + timeoutTime ) + { + Logger log = Bukkit.getServer().getLogger(); + log.log( Level.SEVERE, "------------------------------" ); + log.log( Level.SEVERE, "The server has stopped responding! This is (probably) not a Spigot bug." ); + log.log( Level.SEVERE, "If you see a plugin in the Server thread dump below, then please report it to that author" ); + log.log( Level.SEVERE, "\t *Especially* if it looks like HTTP or MySQL operations are occurring" ); + log.log( Level.SEVERE, "If you see a world save or edit, then it means you did far more than your server can handle at once" ); + log.log( Level.SEVERE, "\t If this is the case, consider increasing timeout-time in spigot.yml but note that this will replace the crash with LARGE lag spikes" ); + log.log( Level.SEVERE, "If you are unsure or still think this is a Spigot bug, please report to https://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().serverThread.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 && !MinecraftServer.getServer().hasStopped() ) + { + 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.25.1