From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Aikar 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. diff --git a/io/papermc/paper/util/LogManagerShutdownThread.java b/io/papermc/paper/util/LogManagerShutdownThread.java new file mode 100644 index 0000000000000000000000000000000000000000..3d7df554b89cff23f64da7ad48b5e4d26ac2baf7 --- /dev/null +++ b/io/papermc/paper/util/LogManagerShutdownThread.java @@ -0,0 +1,29 @@ +package io.papermc.paper.util; + +import org.apache.logging.log4j.LogManager; + +public final class LogManagerShutdownThread extends Thread { + + static LogManagerShutdownThread INSTANCE = new LogManagerShutdownThread(); + + public static void hook() { + if (INSTANCE == null) { + throw new IllegalStateException("Cannot re-hook after being unhooked"); + } + Runtime.getRuntime().addShutdownHook(INSTANCE); + } + + public static void unhook() { + Runtime.getRuntime().removeShutdownHook(INSTANCE); + INSTANCE = null; + } + + private LogManagerShutdownThread() { + super("Log4j2 Shutdown Thread"); + } + + @Override + public void run() { + LogManager.shutdown(); + } +} 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 { } public static CrashReport forThrowable(Throwable cause, String description) { + if (cause instanceof ThreadDeath) com.destroystokyo.paper.util.SneakyThrow.sneaky(cause); // Paper while (cause instanceof CompletionException && cause.getCause() != null) { cause = cause.getCause(); } diff --git a/net/minecraft/server/Main.java b/net/minecraft/server/Main.java index e738405e5112584e02e01df2d5ede2676fa1bffb..560d80cb1177297210646b44ce25fd2fa3766d40 100644 --- a/net/minecraft/server/Main.java +++ b/net/minecraft/server/Main.java @@ -68,6 +68,7 @@ public class Main { ) @DontObfuscate public static void main(final OptionSet optionSet) { // CraftBukkit - replaces main(String[] args) + io.papermc.paper.util.LogManagerShutdownThread.hook(); // Paper SharedConstants.tryDetectVersion(); /* CraftBukkit start - Replace everything OptionParser optionParser = new OptionParser(); diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java index ddd23354d68f5cbdc9f72c11246ab26a6c0bbe16..3f880bdfd95f7556d1d76e4602a6d24dda78438c 100644 --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java @@ -298,6 +298,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop pluginsBlockingSleep = new java.util.HashSet<>(); // Paper - API to allow/disallow tick sleeping @@ -466,6 +467,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop {}; + } + // Paper end return new TickTask(this.tickCount, runnable); } @@ -2184,7 +2223,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop implements Profiler public static boolean isNonRecoverable(Throwable error) { return error instanceof ReportedException reportedException ? isNonRecoverable(reportedException.getCause()) - : error instanceof OutOfMemoryError || error instanceof StackOverflowError; + : error instanceof OutOfMemoryError || error instanceof StackOverflowError || error instanceof ThreadDeath; // Paper } } diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java index 2c7b6034852216fc5aa5c3f42a70ebd8e8317a17..3bf79eedfc358f54bfe23b5a75b3ad121558f6c6 100644 --- a/net/minecraft/world/level/Level.java +++ b/net/minecraft/world/level/Level.java @@ -1498,6 +1498,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl try { consumerEntity.accept(entity); } catch (Throwable var6) { + if (var6 instanceof ThreadDeath) throw var6; // Paper // Paper start - Prevent block entity and entity crashes final String msg = String.format("Entity threw exception at %s:%s,%s,%s", entity.level().getWorld().getName(), entity.getX(), entity.getY(), entity.getZ()); MinecraftServer.LOGGER.error(msg, var6); diff --git a/net/minecraft/world/level/chunk/LevelChunk.java b/net/minecraft/world/level/chunk/LevelChunk.java index c1ae7755e8d6fa8501d2210dab7605d993c55722..b10890c5a7e42163e419e74596b952525c3ed3eb 100644 --- a/net/minecraft/world/level/chunk/LevelChunk.java +++ b/net/minecraft/world/level/chunk/LevelChunk.java @@ -929,6 +929,7 @@ public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.p profilerFiller.pop(); } catch (Throwable var5) { + if (var5 instanceof ThreadDeath) throw var5; // Paper // Paper start - Prevent block entity and entity crashes 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()); net.minecraft.server.MinecraftServer.LOGGER.error(msg, var5);