Watchdog Thread.

By: md_5 <git@md-5.net>
This commit is contained in:
CraftBukkit/Spigot
2014-08-05 17:20:19 +01:00
parent 29a8c7f4b3
commit 496995ccaa
6 changed files with 328 additions and 30 deletions

View File

@@ -2139,7 +2139,7 @@ public final class CraftServer implements Server {
@Override
public boolean isPrimaryThread() {
return Thread.currentThread().equals(this.console.serverThread) || this.console.hasStopped(); // All bets are off if we have shut down (e.g. due to watchdog)
return Thread.currentThread().equals(this.console.serverThread) || this.console.hasStopped() || !org.spigotmc.AsyncCatcher.enabled; // All bets are off if we have shut down (e.g. due to watchdog)
}
@Override
@@ -2580,6 +2580,11 @@ public final class CraftServer implements Server {
{
return org.spigotmc.SpigotConfig.config;
}
@Override
public void restart() {
org.spigotmc.RestartCommand.restart();
}
};
public org.bukkit.Server.Spigot spigot()

View File

@@ -0,0 +1,131 @@
package org.spigotmc;
import java.io.File;
import java.util.List;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.craftbukkit.util.CraftChatMessage;
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 ( this.testPermission( sender ) )
{
MinecraftServer.getServer().processQueue.add( new Runnable()
{
@Override
public void run()
{
RestartCommand.restart();
}
} );
}
return true;
}
public static void restart()
{
RestartCommand.restart( SpigotConfig.restartScript );
}
private static void restart(final String restartScript)
{
AsyncCatcher.enabled = false; // Disable async catcher incase it interferes with us
try
{
String[] split = restartScript.split( " " );
if ( split.length > 0 && new File( split[0] ).isFile() )
{
System.out.println( "Attempting to restart with " + restartScript );
// Disable Watchdog
WatchdogThread.doStop();
// Kick all players
for ( ServerPlayer p : (List<ServerPlayer>) MinecraftServer.getServer().getPlayerList().players )
{
p.connection.disconnect( CraftChatMessage.fromStringOrEmpty( SpigotConfig.restartMessage, true ) );
}
// 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().getConnection().stop();
// 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();
}
}
}

View File

@@ -200,4 +200,18 @@ public class SpigotConfig
SpigotConfig.outdatedClientMessage = SpigotConfig.transform( SpigotConfig.getString( "messages.outdated-client", SpigotConfig.outdatedClientMessage ) );
SpigotConfig.outdatedServerMessage = SpigotConfig.transform( SpigotConfig.getString( "messages.outdated-server", SpigotConfig.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()
{
SpigotConfig.timeoutTime = SpigotConfig.getInt( "settings.timeout-time", SpigotConfig.timeoutTime );
SpigotConfig.restartOnCrash = SpigotConfig.getBoolean( "settings.restart-on-crash", SpigotConfig.restartOnCrash );
SpigotConfig.restartScript = SpigotConfig.getString( "settings.restart-script", SpigotConfig.restartScript );
SpigotConfig.restartMessage = SpigotConfig.transform( SpigotConfig.getString( "messages.restart", "Server is restarting" ) );
SpigotConfig.commands.put( "restart", new RestartCommand( "restart" ) );
WatchdogThread.doStart( SpigotConfig.timeoutTime, SpigotConfig.restartOnCrash );
}
}

View File

@@ -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 ( WatchdogThread.instance == null )
{
WatchdogThread.instance = new WatchdogThread( timeoutTime * 1000L, restart );
WatchdogThread.instance.start();
} else
{
WatchdogThread.instance.timeoutTime = timeoutTime * 1000L;
WatchdogThread.instance.restart = restart;
}
}
public static void tick()
{
WatchdogThread.instance.lastTick = WatchdogThread.monotonicMillis();
}
public static void doStop()
{
if ( WatchdogThread.instance != null )
{
WatchdogThread.instance.stopping = true;
}
}
@Override
public void run()
{
while ( !this.stopping )
{
//
if ( this.lastTick != 0 && this.timeoutTime > 0 && WatchdogThread.monotonicMillis() > this.lastTick + this.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!):" );
WatchdogThread.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 )
{
WatchdogThread.dumpThread( thread, log );
}
log.log( Level.SEVERE, "------------------------------" );
if ( this.restart && !MinecraftServer.getServer().hasStopped() )
{
RestartCommand.restart();
}
break;
}
try
{
sleep( 10000 );
} catch ( InterruptedException ex )
{
this.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 );
}
}
}