2021-06-11 14:02:28 +02:00
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Sun, 12 Apr 2020 15:50:48 -0400
Subject: [PATCH] Improved Watchdog Support
Forced Watchdog Crash support and Improve Async Shutdown
If the request to shut down the server is received while we are in
a watchdog hang, immediately treat it as a crash and begin the shutdown
process. Shutdown process is now improved to also shutdown cleanly when
not using restart scripts either.
If a server is deadlocked, a server owner can send SIGUP (or any other signal
the JVM understands to shut down as it currently does) and the watchdog
will no longer need to wait until the full timeout, allowing you to trigger
a close process and try to shut the server down gracefully, saving player and
world data.
Previously there was no way to trigger this outside of waiting for a full watchdog
timeout, which may be set to a really long time...
Additionally, fix everything to do with shutting the server down asynchronously.
Previously, nearly everything about the process was fragile and unsafe. Main might
not have actually been frozen, and might still be manipulating state.
Or, some reuest might ask main to do something in the shutdown but main is dead.
Or worse, other things might start closing down items such as the Console or Thread Pool
before we are fully shutdown.
This change tries to resolve all of these issues by moving everything into the stop
method and guaranteeing only one thread is stopping the server.
We then issue Thread Death to the main thread of another thread initiates the stop process.
We have to ensure Thread Death propagates correctly though to stop main completely.
This is to ensure that if main isn't truely stuck, it's not manipulating state we are trying to save.
This also moves all plugins who register "delayed init" tasks to occur just before "Done" so they
are properly accounted for and wont trip watchdog on init.
2024-12-17 13:32:46 +01:00
diff --git a/io/papermc/paper/util/LogManagerShutdownThread.java b/io/papermc/paper/util/LogManagerShutdownThread.java
2024-07-29 17:10:53 +02:00
new file mode 100644
2024-12-17 13:32:46 +01:00
index 0000000000000000000000000000000000000000..3d7df554b89cff23f64da7ad48b5e4d26ac2baf7
2024-07-29 17:10:53 +02:00
--- /dev/null
2024-12-17 13:32:46 +01:00
+++ b/io/papermc/paper/util/LogManagerShutdownThread.java
@@ -0,0 +1,29 @@
2024-07-29 17:10:53 +02:00
+package io.papermc.paper.util;
+
2024-12-17 13:32:46 +01:00
+import org.apache.logging.log4j.LogManager;
+
+public final class LogManagerShutdownThread extends Thread {
2024-07-29 17:10:53 +02:00
+
+ static LogManagerShutdownThread INSTANCE = new LogManagerShutdownThread();
2024-12-17 13:32:46 +01:00
+
+ public static void hook() {
2024-07-29 17:10:53 +02:00
+ if (INSTANCE == null) {
+ throw new IllegalStateException("Cannot re-hook after being unhooked");
+ }
+ Runtime.getRuntime().addShutdownHook(INSTANCE);
+ }
+
2024-12-17 13:32:46 +01:00
+ public static void unhook() {
2024-07-29 17:10:53 +02:00
+ Runtime.getRuntime().removeShutdownHook(INSTANCE);
+ INSTANCE = null;
+ }
+
+ private LogManagerShutdownThread() {
+ super("Log4j2 Shutdown Thread");
+ }
+
+ @Override
+ public void run() {
2024-12-17 13:32:46 +01:00
+ LogManager.shutdown();
2024-07-29 17:10:53 +02:00
+ }
+}
2024-12-17 13:32:46 +01:00
diff --git a/net/minecraft/CrashReport.java b/net/minecraft/CrashReport.java
index 3e0e88afcf010d9a3d46e48bca5cbdf98fe97544..8bd7999c17c8772451f873966f8c90969aee1482 100644
--- a/net/minecraft/CrashReport.java
+++ b/net/minecraft/CrashReport.java
@@ -205,6 +205,7 @@ public class CrashReport {
2021-06-11 14:02:28 +02:00
}
2024-12-17 13:32:46 +01:00
public static CrashReport forThrowable(Throwable cause, String description) {
2021-06-11 14:02:28 +02:00
+ if (cause instanceof ThreadDeath) com.destroystokyo.paper.util.SneakyThrow.sneaky(cause); // Paper
while (cause instanceof CompletionException && cause.getCause() != null) {
cause = cause.getCause();
}
2024-12-17 13:32:46 +01:00
diff --git a/net/minecraft/server/Main.java b/net/minecraft/server/Main.java
index 4437283a5d157eede121b98be0112c1067eded5e..fc9ec242743f755a1f0c9ec6bccd11c82375d655 100644
--- a/net/minecraft/server/Main.java
+++ b/net/minecraft/server/Main.java
@@ -68,6 +68,7 @@ public class Main {
)
2024-07-29 17:10:53 +02:00
@DontObfuscate
2024-12-17 13:32:46 +01:00
public static void main(final OptionSet optionSet) { // CraftBukkit - replaces main(String[] args)
2024-07-29 17:10:53 +02:00
+ io.papermc.paper.util.LogManagerShutdownThread.hook(); // Paper
SharedConstants.tryDetectVersion();
/* CraftBukkit start - Replace everything
2024-12-17 13:32:46 +01:00
OptionParser optionParser = new OptionParser();
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
2024-12-17 13:39:42 +01:00
index 22dc6bec58702762e4a31415f9aed2df2b3ad0d6..73704871594ed7372d2b9dc332051cae741beb75 100644
2024-12-17 13:32:46 +01:00
--- a/net/minecraft/server/MinecraftServer.java
+++ b/net/minecraft/server/MinecraftServer.java
2024-12-17 13:39:42 +01:00
@@ -302,6 +302,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
2024-01-23 12:06:27 +01:00
public boolean isIteratingOverLevels = false; // Paper - Throw exception on world create while being ticked
2024-12-05 11:18:29 +01:00
private final Set<String> pluginsBlockingSleep = new java.util.HashSet<>(); // Paper - API to allow/disallow tick sleeping
2024-12-17 13:32:46 +01:00
public static final long SERVER_INIT = System.nanoTime(); // Paper - Lag compensation
2021-06-11 14:02:28 +02:00
+ public volatile Thread shutdownThread; // Paper
2024-12-17 13:32:46 +01:00
+ public volatile boolean abnormalExit; // Paper
public static <S extends MinecraftServer> S spin(Function<Thread, S> threadFunction) {
2024-12-03 23:21:36 +01:00
ca.spottedleaf.dataconverter.minecraft.datatypes.MCTypeRegistry.init(); // Paper - rewrite data converter system
2024-12-17 13:39:42 +01:00
@@ -395,6 +397,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
2024-07-29 17:10:53 +02:00
}
*/
// Paper end
+ io.papermc.paper.util.LogManagerShutdownThread.unhook(); // Paper
Runtime.getRuntime().addShutdownHook(new org.bukkit.craftbukkit.util.ServerShutdownThread(this));
// CraftBukkit end
this.paperConfigurations = services.paperConfigurations(); // Paper - add paper configuration files
2024-12-17 13:39:42 +01:00
@@ -879,6 +882,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
2021-06-11 14:02:28 +02:00
// CraftBukkit start
private boolean hasStopped = false;
2024-01-23 12:06:27 +01:00
private boolean hasLoggedStop = false; // Paper - Debugging
2021-06-11 14:02:28 +02:00
+ public volatile boolean hasFullyShutdown = false; // Paper
private final Object stopLock = new Object();
public final boolean hasStopped() {
2021-06-14 03:06:38 +02:00
synchronized (this.stopLock) {
2024-12-17 13:39:42 +01:00
@@ -894,6 +898,10 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
2021-06-14 03:06:38 +02:00
this.hasStopped = true;
2021-06-11 14:02:28 +02:00
}
2024-01-23 12:06:27 +01:00
if (!hasLoggedStop && isDebugging()) io.papermc.paper.util.TraceUtil.dumpTraceForThread("Server stopped"); // Paper - Debugging
2021-06-11 14:02:28 +02:00
+ // Paper start - kill main thread, and kill it hard
+ shutdownThread = Thread.currentThread();
+ org.spigotmc.WatchdogThread.doStop(); // Paper
+ // Paper end
// CraftBukkit end
2022-06-08 06:22:42 +02:00
if (this.metricsRecorder.isRecording()) {
this.cancelRecordingMetrics();
2024-12-17 13:39:42 +01:00
@@ -966,6 +974,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
2024-12-17 13:32:46 +01:00
this.getProfileCache().save(false); // Paper - Perf: Async GameProfileCache saving
2021-06-11 14:02:28 +02:00
}
2024-12-17 13:32:46 +01:00
// Spigot end
2024-10-26 03:13:48 +02:00
+ // Paper start - Improved watchdog support - move final shutdown items here
2024-06-17 13:36:43 +02:00
+ Util.shutdownExecutors();
2021-06-11 14:02:28 +02:00
+ try {
+ net.minecrell.terminalconsole.TerminalConsoleAppender.close(); // Paper - Use TerminalConsoleAppender
2024-06-17 13:36:43 +02:00
+ } catch (final Exception ignored) {
2021-06-11 14:02:28 +02:00
+ }
2024-01-23 12:06:27 +01:00
+ io.papermc.paper.log.CustomLogManager.forceReset(); // Paper - Reset loggers after shutdown
2021-06-11 14:02:28 +02:00
+ this.onServerExit();
2024-10-26 03:13:48 +02:00
+ // Paper end - Improved watchdog support - move final shutdown items here
2021-06-11 14:02:28 +02:00
}
public String getLocalIp() {
2024-12-17 13:39:42 +01:00
@@ -1058,6 +1075,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
2021-06-11 14:02:28 +02:00
protected void runServer() {
try {
+ long serverStartTime = Util.getNanos(); // Paper
2022-06-08 06:22:42 +02:00
if (!this.initServer()) {
throw new IllegalStateException("Failed to initialize server");
}
2024-12-17 13:39:42 +01:00
@@ -1068,6 +1086,17 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
2021-06-11 14:02:28 +02:00
2024-12-05 11:18:29 +01:00
this.server.spark.enableBeforePlugins(); // Paper - spark
2022-06-08 06:22:42 +02:00
// Spigot start
2024-07-19 21:36:09 +02:00
+ // Paper start - Improved Watchdog Support
2022-06-08 06:22:42 +02:00
+ LOGGER.info("Running delayed init tasks");
2024-10-26 03:13:48 +02:00
+ this.server.getScheduler().mainThreadHeartbeat(); // run all 1 tick delay tasks during init,
2022-06-08 06:22:42 +02:00
+ // this is going to be the first thing the tick process does anyways, so move done and run it after
+ // everything is init before watchdog tick.
+ // anything at 3+ won't be caught here but also will trip watchdog....
+ // tasks are default scheduled at -1 + delay, and first tick will tick at 1
2024-07-19 00:33:14 +02:00
+ final long actualDoneTimeMs = System.currentTimeMillis() - org.bukkit.craftbukkit.Main.BOOT_TIME.toEpochMilli(); // Paper - Add total time
2024-07-19 21:36:09 +02:00
+ LOGGER.info("Done ({})! For help, type \"help\"", String.format(java.util.Locale.ROOT, "%.3fs", actualDoneTimeMs / 1000.00D)); // Paper - Add total time
+ org.spigotmc.WatchdogThread.tick();
+ // Paper end - Improved Watchdog Support
2022-06-08 06:22:42 +02:00
org.spigotmc.WatchdogThread.hasStarted = true; // Paper
2023-10-27 01:34:58 +02:00
Arrays.fill( this.recentTps, 20 );
2024-01-25 10:54:46 +01:00
// Paper start - further improve server tick loop
2024-12-17 13:39:42 +01:00
@@ -1157,6 +1186,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
2023-12-05 23:55:31 +01:00
JvmProfiler.INSTANCE.onServerTick(this.smoothedTickTimeMillis);
2021-06-11 14:02:28 +02:00
}
2024-12-17 13:32:46 +01:00
} catch (Throwable var69) {
2021-06-11 14:02:28 +02:00
+ // Paper start
2024-12-17 13:32:46 +01:00
+ if (var69 instanceof ThreadDeath) {
+ MinecraftServer.LOGGER.error("Main thread terminated by WatchDog due to hard crash", var69);
2021-06-11 14:02:28 +02:00
+ return;
+ }
+ // Paper end
2024-12-17 13:32:46 +01:00
LOGGER.error("Encountered an unexpected exception", var69);
CrashReport crashReport = constructOrExtractCrashReport(var69);
this.fillSystemReport(crashReport.getSystemReport());
2024-12-17 13:39:42 +01:00
@@ -1179,15 +1214,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
2022-06-08 06:22:42 +02:00
this.services.profileCache().clearExecutor();
2021-11-24 12:06:34 +01:00
}
2021-06-11 14:02:28 +02:00
- org.spigotmc.WatchdogThread.doStop(); // Spigot
+ //org.spigotmc.WatchdogThread.doStop(); // Spigot // Paper - move into stop
// CraftBukkit start - Restore terminal to original settings
try {
- net.minecrell.terminalconsole.TerminalConsoleAppender.close(); // Paper - Use TerminalConsoleAppender
+ //net.minecrell.terminalconsole.TerminalConsoleAppender.close(); // Paper - Move into stop
} catch (Exception ignored) {
}
// CraftBukkit end
2024-01-23 12:06:27 +01:00
- io.papermc.paper.log.CustomLogManager.forceReset(); // Paper - Reset loggers after shutdown
2021-06-11 14:02:28 +02:00
- this.onServerExit();
2024-01-23 12:06:27 +01:00
+ //io.papermc.paper.log.CustomLogManager.forceReset(); // Paper - Reset loggers after shutdown
2021-11-24 12:06:34 +01:00
+ //this.onServerExit(); // Paper - moved into stop
2021-06-11 14:02:28 +02:00
}
}
2024-12-17 13:32:46 +01:00
}
2024-12-17 13:39:42 +01:00
@@ -1291,6 +1326,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
2021-06-11 14:02:28 +02:00
@Override
public TickTask wrapRunnable(Runnable runnable) {
+ // Paper start - anything that does try to post to main during watchdog crash, run on watchdog
+ if (this.hasStopped && Thread.currentThread().equals(shutdownThread)) {
+ runnable.run();
+ runnable = () -> {};
+ }
+ // Paper end
return new TickTask(this.tickCount, runnable);
}
2024-12-17 13:39:42 +01:00
@@ -2087,7 +2128,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
2024-12-17 13:32:46 +01:00
this.resources.managers.updateStaticRegistryTags();
this.resources.managers.getRecipeManager().finalizeRecipeLoading(this.worldData.enabledFeatures());
this.potionBrewing = this.potionBrewing.reload(this.worldData.enabledFeatures()); // Paper - Custom Potion Mixes
- this.getPlayerList().saveAll();
+ // Paper start
+ if (Thread.currentThread() != this.serverThread) {
+ return;
+ }
+ // this.getPlayerList().saveAll(); // Paper - we don't need to save everything, just advancements // TODO Move this to a different patch
+ for (ServerPlayer player : this.getPlayerList().getPlayers()) {
+ player.getAdvancements().save();
+ }
+ // Paper end
this.getPlayerList().reloadResources();
this.functionManager.replaceLibrary(this.resources.managers.getFunctionLibrary());
this.structureTemplateManager.onResourceManagerReload(this.resources.resourceManager);
diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java
2024-12-17 13:39:42 +01:00
index 118f4ebb617d304e9a1cac2f9a853dc219a42456..ef39268caa59836506928582e88bc81e9fb22e88 100644
2024-12-17 13:32:46 +01:00
--- a/net/minecraft/server/dedicated/DedicatedServer.java
+++ b/net/minecraft/server/dedicated/DedicatedServer.java
@@ -322,7 +322,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
this.loadLevel(this.storageSource.getLevelId()); // CraftBukkit
long l = Util.getNanos() - nanos;
String string = String.format(Locale.ROOT, "%.3fs", l / 1.0E9);
- LOGGER.info("Done ({})! For help, type \"help\"", string);
+ LOGGER.info("Done preparing level \"{}\" ({})", this.getLevelIdName(), string); // Paper - clarify startup log messages & add total time
if (properties.announcePlayerAchievements != null) {
this.getGameRules().getRule(GameRules.RULE_ANNOUNCE_ADVANCEMENTS).set(properties.announcePlayerAchievements, this.overworld()); // CraftBukkit - per-world
2021-06-11 14:02:28 +02:00
}
2024-12-17 13:39:42 +01:00
@@ -419,7 +419,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
2021-06-11 14:02:28 +02:00
}
2024-12-17 13:39:42 +01:00
this.hasFullyShutdown = true; // Paper - Improved watchdog support
2021-06-11 14:02:28 +02:00
- System.exit(0); // CraftBukkit
+ System.exit(this.abnormalExit ? 70 : 0); // CraftBukkit // Paper
}
@Override
2024-12-17 13:39:42 +01:00
@@ -727,7 +727,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
2021-06-11 14:02:28 +02:00
@Override
public void stopServer() {
super.stopServer();
- Util.shutdownExecutors();
2021-11-24 12:06:34 +01:00
+ //Util.shutdownExecutors(); // Paper - moved into super
SkullBlockEntity.clear();
2021-06-11 14:02:28 +02:00
}
2024-12-17 13:32:46 +01:00
diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
index d227714de0fe13544779fae6cf0e9ff6af5469c7..393bd2ec0962d3870f5b4cb74200e5467b50cdb8 100644
--- a/net/minecraft/server/players/PlayerList.java
+++ b/net/minecraft/server/players/PlayerList.java
@@ -513,7 +513,7 @@ public abstract class PlayerList {
2021-06-14 03:06:38 +02:00
this.cserver.getPluginManager().callEvent(playerQuitEvent);
2024-12-17 13:32:46 +01:00
player.getBukkitEntity().disconnect(playerQuitEvent.getQuitMessage());
2021-06-11 14:02:28 +02:00
2024-12-17 13:32:46 +01:00
- player.doTick(); // SPIGOT-924
+ if (this.server.isSameThread()) player.doTick(); // SPIGOT-924 // Paper - don't tick during emergency shutdowns (Watchdog)
2021-06-11 14:02:28 +02:00
// CraftBukkit end
2024-01-23 14:34:17 +01:00
// Paper start - Configurable player collision; Remove from collideRule team if needed
2024-12-17 13:32:46 +01:00
diff --git a/net/minecraft/util/thread/BlockableEventLoop.java b/net/minecraft/util/thread/BlockableEventLoop.java
index 186c1b2e3599770385150eb7acdcd890aa5835eb..bfea9a2ae5e0bd5dae2873f715d192dfcbe97ee5 100644
--- a/net/minecraft/util/thread/BlockableEventLoop.java
+++ b/net/minecraft/util/thread/BlockableEventLoop.java
@@ -169,6 +169,6 @@ public abstract class BlockableEventLoop<R extends Runnable> implements Profiler
public static boolean isNonRecoverable(Throwable error) {
return error instanceof ReportedException reportedException
2024-10-26 03:13:48 +02:00
? isNonRecoverable(reportedException.getCause())
2024-12-17 13:32:46 +01:00
- : error instanceof OutOfMemoryError || error instanceof StackOverflowError;
+ : error instanceof OutOfMemoryError || error instanceof StackOverflowError || error instanceof ThreadDeath; // Paper
2024-04-12 21:14:06 +02:00
}
2024-10-26 03:13:48 +02:00
}
2024-12-17 13:32:46 +01:00
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
index cb6ca60af3d3f90501e4693a78466b9f7462362d..127e25dab3a5e4df9cdf8eefd0485ea07b7696d9 100644
--- a/net/minecraft/world/level/Level.java
+++ b/net/minecraft/world/level/Level.java
@@ -863,6 +863,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
2021-06-11 14:02:28 +02:00
try {
2024-12-17 13:32:46 +01:00
consumerEntity.accept(entity);
} catch (Throwable var6) {
+ if (var6 instanceof ThreadDeath) throw var6; // Paper
2024-01-24 11:45:17 +01:00
// Paper start - Prevent block entity and entity crashes
2023-06-08 00:41:25 +02:00
final String msg = String.format("Entity threw exception at %s:%s,%s,%s", entity.level().getWorld().getName(), entity.getX(), entity.getY(), entity.getZ());
2024-12-17 13:32:46 +01:00
MinecraftServer.LOGGER.error(msg, var6);
diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java
index d1d0dc13eecb0e0eb3a7839b570a5fe7f62f3fba..205f5a687eb685284a2e403f3eb6bdc694fc5423 100644
--- a/net/minecraft/world/level/chunk/LevelChunk.java
+++ b/net/minecraft/world/level/chunk/LevelChunk.java
@@ -855,6 +855,7 @@ public class LevelChunk extends ChunkAccess {
2021-06-14 03:06:38 +02:00
2024-12-17 13:32:46 +01:00
profilerFiller.pop();
} catch (Throwable var5) {
+ if (var5 instanceof ThreadDeath) throw var5; // Paper
2024-01-24 11:45:17 +01:00
// Paper start - Prevent block entity and entity crashes
2021-06-21 10:09:18 +02:00
final String msg = String.format("BlockEntity threw exception at %s:%s,%s,%s", LevelChunk.this.getLevel().getWorld().getName(), this.getPos().getX(), this.getPos().getY(), this.getPos().getZ());
2024-12-17 13:32:46 +01:00
net.minecraft.server.MinecraftServer.LOGGER.error(msg, var5);