2020-05-06 11:48:49 +02:00
|
|
|
From 0000000000000000000000000000000000000000 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
|
2020-05-17 03:38:19 +02:00
|
|
|
index 629304c403c596bf81dd8de919f0fcb5c77bd403..80d8b0b0eac47b8d8e62db60da9daf0da8671fb3 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-07-20 06:01:24 +02:00
|
|
|
@@ -88,6 +88,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
|
2019-01-06 18:15:21 +01:00
|
|
|
public final Map<DimensionManager, WorldServer> worldServer = Maps.newLinkedHashMap(); // CraftBukkit - keep order, k+v already use identity methods
|
2018-12-17 06:18:06 +01:00
|
|
|
private PlayerList playerList;
|
2019-04-27 08:26:04 +02:00
|
|
|
private volatile boolean isRunning = true;
|
|
|
|
+ private volatile boolean isRestarting = false; // Paper - flag to signify we're attempting to restart
|
2017-05-14 15:49:56 +02:00
|
|
|
private boolean isStopped;
|
|
|
|
private int ticks;
|
2019-04-27 08:26:04 +02:00
|
|
|
protected final Proxy proxy;
|
2020-01-22 03:02:07 +01:00
|
|
|
@@ -723,7 +724,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
|
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();
|
2019-04-27 08:26:04 +02:00
|
|
|
- this.playerList.shutdown();
|
|
|
|
+ this.playerList.shutdown(this.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
|
|
|
|
}
|
|
|
|
|
2020-05-17 03:38:19 +02:00
|
|
|
@@ -780,8 +781,13 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
|
2017-05-14 15:49:56 +02:00
|
|
|
return this.isRunning;
|
|
|
|
}
|
|
|
|
|
|
|
|
+ // Paper start - allow passing of the intent to restart
|
2019-04-27 08:26:04 +02:00
|
|
|
public void safeShutdown(boolean flag) {
|
|
|
|
+ this.safeShutdown(flag, false);
|
2017-05-14 15:49:56 +02:00
|
|
|
+ }
|
2019-04-27 08:26:04 +02:00
|
|
|
+ public void safeShutdown(boolean flag, boolean isRestarting) {
|
2017-05-14 15:49:56 +02:00
|
|
|
this.isRunning = false;
|
|
|
|
+ this.isRestarting = isRestarting;
|
2019-04-27 08:26:04 +02:00
|
|
|
if (flag) {
|
|
|
|
try {
|
|
|
|
this.serverThread.join();
|
2020-05-17 03:38:19 +02:00
|
|
|
@@ -791,6 +797,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
|
2019-04-27 08:26:04 +02:00
|
|
|
}
|
|
|
|
|
2017-05-14 15:49:56 +02:00
|
|
|
}
|
|
|
|
+ // Paper end
|
2017-05-13 12:51:09 +02:00
|
|
|
|
2019-04-27 08:26:04 +02:00
|
|
|
// Spigot Start
|
|
|
|
private static double calcTps(double avg, double exp, double tps)
|
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
|
2020-05-12 07:27:03 +02:00
|
|
|
index a97941d1f8a125c6d033b1d359c1527937f97799..940c3acfa0dee0d454a1cc10dd3ca3862fd7b030 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
|
2020-05-12 07:27:03 +02:00
|
|
|
@@ -1031,10 +1031,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
|
2019-04-27 08:26:04 +02:00
|
|
|
public void shutdown() {
|
|
|
|
+ this.shutdown(false);
|
2017-05-14 15:49:56 +02:00
|
|
|
+ }
|
|
|
|
+
|
2019-04-27 08:26:04 +02:00
|
|
|
+ public void shutdown(boolean isRestarting) {
|
2017-05-14 15:49:56 +02:00
|
|
|
// 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
|
2019-12-12 00:43:22 +01:00
|
|
|
|
2020-05-12 07:27:03 +02:00
|
|
|
@@ -1046,6 +1051,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
|
2020-05-06 11:48:49 +02:00
|
|
|
index ccea803f58e09067cc998c62ffa134d6604878ff..aefea3a9a8b9b75c62bd20018be7cd166a213001 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-04-27 08:26:04 +02:00
|
|
|
@@ -46,86 +46,134 @@ 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-04-27 08:26:04 +02:00
|
|
|
+ boolean isRestarting = addShutdownHook( restartScript );
|
|
|
|
+ if ( isRestarting )
|
|
|
|
{
|
|
|
|
- System.out.println( "Attempting to restart with " + restartScript );
|
|
|
|
+ System.out.println( "Attempting to restart with " + SpigotConfig.restartScript );
|
|
|
|
+ } else
|
|
|
|
+ {
|
2017-05-14 15:49:56 +02:00
|
|
|
+ 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();
|
2019-04-27 08:26:04 +02:00
|
|
|
|
|
|
|
- // Disable Watchdog
|
|
|
|
- WatchdogThread.doStop();
|
|
|
|
+ shutdownServer( isRestarting );
|
|
|
|
+ // Paper end
|
2017-06-09 02:46:54 +02:00
|
|
|
+ } catch ( Exception ex )
|
|
|
|
+ {
|
|
|
|
+ ex.printStackTrace();
|
|
|
|
+ }
|
|
|
|
+ }
|
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();
|
2019-04-27 08:26:04 +02:00
|
|
|
+ // 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 ) )
|
|
|
|
+ {
|
|
|
|
+ p.playerConnection.disconnect(SpigotConfig.restartMessage);
|
|
|
|
+ }
|
|
|
|
+ // Give the socket a chance to send the packets
|
2017-06-09 02:46:54 +02:00
|
|
|
+ try
|
|
|
|
+ {
|
2019-04-27 08:26:04 +02:00
|
|
|
+ Thread.sleep( 100 );
|
|
|
|
+ } catch ( InterruptedException ex )
|
2017-06-09 02:46:54 +02:00
|
|
|
+ {
|
|
|
|
+ }
|
|
|
|
|
2017-05-13 06:49:59 +02:00
|
|
|
- // Give time for it to kick in
|
|
|
|
- try
|
|
|
|
- {
|
|
|
|
- Thread.sleep( 100 );
|
|
|
|
- } catch ( InterruptedException ex )
|
|
|
|
- {
|
|
|
|
- }
|
2019-04-27 08:26:04 +02:00
|
|
|
+ closeSocket();
|
2017-05-14 15:49:56 +02:00
|
|
|
|
2017-05-13 06:49:59 +02:00
|
|
|
- // Actually shutdown
|
|
|
|
- try
|
|
|
|
- {
|
2019-04-27 08:26:04 +02:00
|
|
|
- MinecraftServer.getServer().close();
|
2017-05-13 06:49:59 +02:00
|
|
|
- } catch ( Throwable t )
|
|
|
|
- {
|
|
|
|
- }
|
2019-04-27 08:26:04 +02:00
|
|
|
+ // Actually shutdown
|
|
|
|
+ try
|
|
|
|
+ {
|
|
|
|
+ MinecraftServer.getServer().close(); // calls stop()
|
|
|
|
+ } catch ( Throwable t )
|
|
|
|
+ {
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Actually stop the JVM
|
|
|
|
+ System.exit( 0 );
|
2017-05-14 15:49:56 +02:00
|
|
|
|
|
|
|
- // This will be done AFTER the server has completely halted
|
|
|
|
- Thread shutdownHook = new Thread()
|
2019-04-27 08:26:04 +02:00
|
|
|
+ } else
|
|
|
|
+ {
|
|
|
|
+ // Mark the server to shutdown at the end of the tick
|
|
|
|
+ MinecraftServer.getServer().safeShutdown( false, isRestarting );
|
|
|
|
+
|
2019-01-01 04:15:55 +01:00
|
|
|
+ // wait 10 seconds to see if we're actually going to try shutdown
|
|
|
|
+ try
|
|
|
|
+ {
|
2019-04-27 08:26:04 +02:00
|
|
|
+ Thread.sleep( 10000 );
|
2019-01-01 04:15:55 +01:00
|
|
|
+ }
|
|
|
|
+ catch (InterruptedException ignored)
|
|
|
|
+ {
|
2019-04-27 08:26:04 +02:00
|
|
|
+ }
|
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();
|
2019-04-27 08:26:04 +02:00
|
|
|
+ System.exit( 0 );
|
2017-05-13 06:49:59 +02:00
|
|
|
+ }
|
|
|
|
+ }
|
2019-04-27 08:26:04 +02:00
|
|
|
+ // Paper end
|
2017-05-13 06:49:59 +02:00
|
|
|
+
|
|
|
|
+ // Paper - Split from moved code
|
2019-04-27 08:26:04 +02:00
|
|
|
+ private static void closeSocket()
|
|
|
|
+ {
|
2017-05-13 06:49:59 +02:00
|
|
|
+ // Close the socket so we can rebind with the new process
|
|
|
|
+ MinecraftServer.getServer().getServerConnection().b();
|
|
|
|
+
|
|
|
|
+ // Give time for it to kick in
|
|
|
|
+ try
|
2019-04-27 08:26:04 +02:00
|
|
|
+ {
|
2017-05-13 06:49:59 +02:00
|
|
|
+ Thread.sleep( 100 );
|
|
|
|
+ } catch ( InterruptedException ex )
|
|
|
|
+ {
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ // Paper end
|
2017-05-14 15:49:56 +02:00
|
|
|
+
|
2019-04-27 08:26:04 +02:00
|
|
|
+ // Paper start - copied from above and modified to return if the hook registered
|
|
|
|
+ private static boolean addShutdownHook(String restartScript)
|
|
|
|
+ {
|
2019-01-01 04:15:55 +01:00
|
|
|
+ String[] split = restartScript.split( " " );
|
|
|
|
+ if ( split.length > 0 && new File( split[0] ).isFile() )
|
|
|
|
+ {
|
2019-04-27 08:26:04 +02:00
|
|
|
+ Thread shutdownHook = new Thread()
|
|
|
|
+ {
|
2017-05-14 15:49:56 +02:00
|
|
|
+ @Override
|
2019-04-27 08:26:04 +02:00
|
|
|
+ public void run()
|
|
|
|
{
|
|
|
|
- @Override
|
|
|
|
- public void run()
|
|
|
|
+ try
|
|
|
|
{
|
|
|
|
- try
|
|
|
|
+ String os = System.getProperty( "os.name" ).toLowerCase(java.util.Locale.ENGLISH);
|
|
|
|
+ if ( os.contains( "win" ) )
|
|
|
|
{
|
|
|
|
- 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 )
|
|
|
|
+ Runtime.getRuntime().exec( "cmd /c start " + restartScript );
|
|
|
|
+ } else
|
|
|
|
{
|
|
|
|
- e.printStackTrace();
|
2019-01-01 04:15:55 +01:00
|
|
|
+ Runtime.getRuntime().exec( "sh " + restartScript );
|
2019-04-27 08:26:04 +02:00
|
|
|
}
|
|
|
|
+ } catch ( Exception e )
|
|
|
|
+ {
|
2017-05-14 15:49:56 +02:00
|
|
|
+ e.printStackTrace();
|
2019-04-27 08:26:04 +02:00
|
|
|
}
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- 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 )
|
2017-05-14 15:49:56 +02:00
|
|
|
+ };
|
|
|
|
+
|
2019-04-27 08:26:04 +02:00
|
|
|
+ shutdownHook.setDaemon( true );
|
|
|
|
+ Runtime.getRuntime().addShutdownHook( shutdownHook );
|
2017-05-14 15:49:56 +02:00
|
|
|
+ return true;
|
2019-04-27 08:26:04 +02:00
|
|
|
+ } else
|
|
|
|
{
|
|
|
|
- ex.printStackTrace();
|
2017-05-14 15:49:56 +02:00
|
|
|
+ return false;
|
|
|
|
}
|
|
|
|
}
|
2019-04-27 08:26:04 +02:00
|
|
|
+ // Paper end
|
|
|
|
+
|
2017-05-13 06:49:59 +02:00
|
|
|
}
|