From 4930fa4aa556413371df3feef49b03b62bdd06dd 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 f9cbe47..2407ab2 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -488,6 +488,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) {
@@ -678,6 +679,7 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IMo
         SpigotTimings.tickablesTimer.stopTiming(); // Spigot
 
         this.methodProfiler.b();
+        org.spigotmc.WatchdogThread.tick(); // Spigot
         SpigotTimings.serverTickTimer.stopTiming(); // Spigot
         org.spigotmc.CustomTimingsHandler.tick(); // 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..2d330fc
--- /dev/null
+++ b/src/main/java/org/spigotmc/RestartCommand.java
@@ -0,0 +1,110 @@
+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 ) )
+        {
+            restart();
+        }
+        return true;
+    }
+
+    public static void restart()
+    {
+        AsyncCatcher.enabled = false; // Disable async catcher incase it interferes with us
+        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.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().ah().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 " + 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 0ab2ec3..2ea1be7 100644
--- a/src/main/java/org/spigotmc/SpigotConfig.java
+++ b/src/main/java/org/spigotmc/SpigotConfig.java
@@ -167,4 +167,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 );
+        }
+    }
+}
-- 
1.8.5.2.msysgit.0