2019-01-01 04:15:55 +01:00
|
|
|
From b2776d5425d6c74a71d747af2e7714c8ed573402 Mon Sep 17 00:00:00 2001
|
2017-05-13 06:49:59 +02:00
|
|
|
From: Zach Brown <zach.brown@destroystokyo.com>
|
|
|
|
Date: Fri, 12 May 2017 23:34:11 -0500
|
|
|
|
Subject: [PATCH] Properly handle async calls to restart the server
|
|
|
|
|
2017-05-13 12:51:09 +02:00
|
|
|
The watchdog thread calls the server restart function asynchronously. Prior to
|
|
|
|
this change, it attempted to do several non-safe operations from the watchdog
|
|
|
|
thread, rather than the main. Specifically, because of a separate upstream change,
|
|
|
|
it causes player entities to be ticked asynchronously, among other things.
|
2017-05-13 06:49:59 +02:00
|
|
|
|
2017-05-13 12:51:09 +02:00
|
|
|
This is dangerous.
|
|
|
|
|
|
|
|
This patch moves the old handling into a synchronous variant, for calls from the
|
|
|
|
restart command, and adds separate handling for async calls, such as those from
|
|
|
|
the watchdog thread.
|
|
|
|
|
|
|
|
When calling from the watchdog thread, we cannot assume the main thread is in a
|
2017-05-14 15:49:56 +02:00
|
|
|
tickable state; it may be completely deadlocked. In order to handle this, we mark
|
|
|
|
the server as stopping, in order to account for situations where the server should
|
|
|
|
complete a tick reasonbly soon, i.e. 99% of cases.
|
|
|
|
|
|
|
|
Should the server not enter a state where it is stopping within 10 seconds, We
|
|
|
|
will assume that the server has in fact deadlocked and will proceed to force
|
|
|
|
kill the server.
|
|
|
|
|
|
|
|
This modification does not force restart the server should we actually enter a
|
|
|
|
deadlocked state where the server is stopping, whereas this will in most cases
|
|
|
|
exit within a reasonable amount of time, to put a fixed limit on a process that
|
|
|
|
will have plugins and worlds saving to the disk has a high potential to result
|
|
|
|
in corruption/dataloss.
|
2017-05-13 12:51:09 +02:00
|
|
|
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
|
2019-01-01 04:15:55 +01:00
|
|
|
index f09d358e2..f2c172ff1 100644
|
2017-05-13 12:51:09 +02:00
|
|
|
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
|
|
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
|
2019-01-01 04:15:55 +01:00
|
|
|
@@ -85,6 +85,7 @@ public abstract class MinecraftServer implements IAsyncTaskHandler, IMojangStati
|
2018-08-26 20:11:49 +02:00
|
|
|
public final Map<DimensionManager, WorldServer> worldServer = Maps.newIdentityHashMap();
|
2018-12-17 06:18:06 +01:00
|
|
|
private PlayerList playerList;
|
2017-05-14 15:49:56 +02:00
|
|
|
private boolean isRunning = true;
|
|
|
|
+ private boolean isRestarting = false; // Paper - flag to signify we're attempting to restart
|
|
|
|
private boolean isStopped;
|
|
|
|
private int ticks;
|
2018-08-26 20:11:49 +02:00
|
|
|
protected final Proxy c;
|
2019-01-01 04:15:55 +01:00
|
|
|
@@ -654,7 +655,7 @@ public abstract class MinecraftServer implements IAsyncTaskHandler, IMojangStati
|
2018-12-17 06:18:06 +01:00
|
|
|
if (this.playerList != null) {
|
2017-05-14 15:49:56 +02:00
|
|
|
MinecraftServer.LOGGER.info("Saving players");
|
2018-12-17 06:18:06 +01:00
|
|
|
this.playerList.savePlayers();
|
|
|
|
- this.playerList.u();
|
|
|
|
+ this.playerList.u(isRestarting); // Paper
|
2017-05-14 15:49:56 +02:00
|
|
|
try { Thread.sleep(100); } catch (InterruptedException ex) {} // CraftBukkit - SPIGOT-625 - give server at least a chance to send packets
|
|
|
|
}
|
|
|
|
|
2019-01-01 04:15:55 +01:00
|
|
|
@@ -704,9 +705,16 @@ public abstract class MinecraftServer implements IAsyncTaskHandler, IMojangStati
|
2017-05-14 15:49:56 +02:00
|
|
|
return this.isRunning;
|
|
|
|
}
|
|
|
|
|
|
|
|
+ // Paper start - allow passing of the intent to restart
|
|
|
|
public void safeShutdown() {
|
|
|
|
+ safeShutdown(false);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public void safeShutdown(boolean isRestarting) {
|
|
|
|
this.isRunning = false;
|
|
|
|
+ this.isRestarting = isRestarting;
|
|
|
|
}
|
|
|
|
+ // Paper end
|
2017-05-13 12:51:09 +02:00
|
|
|
|
2018-12-17 06:18:06 +01:00
|
|
|
private boolean canSleepForTick() {
|
2018-08-26 20:11:49 +02:00
|
|
|
return System.nanoTime() - lastTick + catchupTime < TICK_TIME; // Paper - improved "are we lagging" check to match our own
|
2017-05-14 15:49:56 +02:00
|
|
|
diff --git a/src/main/java/net/minecraft/server/PlayerList.java b/src/main/java/net/minecraft/server/PlayerList.java
|
2019-01-01 04:15:55 +01:00
|
|
|
index 0bc27d914..128fcaf25 100644
|
2017-05-14 15:49:56 +02:00
|
|
|
--- a/src/main/java/net/minecraft/server/PlayerList.java
|
|
|
|
+++ b/src/main/java/net/minecraft/server/PlayerList.java
|
2019-01-01 04:15:55 +01:00
|
|
|
@@ -1332,10 +1332,15 @@ public abstract class PlayerList {
|
2018-07-18 02:08:13 +02:00
|
|
|
entityplayer.playerInteractManager.b(generatoraccess.getWorldData().getGameType());
|
2017-05-14 15:49:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
+ // Paper start - Extract method to allow for restarting flag
|
|
|
|
public void u() {
|
|
|
|
+ u(false);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public void u(boolean isRestarting) {
|
|
|
|
// CraftBukkit start - disconnect safely
|
|
|
|
for (EntityPlayer player : this.players) {
|
|
|
|
- player.playerConnection.disconnect(this.server.server.getShutdownMessage()); // CraftBukkit - add custom shutdown message
|
|
|
|
+ player.playerConnection.disconnect(!isRestarting ? this.server.server.getShutdownMessage() : org.spigotmc.SpigotConfig.restartMessage); // CraftBukkit - add custom shutdown message // Paper - add isRestarting flag
|
|
|
|
}
|
|
|
|
// CraftBukkit end
|
|
|
|
// Paper start - Remove collideRule team if it exists
|
2019-01-01 04:15:55 +01:00
|
|
|
@@ -1346,6 +1351,7 @@ public abstract class PlayerList {
|
2017-05-14 15:49:56 +02:00
|
|
|
}
|
|
|
|
// Paper end
|
|
|
|
}
|
|
|
|
+ // Paper end
|
|
|
|
|
|
|
|
// CraftBukkit start
|
|
|
|
public void sendMessage(IChatBaseComponent[] iChatBaseComponents) {
|
2017-05-13 06:49:59 +02:00
|
|
|
diff --git a/src/main/java/org/spigotmc/RestartCommand.java b/src/main/java/org/spigotmc/RestartCommand.java
|
2019-01-01 04:15:55 +01:00
|
|
|
index 944151d14..061cbe7fc 100644
|
2017-05-13 06:49:59 +02:00
|
|
|
--- a/src/main/java/org/spigotmc/RestartCommand.java
|
|
|
|
+++ b/src/main/java/org/spigotmc/RestartCommand.java
|
2019-01-01 04:15:55 +01:00
|
|
|
@@ -46,86 +46,123 @@ public class RestartCommand extends Command
|
2018-02-15 06:34:58 +01:00
|
|
|
org.spigotmc.AsyncCatcher.shuttingDown = true; // Paper
|
2017-05-14 15:49:56 +02:00
|
|
|
try
|
|
|
|
{
|
2019-01-01 04:15:55 +01:00
|
|
|
- String[] split = restartScript.split( " " );
|
|
|
|
- if ( split.length > 0 && new File( split[0] ).isFile() )
|
2017-05-14 15:49:56 +02:00
|
|
|
+ // Paper - extract method and cleanup
|
2019-01-01 04:15:55 +01:00
|
|
|
+ boolean isRestarting = addShutdownHook(restartScript);
|
2017-05-14 15:49:56 +02:00
|
|
|
+ if (isRestarting) {
|
|
|
|
+ System.out.println("Attempting to restart with " + SpigotConfig.restartScript);
|
|
|
|
+ } else {
|
|
|
|
+ System.out.println( "Startup script '" + SpigotConfig.restartScript + "' does not exist! Stopping server." );
|
|
|
|
+ }
|
2017-06-09 02:46:54 +02:00
|
|
|
+
|
|
|
|
+ // Stop the watchdog
|
|
|
|
+ WatchdogThread.doStop();
|
|
|
|
+
|
|
|
|
+ shutdownServer(isRestarting);
|
|
|
|
+ } catch ( Exception ex )
|
|
|
|
+ {
|
|
|
|
+ ex.printStackTrace();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Paper start - sync copied from above with minor changes, async added
|
|
|
|
+ private static void shutdownServer(boolean isRestarting)
|
|
|
|
+ {
|
|
|
|
+ if (MinecraftServer.getServer().isMainThread())
|
|
|
|
+ {
|
|
|
|
+ // Kick all players
|
|
|
|
+ for ( EntityPlayer p : com.google.common.collect.ImmutableList.copyOf( MinecraftServer.getServer().getPlayerList().players ) )
|
2019-01-01 04:15:55 +01:00
|
|
|
{
|
|
|
|
- System.out.println( "Attempting to restart with " + restartScript );
|
2017-06-09 02:46:54 +02:00
|
|
|
+ p.playerConnection.disconnect(SpigotConfig.restartMessage);
|
|
|
|
+ }
|
|
|
|
+ // Give the socket a chance to send the packets
|
|
|
|
+ try
|
|
|
|
+ {
|
|
|
|
+ Thread.sleep( 100 );
|
|
|
|
+ } catch ( InterruptedException ex )
|
2019-01-01 04:15:55 +01:00
|
|
|
+ {
|
2017-06-09 02:46:54 +02:00
|
|
|
+ }
|
2017-05-14 15:49:56 +02:00
|
|
|
|
|
|
|
- // Disable Watchdog
|
|
|
|
- WatchdogThread.doStop();
|
2017-06-09 02:46:54 +02:00
|
|
|
+ closeSocket();
|
2017-05-13 06:49:59 +02:00
|
|
|
|
|
|
|
- // 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();
|
2017-06-09 02:46:54 +02:00
|
|
|
+ // Actually shutdown
|
|
|
|
+ try
|
|
|
|
+ {
|
|
|
|
+ MinecraftServer.getServer().stop();
|
|
|
|
+ } catch ( Throwable t )
|
|
|
|
+ {
|
|
|
|
+ }
|
|
|
|
|
2017-05-13 06:49:59 +02:00
|
|
|
- // Give time for it to kick in
|
|
|
|
- try
|
|
|
|
- {
|
|
|
|
- Thread.sleep( 100 );
|
|
|
|
- } catch ( InterruptedException ex )
|
|
|
|
- {
|
|
|
|
- }
|
2017-06-09 02:46:54 +02:00
|
|
|
+ // Actually stop the JVM
|
|
|
|
+ System.exit(0);
|
2017-05-14 15:49:56 +02:00
|
|
|
|
2017-05-13 06:49:59 +02:00
|
|
|
- // Actually shutdown
|
|
|
|
- try
|
|
|
|
- {
|
|
|
|
- MinecraftServer.getServer().stop();
|
|
|
|
- } catch ( Throwable t )
|
|
|
|
- {
|
|
|
|
- }
|
2017-06-09 02:46:54 +02:00
|
|
|
+ } else
|
2017-05-13 06:49:59 +02:00
|
|
|
+ {
|
2017-06-09 02:46:54 +02:00
|
|
|
+ // Mark the server to shutdown at the end of the tick
|
|
|
|
+ MinecraftServer.getServer().safeShutdown(isRestarting);
|
2017-05-14 15:49:56 +02:00
|
|
|
|
|
|
|
- // 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" ) )
|
|
|
|
- {
|
2019-01-01 04:15:55 +01:00
|
|
|
- Runtime.getRuntime().exec( "cmd /c start " + restartScript );
|
2017-05-14 15:49:56 +02:00
|
|
|
- } else
|
|
|
|
- {
|
2019-01-01 04:15:55 +01:00
|
|
|
- Runtime.getRuntime().exec( "sh " + restartScript );
|
2017-05-14 15:49:56 +02:00
|
|
|
- }
|
|
|
|
- } catch ( Exception e )
|
|
|
|
- {
|
|
|
|
- e.printStackTrace();
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- };
|
|
|
|
|
|
|
|
- shutdownHook.setDaemon( true );
|
|
|
|
- Runtime.getRuntime().addShutdownHook( shutdownHook );
|
|
|
|
- } else
|
2019-01-01 04:15:55 +01:00
|
|
|
- {
|
2017-06-09 02:46:54 +02:00
|
|
|
- System.out.println( "Startup script '" + SpigotConfig.restartScript + "' does not exist! Stopping server." );
|
2019-01-01 04:15:55 +01:00
|
|
|
|
2017-05-14 15:49:56 +02:00
|
|
|
- // Actually shutdown
|
|
|
|
- try
|
|
|
|
- {
|
|
|
|
- MinecraftServer.getServer().stop();
|
|
|
|
- } catch ( Throwable t )
|
|
|
|
- {
|
|
|
|
- }
|
2019-01-01 04:15:55 +01:00
|
|
|
+ // wait 10 seconds to see if we're actually going to try shutdown
|
|
|
|
+ try
|
|
|
|
+ {
|
|
|
|
+ Thread.sleep(10000);
|
|
|
|
+ }
|
|
|
|
+ catch (InterruptedException ignored)
|
|
|
|
+ {
|
2017-05-14 15:49:56 +02:00
|
|
|
}
|
|
|
|
+
|
|
|
|
+ // Check if we've actually hit a state where the server is going to safely shutdown
|
|
|
|
+ // if we have, let the server stop as usual
|
|
|
|
+ if (MinecraftServer.getServer().isStopped()) return;
|
|
|
|
+
|
|
|
|
+ // If the server hasn't stopped by now, assume worse case and kill
|
2017-05-13 06:49:59 +02:00
|
|
|
+ closeSocket();
|
2017-05-14 15:49:56 +02:00
|
|
|
System.exit( 0 );
|
|
|
|
- } catch ( Exception ex )
|
2017-05-13 06:49:59 +02:00
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Paper - Split from moved code
|
|
|
|
+ private static void closeSocket() {
|
|
|
|
+ // Close the socket so we can rebind with the new process
|
|
|
|
+ MinecraftServer.getServer().getServerConnection().b();
|
|
|
|
+
|
|
|
|
+ // Give time for it to kick in
|
|
|
|
+ try
|
2017-05-14 15:49:56 +02:00
|
|
|
{
|
|
|
|
- ex.printStackTrace();
|
2017-05-13 06:49:59 +02:00
|
|
|
+ Thread.sleep( 100 );
|
|
|
|
+ } catch ( InterruptedException ex )
|
|
|
|
+ {
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ // Paper end
|
2017-05-14 15:49:56 +02:00
|
|
|
+
|
|
|
|
+ // Paper - copied from above and modified to return if the hook registered
|
2019-01-01 04:15:55 +01:00
|
|
|
+ private static boolean addShutdownHook(final String restartScript) {
|
2017-05-14 15:49:56 +02:00
|
|
|
+
|
2019-01-01 04:15:55 +01:00
|
|
|
+ String[] split = restartScript.split( " " );
|
|
|
|
+ if ( split.length > 0 && new File( split[0] ).isFile() )
|
|
|
|
+ {
|
2017-05-14 15:49:56 +02:00
|
|
|
+ Thread shutdownHook = new Thread() {
|
|
|
|
+ @Override
|
|
|
|
+ public void run() {
|
|
|
|
+ try {
|
|
|
|
+ String os = System.getProperty("os.name").toLowerCase(java.util.Locale.ENGLISH);
|
|
|
|
+ if (os.contains("win")) {
|
2019-01-01 04:15:55 +01:00
|
|
|
+ Runtime.getRuntime().exec("cmd /c start " + restartScript);
|
2017-05-14 15:49:56 +02:00
|
|
|
+ } else {
|
2019-01-01 04:15:55 +01:00
|
|
|
+ Runtime.getRuntime().exec( "sh " + restartScript );
|
2017-05-14 15:49:56 +02:00
|
|
|
+ }
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
+ e.printStackTrace();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ shutdownHook.setDaemon(true);
|
|
|
|
+ Runtime.getRuntime().addShutdownHook(shutdownHook);
|
|
|
|
+ return true;
|
|
|
|
+ } else {
|
|
|
|
+ return false;
|
|
|
|
}
|
|
|
|
}
|
2017-05-13 06:49:59 +02:00
|
|
|
}
|
|
|
|
--
|
2019-01-01 04:15:55 +01:00
|
|
|
2.20.1
|
2017-05-13 06:49:59 +02:00
|
|
|
|