From 3c8f2ccaa98803166178f16290fce2b8626e6ebb 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/spigotmc/RestartCommand.java b/src/main/java/org/spigotmc/RestartCommand.java
new file mode 100644
index 0000000..a5c4549
--- /dev/null
+++ b/src/main/java/org/spigotmc/RestartCommand.java
@@ -0,0 +1,111 @@
+package org.spigotmc;
+
+import java.io.File;
+import java.util.List;
+import net.minecraft.server.EntityPlayer;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.Packet255KickDisconnect;
+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 ) )
+        {
+            restart();
+        }
+        return true;
+    }
+
+    public static void restart()
+    {
+        try
+        {
+            final File file = new File( SpigotConfig.restartScript );
+            if ( file.isFile() )
+            {
+                System.out.println( "Attempting to restart with " + SpigotConfig.restartScript );
+
+                // 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 '" + 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 8f53ca9..b7f3896 100644
--- a/src/main/java/org/spigotmc/SpigotConfig.java
+++ b/src/main/java/org/spigotmc/SpigotConfig.java
@@ -136,4 +136,16 @@ public class SpigotConfig
     {
         commands.put( "tps", new TicksPerSecondCommand( "tps" ) );
     }
+
+    public static int timeoutTime = 60;
+    public static boolean restartOnCrash = true;
+    public static String restartScript = "./start.sh";
+    private static void watchdog()
+    {
+        timeoutTime = getInt( "settings.timeout-time", timeoutTime );
+        restartOnCrash = getBoolean( "settings.restart-on-crash", restartOnCrash );
+        restartScript = getString( "settings.restart-script", restartScript );
+        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..38ef9b7
--- /dev/null
+++ b/src/main/java/org/spigotmc/WatchdogThread.java
@@ -0,0 +1,110 @@
+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;
+
+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 )
+                {
+                    RestartCommand.restart();
+                }
+                break;
+            }
+
+            try
+            {
+                sleep( 10000 );
+            } catch ( InterruptedException ex )
+            {
+                interrupt();
+            }
+        }
+    }
+}
-- 
1.8.1.2