aboutsummaryrefslogtreecommitdiffhomepage
path: root/patches/server/0370-Improved-Watchdog-Support.patch
diff options
context:
space:
mode:
authorJake Potrebic <[email protected]>2023-06-08 21:29:58 -0700
committerGitHub <[email protected]>2023-06-08 21:29:58 -0700
commit82c64790f43239051625385e3e72b26fe17626e1 (patch)
tree69567a33f5307dc8dd09e2d44c1e91b47214667e /patches/server/0370-Improved-Watchdog-Support.patch
parente829a9d8f3a52a4803eaaa96a046c4a4876a262a (diff)
downloadPaper-82c64790f43239051625385e3e72b26fe17626e1.tar.gz
Paper-82c64790f43239051625385e3e72b26fe17626e1.zip
Add back Anti-Xray patch (#9283)
Diffstat (limited to 'patches/server/0370-Improved-Watchdog-Support.patch')
-rw-r--r--patches/server/0370-Improved-Watchdog-Support.patch549
1 files changed, 0 insertions, 549 deletions
diff --git a/patches/server/0370-Improved-Watchdog-Support.patch b/patches/server/0370-Improved-Watchdog-Support.patch
deleted file mode 100644
index f8e0975ee3..0000000000
--- a/patches/server/0370-Improved-Watchdog-Support.patch
+++ /dev/null
@@ -1,549 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-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/src/main/java/com/destroystokyo/paper/Metrics.java b/src/main/java/com/destroystokyo/paper/Metrics.java
-index 6aaed8e8bf8c721fc834da5c76ac72a4c3e92458..4b002e8b75d117b726b0de274a76d3596fce015b 100644
---- a/src/main/java/com/destroystokyo/paper/Metrics.java
-+++ b/src/main/java/com/destroystokyo/paper/Metrics.java
-@@ -92,7 +92,12 @@ public class Metrics {
- * Starts the Scheduler which submits our data every 30 minutes.
- */
- private void startSubmitting() {
-- final Runnable submitTask = this::submitData;
-+ final Runnable submitTask = () -> {
-+ if (MinecraftServer.getServer().hasStopped()) {
-+ return;
-+ }
-+ submitData();
-+ };
-
- // Many servers tend to restart at a fixed time at xx:00 which causes an uneven distribution of requests on the
- // bStats backend. To circumvent this problem, we introduce some randomness into the initial and second delay.
-diff --git a/src/main/java/net/minecraft/CrashReport.java b/src/main/java/net/minecraft/CrashReport.java
-index 336795dff742b7c6957fbd3476aff31d25a5e659..30a58229aa6dac5039511d0c0df5f2912ea7de9f 100644
---- a/src/main/java/net/minecraft/CrashReport.java
-+++ b/src/main/java/net/minecraft/CrashReport.java
-@@ -230,6 +230,7 @@ public class CrashReport {
- }
-
- public static CrashReport forThrowable(Throwable cause, String title) {
-+ 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/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
-index b2b7f5f30b3967a9f8a32e27557639da7d408152..8fc669ec3b6afa4ed9447878604ba2b2a2c11678 100644
---- a/src/main/java/net/minecraft/server/MinecraftServer.java
-+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
-@@ -299,7 +299,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
- public java.util.Queue<Runnable> processQueue = new java.util.concurrent.ConcurrentLinkedQueue<Runnable>();
- public int autosavePeriod;
- public Commands vanillaCommandDispatcher;
-- private boolean forceTicks;
-+ public boolean forceTicks; // Paper
- // CraftBukkit end
- // Spigot start
- public static final int TPS = 20;
-@@ -310,6 +310,9 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
- public final io.papermc.paper.configuration.PaperConfigurations paperConfigurations;
- public static long currentTickLong = 0L; // Paper
-
-+ public volatile Thread shutdownThread; // Paper
-+ public volatile boolean abnormalExit = false; // Paper
-+
- public static <S extends MinecraftServer> S spin(Function<Thread, S> serverFactory) {
- AtomicReference<S> atomicreference = new AtomicReference();
- Thread thread = new io.papermc.paper.util.TickThread(() -> { // Paper - rewrite chunk system
-@@ -886,6 +889,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
-
- // CraftBukkit start
- private boolean hasStopped = false;
-+ public volatile boolean hasFullyShutdown = false; // Paper
- private final Object stopLock = new Object();
- public final boolean hasStopped() {
- synchronized (this.stopLock) {
-@@ -900,6 +904,19 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
- if (this.hasStopped) return;
- this.hasStopped = true;
- }
-+ // Paper start - kill main thread, and kill it hard
-+ shutdownThread = Thread.currentThread();
-+ org.spigotmc.WatchdogThread.doStop(); // Paper
-+ if (!isSameThread()) {
-+ MinecraftServer.LOGGER.info("Stopping main thread (Ignore any thread death message you see! - DO NOT REPORT THREAD DEATH TO PAPER)");
-+ while (this.getRunningThread().isAlive()) {
-+ this.getRunningThread().stop();
-+ try {
-+ Thread.sleep(1);
-+ } catch (InterruptedException e) {}
-+ }
-+ }
-+ // Paper end
- // CraftBukkit end
- if (this.metricsRecorder.isRecording()) {
- this.cancelRecordingMetrics();
-@@ -956,7 +973,19 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
- this.getProfileCache().save(false); // Paper
- }
- // Spigot end
-+
-+ // Paper start - move final shutdown items here
-+ LOGGER.info("Flushing Chunk IO");
- io.papermc.paper.chunk.system.io.RegionFileIOThread.close(true); // Paper // Paper - rewrite chunk system
-+ LOGGER.info("Closing Thread Pool");
-+ Util.shutdownExecutors(); // Paper
-+ LOGGER.info("Closing Server");
-+ try {
-+ net.minecrell.terminalconsole.TerminalConsoleAppender.close(); // Paper - Use TerminalConsoleAppender
-+ } catch (Exception e) {
-+ }
-+ this.onServerExit();
-+ // Paper end
- }
-
- public String getLocalIp() {
-@@ -1051,6 +1080,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
-
- protected void runServer() {
- try {
-+ long serverStartTime = Util.getNanos(); // Paper
- if (!this.initServer()) {
- throw new IllegalStateException("Failed to initialize server");
- }
-@@ -1060,6 +1090,18 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
- this.status = this.buildServerStatus();
-
- // Spigot start
-+ // Paper start - move done tracking
-+ LOGGER.info("Running delayed init tasks");
-+ this.server.getScheduler().mainThreadHeartbeat(this.tickCount); // run all 1 tick delay tasks during init,
-+ // 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
-+ String doneTime = String.format(java.util.Locale.ROOT, "%.3fs", (double) (Util.getNanos() - serverStartTime) / 1.0E9D);
-+ LOGGER.info("Done ({})! For help, type \"help\"", doneTime);
-+ // Paper end
-+
-+ org.spigotmc.WatchdogThread.tick(); // Paper
- org.spigotmc.WatchdogThread.hasStarted = true; // Paper
- Arrays.fill( recentTps, 20 );
- long start = System.nanoTime(), curTime, tickSection = start; // Paper - Further improve server tick loop
-@@ -1120,6 +1162,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
- JvmProfiler.INSTANCE.onServerTick(this.averageTickTime);
- }
- } catch (Throwable throwable) {
-+ // Paper start
-+ if (throwable instanceof ThreadDeath) {
-+ MinecraftServer.LOGGER.error("Main thread terminated by WatchDog due to hard crash", throwable);
-+ return;
-+ }
-+ // Paper end
- MinecraftServer.LOGGER.error("Encountered an unexpected exception", throwable);
- // Spigot Start
- if ( throwable.getCause() != null )
-@@ -1150,14 +1198,14 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
- this.services.profileCache().clearExecutor();
- }
-
-- 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
-- this.onServerExit();
-+ //this.onServerExit(); // Paper - moved into stop
- }
-
- }
-@@ -1226,6 +1274,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
-
- @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);
- }
-
-@@ -1461,6 +1515,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
- try {
- crashreport = CrashReport.forThrowable(throwable, "Exception ticking world");
- } catch (Throwable t) {
-+ if (throwable instanceof ThreadDeath) { throw (ThreadDeath)throwable; } // Paper
- throw new RuntimeException("Error generating crash report", t);
- }
- // Spigot End
-@@ -1961,7 +2016,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
-
- this.worldData.setDataConfiguration(worlddataconfiguration);
- this.resources.managers.updateRegistryTags(this.registryAccess());
-- this.getPlayerList().saveAll();
-+ // Paper start
-+ if (Thread.currentThread() != this.serverThread) {
-+ return;
-+ }
-+ // this.getPlayerList().saveAll(); // Paper - we don't need to save everything, just advancements
-+ 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/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
-index f4c7866a27319b2da8c00e8fe9b8f1f6306045ab..71b395db734c257a64ec3297eebbe52883ea4cc7 100644
---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
-+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
-@@ -269,7 +269,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
- long j = Util.getNanos() - i;
- String s = String.format(Locale.ROOT, "%.3fs", (double) j / 1.0E9D);
-
-- DedicatedServer.LOGGER.info("Done ({})! For help, type \"help\"", s);
-+ //DedicatedServer.LOGGER.info("Done ({})! For help, type \"help\"", s); // Paper moved to after init
- if (dedicatedserverproperties.announcePlayerAchievements != null) {
- ((GameRules.BooleanValue) this.getGameRules().getRule(GameRules.RULE_ANNOUNCE_ADVANCEMENTS)).set(dedicatedserverproperties.announcePlayerAchievements, this);
- }
-@@ -398,7 +398,8 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
- //this.remoteStatusListener.b(); // Paper - don't wait for remote connections
- }
-
-- System.exit(0); // CraftBukkit
-+ hasFullyShutdown = true; // Paper
-+ System.exit(this.abnormalExit ? 70 : 0); // CraftBukkit // Paper
- }
-
- @Override
-@@ -763,7 +764,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
- @Override
- public void stopServer() {
- super.stopServer();
-- Util.shutdownExecutors();
-+ //Util.shutdownExecutors(); // Paper - moved into super
- SkullBlockEntity.clear();
- }
-
-diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
-index 5b3e8adde28d11aec4ea526ebba61d561ec06cb6..95356cfb36f2103889a21d6c360d29a318c871dd 100644
---- a/src/main/java/net/minecraft/server/players/PlayerList.java
-+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
-@@ -547,7 +547,7 @@ public abstract class PlayerList {
- this.cserver.getPluginManager().callEvent(playerQuitEvent);
- entityplayer.getBukkitEntity().disconnect(playerQuitEvent.getQuitMessage());
-
-- entityplayer.doTick(); // SPIGOT-924
-+ if (server.isSameThread()) entityplayer.doTick(); // SPIGOT-924 // Paper - don't tick during emergency shutdowns (Watchdog)
- // CraftBukkit end
-
- // Paper start - Remove from collideRule team if needed
-diff --git a/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java b/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java
-index f5829ae484d93b547a5437b85a9621346384a11b..83701fbfaa56a232593ee8f11a3afb8941238bfa 100644
---- a/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java
-+++ b/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java
-@@ -152,6 +152,7 @@ public abstract class BlockableEventLoop<R extends Runnable> implements Profiler
- try {
- task.run();
- } catch (Exception var3) {
-+ if (var3.getCause() instanceof ThreadDeath) throw var3; // Paper
- LOGGER.error(LogUtils.FATAL_MARKER, "Error executing task on {}", this.name(), var3);
- }
-
-diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
-index 9d1c3859e54af9b42665e7ca2fc50e83e7784148..904ffb532ecfa45ef8dfc318d7672d19e99934cd 100644
---- a/src/main/java/net/minecraft/world/level/Level.java
-+++ b/src/main/java/net/minecraft/world/level/Level.java
-@@ -817,6 +817,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
- try {
- tickConsumer.accept(entity);
- } catch (Throwable throwable) {
-+ if (throwable instanceof ThreadDeath) throw throwable; // Paper
- // Paper start - Prevent tile 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, throwable);
-diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-index 8d6e3c60035ba2e7e05d081aec66274bdeedf3ea..f1c9dd86f62243ece7e81e820d1dbcae72d2598c 100644
---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-@@ -1180,6 +1180,7 @@ public class LevelChunk extends ChunkAccess {
-
- gameprofilerfiller.pop();
- } catch (Throwable throwable) {
-+ if (throwable instanceof ThreadDeath) throw throwable; // Paper
- // Paper start - Prevent tile 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, throwable);
-diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java
-index 24607ed5f60ce68aeda7380c433a8b416ed885c4..203a25a2a8d843c33440d849a94b85e2c717d2a0 100644
---- a/src/main/java/org/bukkit/craftbukkit/Main.java
-+++ b/src/main/java/org/bukkit/craftbukkit/Main.java
-@@ -178,6 +178,36 @@ public class Main {
-
- OptionSet options = null;
-
-+ // Paper start - preload logger classes to avoid plugins mixing versions
-+ tryPreloadClass("org.apache.logging.log4j.core.Core");
-+ tryPreloadClass("org.apache.logging.log4j.core.appender.AsyncAppender");
-+ tryPreloadClass("org.apache.logging.log4j.core.Appender");
-+ tryPreloadClass("org.apache.logging.log4j.core.ContextDataInjector");
-+ tryPreloadClass("org.apache.logging.log4j.core.Filter");
-+ tryPreloadClass("org.apache.logging.log4j.core.ErrorHandler");
-+ tryPreloadClass("org.apache.logging.log4j.core.LogEvent");
-+ tryPreloadClass("org.apache.logging.log4j.core.Logger");
-+ tryPreloadClass("org.apache.logging.log4j.core.LoggerContext");
-+ tryPreloadClass("org.apache.logging.log4j.core.LogEventListener");
-+ tryPreloadClass("org.apache.logging.log4j.core.AbstractLogEvent");
-+ tryPreloadClass("org.apache.logging.log4j.message.AsynchronouslyFormattable");
-+ tryPreloadClass("org.apache.logging.log4j.message.FormattedMessage");
-+ tryPreloadClass("org.apache.logging.log4j.message.ParameterizedMessage");
-+ tryPreloadClass("org.apache.logging.log4j.message.Message");
-+ tryPreloadClass("org.apache.logging.log4j.message.MessageFactory");
-+ tryPreloadClass("org.apache.logging.log4j.message.TimestampMessage");
-+ tryPreloadClass("org.apache.logging.log4j.message.SimpleMessage");
-+ tryPreloadClass("org.apache.logging.log4j.core.async.AsyncLogger");
-+ tryPreloadClass("org.apache.logging.log4j.core.async.AsyncLoggerContext");
-+ tryPreloadClass("org.apache.logging.log4j.core.async.AsyncQueueFullPolicy");
-+ tryPreloadClass("org.apache.logging.log4j.core.async.AsyncLoggerDisruptor");
-+ tryPreloadClass("org.apache.logging.log4j.core.async.RingBufferLogEvent");
-+ tryPreloadClass("org.apache.logging.log4j.core.async.DisruptorUtil");
-+ tryPreloadClass("org.apache.logging.log4j.core.async.RingBufferLogEventHandler");
-+ tryPreloadClass("org.apache.logging.log4j.core.impl.ThrowableProxy");
-+ tryPreloadClass("org.apache.logging.log4j.core.impl.ExtendedClassInfo");
-+ tryPreloadClass("org.apache.logging.log4j.core.impl.ExtendedStackTraceElement");
-+ // Paper end
- try {
- options = parser.parse(args);
- } catch (joptsimple.OptionException ex) {
-@@ -280,8 +310,64 @@ public class Main {
- } catch (Throwable t) {
- t.printStackTrace();
- }
-+ // Paper start
-+ // load some required classes to avoid errors during shutdown if jar is replaced
-+ // also to guarantee our version loads over plugins
-+ tryPreloadClass("com.destroystokyo.paper.util.SneakyThrow");
-+ tryPreloadClass("com.google.common.collect.Iterators$PeekingImpl");
-+ tryPreloadClass("com.google.common.collect.MapMakerInternalMap$Values");
-+ tryPreloadClass("com.google.common.collect.MapMakerInternalMap$ValueIterator");
-+ tryPreloadClass("com.google.common.collect.MapMakerInternalMap$WriteThroughEntry");
-+ tryPreloadClass("com.google.common.collect.Iterables");
-+ for (int i = 1; i <= 15; i++) {
-+ tryPreloadClass("com.google.common.collect.Iterables$" + i, false);
-+ }
-+ tryPreloadClass("org.apache.commons.lang3.mutable.MutableBoolean");
-+ tryPreloadClass("org.apache.commons.lang3.mutable.MutableInt");
-+ tryPreloadClass("org.jline.terminal.impl.MouseSupport");
-+ tryPreloadClass("org.jline.terminal.impl.MouseSupport$1");
-+ tryPreloadClass("org.jline.terminal.Terminal$MouseTracking");
-+ tryPreloadClass("co.aikar.timings.TimingHistory");
-+ tryPreloadClass("co.aikar.timings.TimingHistory$MinuteReport");
-+ tryPreloadClass("io.netty.channel.AbstractChannelHandlerContext");
-+ tryPreloadClass("io.netty.channel.AbstractChannelHandlerContext$11");
-+ tryPreloadClass("io.netty.channel.AbstractChannelHandlerContext$12");
-+ tryPreloadClass("io.netty.channel.AbstractChannel$AbstractUnsafe$8");
-+ tryPreloadClass("io.netty.util.concurrent.DefaultPromise");
-+ tryPreloadClass("io.netty.util.concurrent.DefaultPromise$1");
-+ tryPreloadClass("io.netty.util.internal.PromiseNotificationUtil");
-+ tryPreloadClass("io.netty.util.internal.SystemPropertyUtil");
-+ tryPreloadClass("org.bukkit.craftbukkit.scheduler.CraftScheduler");
-+ tryPreloadClass("org.bukkit.craftbukkit.scheduler.CraftScheduler$1");
-+ tryPreloadClass("org.bukkit.craftbukkit.scheduler.CraftScheduler$2");
-+ tryPreloadClass("org.bukkit.craftbukkit.scheduler.CraftScheduler$3");
-+ tryPreloadClass("org.bukkit.craftbukkit.scheduler.CraftScheduler$4");
-+ tryPreloadClass("org.slf4j.helpers.MessageFormatter");
-+ tryPreloadClass("org.slf4j.helpers.FormattingTuple");
-+ tryPreloadClass("org.slf4j.helpers.BasicMarker");
-+ tryPreloadClass("org.slf4j.helpers.Util");
-+ tryPreloadClass("com.destroystokyo.paper.event.player.PlayerConnectionCloseEvent");
-+ tryPreloadClass("com.destroystokyo.paper.event.entity.EntityRemoveFromWorldEvent");
-+ // Minecraft, seen during saving
-+ tryPreloadClass(net.minecraft.world.level.lighting.LayerLightEventListener.DummyLightLayerEventListener.class.getName());
-+ tryPreloadClass(net.minecraft.world.level.lighting.LayerLightEventListener.class.getName());
-+ tryPreloadClass(net.minecraft.util.ExceptionCollector.class.getName());
-+ // Paper end
-+ }
-+ }
-+
-+ // Paper start
-+ private static void tryPreloadClass(String className) {
-+ tryPreloadClass(className, true);
-+ }
-+ private static void tryPreloadClass(String className, boolean printError) {
-+ try {
-+ Class.forName(className);
-+ } catch (ClassNotFoundException e) {
-+ if (printError) System.err.println("An expected class " + className + " was not found for preloading: " + e.getMessage());
- }
- }
-+ // Paper end
-
- private static List<String> asList(String... params) {
- return Arrays.asList(params);
-diff --git a/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java b/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java
-index b4a19d80bbf71591f25729fd0e98590350cb31d0..e948ec5a573b22645664eb53bc3e9932246544e4 100644
---- a/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java
-+++ b/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java
-@@ -12,12 +12,28 @@ public class ServerShutdownThread extends Thread {
- @Override
- public void run() {
- try {
-+ // Paper start - try to shutdown on main
-+ server.safeShutdown(false, false);
-+ for (int i = 1000; i > 0 && !server.hasStopped(); i -= 100) {
-+ Thread.sleep(100);
-+ }
-+ if (server.hasStopped()) {
-+ while (!server.hasFullyShutdown) Thread.sleep(1000);
-+ return;
-+ }
-+ // Looks stalled, close async
- org.spigotmc.AsyncCatcher.enabled = false; // Spigot
- org.spigotmc.AsyncCatcher.shuttingDown = true; // Paper
-+ server.forceTicks = true;
- this.server.close();
-+ while (!server.hasFullyShutdown) Thread.sleep(1000);
-+ } catch (InterruptedException e) {
-+ e.printStackTrace();
-+ // Paper end
- } finally {
-+ org.apache.logging.log4j.LogManager.shutdown(); // Paper
- try {
-- net.minecrell.terminalconsole.TerminalConsoleAppender.close(); // Paper - Use TerminalConsoleAppender
-+ //net.minecrell.terminalconsole.TerminalConsoleAppender.close(); // Paper - Move into stop
- } catch (Exception e) {
- }
- }
-diff --git a/src/main/java/org/spigotmc/RestartCommand.java b/src/main/java/org/spigotmc/RestartCommand.java
-index a142a56a920e153ed84c08cece993f10d76f7793..92d97a5810a379b427a99b4c63fb9844d823a84f 100644
---- a/src/main/java/org/spigotmc/RestartCommand.java
-+++ b/src/main/java/org/spigotmc/RestartCommand.java
-@@ -139,7 +139,7 @@ public class RestartCommand extends Command
- // Paper end
-
- // Paper start - copied from above and modified to return if the hook registered
-- private static boolean addShutdownHook(String restartScript)
-+ public static boolean addShutdownHook(String restartScript)
- {
- String[] split = restartScript.split( " " );
- if ( split.length > 0 && new File( split[0] ).isFile() )
-diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java
-index b47d043144c499b1499f6b4be5a16a3f75c9fcb8..383c52c62f49b17db2fbf58009d6ea132d124bea 100644
---- a/src/main/java/org/spigotmc/WatchdogThread.java
-+++ b/src/main/java/org/spigotmc/WatchdogThread.java
-@@ -11,6 +11,7 @@ import org.bukkit.Bukkit;
- public final class WatchdogThread extends io.papermc.paper.util.TickThread // Paper - rewrite chunk system
- {
-
-+ public static final boolean DISABLE_WATCHDOG = Boolean.getBoolean("disable.watchdog"); // Paper
- private static WatchdogThread instance;
- private long timeoutTime;
- private boolean restart;
-@@ -39,6 +40,7 @@ public final class WatchdogThread extends io.papermc.paper.util.TickThread // Pa
- {
- if ( WatchdogThread.instance == null )
- {
-+ if (timeoutTime <= 0) timeoutTime = 300; // Paper
- WatchdogThread.instance = new WatchdogThread( timeoutTime * 1000L, restart );
- WatchdogThread.instance.start();
- } else
-@@ -70,12 +72,13 @@ public final class WatchdogThread extends io.papermc.paper.util.TickThread // Pa
- // Paper start
- Logger log = Bukkit.getServer().getLogger();
- long currentTime = WatchdogThread.monotonicMillis();
-- if ( this.lastTick != 0 && this.timeoutTime > 0 && currentTime > this.lastTick + this.earlyWarningEvery && !Boolean.getBoolean("disable.watchdog")) // Paper - Add property to disable
-+ MinecraftServer server = MinecraftServer.getServer();
-+ if ( this.lastTick != 0 && this.timeoutTime > 0 && WatchdogThread.hasStarted && (!server.isRunning() || (currentTime > this.lastTick + this.earlyWarningEvery && !DISABLE_WATCHDOG) )) // Paper - add property to disable
- {
-- boolean isLongTimeout = currentTime > lastTick + timeoutTime;
-+ boolean isLongTimeout = currentTime > lastTick + timeoutTime || (!server.isRunning() && !server.hasStopped() && currentTime > lastTick + 1000);
- // Don't spam early warning dumps
- if ( !isLongTimeout && (earlyWarningEvery <= 0 || !hasStarted || currentTime < lastEarlyWarning + earlyWarningEvery || currentTime < lastTick + earlyWarningDelay)) continue;
-- if ( !isLongTimeout && MinecraftServer.getServer().hasStopped()) continue; // Don't spam early watchdog warnings during shutdown, we'll come back to this...
-+ if ( !isLongTimeout && server.hasStopped()) continue; // Don't spam early watchdog warnings during shutdown, we'll come back to this...
- lastEarlyWarning = currentTime;
- if (isLongTimeout) {
- // Paper end
-@@ -137,9 +140,25 @@ public final class WatchdogThread extends io.papermc.paper.util.TickThread // Pa
-
- if ( isLongTimeout )
- {
-- if ( this.restart && !MinecraftServer.getServer().hasStopped() )
-+ if ( !server.hasStopped() )
- {
-- RestartCommand.restart();
-+ AsyncCatcher.enabled = false; // Disable async catcher incase it interferes with us
-+ AsyncCatcher.shuttingDown = true;
-+ server.forceTicks = true;
-+ if (restart) {
-+ RestartCommand.addShutdownHook( SpigotConfig.restartScript );
-+ }
-+ // try one last chance to safe shutdown on main incase it 'comes back'
-+ server.abnormalExit = true;
-+ server.safeShutdown(false, restart);
-+ try {
-+ Thread.sleep(1000);
-+ } catch (InterruptedException e) {
-+ e.printStackTrace();
-+ }
-+ if (!server.hasStopped()) {
-+ server.close();
-+ }
- }
- break;
- } // Paper end
-diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml
-index 3dc317e466e1b93dff030794dd7f29ca1b266778..d285dbec16272db6b8a71865e05924ad66087407 100644
---- a/src/main/resources/log4j2.xml
-+++ b/src/main/resources/log4j2.xml
-@@ -1,5 +1,5 @@
- <?xml version="1.0" encoding="UTF-8"?>
--<Configuration status="WARN" packages="com.mojang.util">
-+<Configuration status="WARN" packages="com.mojang.util" shutdownHook="disable">
- <Appenders>
- <Queue name="ServerGuiConsole">
- <PatternLayout pattern="[%d{HH:mm:ss} %level]: %msg%n" />