diff options
Diffstat (limited to 'Spigot-Server-Patches-Unmapped/0438-Improved-Watchdog-Support.patch')
-rw-r--r-- | Spigot-Server-Patches-Unmapped/0438-Improved-Watchdog-Support.patch | 604 |
1 files changed, 604 insertions, 0 deletions
diff --git a/Spigot-Server-Patches-Unmapped/0438-Improved-Watchdog-Support.patch b/Spigot-Server-Patches-Unmapped/0438-Improved-Watchdog-Support.patch new file mode 100644 index 0000000000..dfd22c1935 --- /dev/null +++ b/Spigot-Server-Patches-Unmapped/0438-Improved-Watchdog-Support.patch @@ -0,0 +1,604 @@ +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 0b9e689d57705965721b5c55bc45d36657f360e4..dee00aac05f1acf050f05d4db557a08dd0f301c8 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 d0fdb9ce57b22a1f582cddec9afcc35b75d58cc6..9b7a51890c667601b195ff15b2bf0d6c76c7f19f 100644 +--- a/src/main/java/net/minecraft/CrashReport.java ++++ b/src/main/java/net/minecraft/CrashReport.java +@@ -257,6 +257,7 @@ public class CrashReport { + } + + public static CrashReport a(Throwable throwable, String s) { ++ if (throwable instanceof ThreadDeath) com.destroystokyo.paper.util.SneakyThrow.sneaky(throwable); // Paper + while (throwable instanceof CompletionException && throwable.getCause() != null) { + throwable = throwable.getCause(); + } +diff --git a/src/main/java/net/minecraft/SystemUtils.java b/src/main/java/net/minecraft/SystemUtils.java +index 397194b3e90c9df39cfae17b401c7ac891b0dbb7..61b4c42e95994343772a91640b243b8e8224e09b 100644 +--- a/src/main/java/net/minecraft/SystemUtils.java ++++ b/src/main/java/net/minecraft/SystemUtils.java +@@ -130,6 +130,7 @@ public class SystemUtils { + return SystemUtils.f; + } + ++ public static void shutdownServerThreadPool() { h(); } // Paper - OBFHELPER + public static void h() { + a(SystemUtils.e); + a(SystemUtils.f); +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index e302c84db5d129c3d302b6361c80af88806c35b4..701dcbf9d3f23b63d740fbe10a642080128b7f62 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -270,7 +270,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas + public int autosavePeriod; + public boolean serverAutoSave = false; // Paper + public CommandDispatcher vanillaCommandDispatcher; +- private boolean forceTicks; ++ public boolean forceTicks; // Paper + // CraftBukkit end + // Spigot start + public static final int TPS = 20; +@@ -280,6 +280,8 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas + public final SlackActivityAccountant slackActivityAccountant = new SlackActivityAccountant(); + // Spigot end + ++ public volatile Thread shutdownThread; // Paper ++ + public static <S extends MinecraftServer> S a(Function<Thread, S> function) { + AtomicReference<S> atomicreference = new AtomicReference(); + Thread thread = new Thread(() -> { +@@ -852,6 +854,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas + + // CraftBukkit start + private boolean hasStopped = false; ++ public volatile boolean hasFullyShutdown = false; // Paper + private final Object stopLock = new Object(); + public final boolean hasStopped() { + synchronized (stopLock) { +@@ -866,6 +869,23 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas + if (hasStopped) return; + hasStopped = true; + } ++ // Paper start - kill main thread, and kill it hard ++ shutdownThread = Thread.currentThread(); ++ org.spigotmc.WatchdogThread.doStop(); // Paper ++ if (!isMainThread()) { ++ MinecraftServer.LOGGER.info("Stopping main thread (Ignore any thread death message you see! - DO NOT REPORT THREAD DEATH TO PAPER)"); ++ while (this.getThread().isAlive()) { ++ this.getThread().stop(); ++ try { ++ Thread.sleep(1); ++ } catch (InterruptedException e) {} ++ } ++ // We've just obliterated the main thread, this will prevent stop from dying when removing players ++ MinecraftServer.getServer().getWorlds().forEach(world -> { ++ world.tickingEntities = false; ++ }); ++ } ++ // Paper end + // CraftBukkit end + MinecraftServer.LOGGER.info("Stopping server"); + MinecraftTimings.stopServer(); // Paper +@@ -931,7 +951,18 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas + this.getUserCache().b(false); // Paper + } + // Spigot end ++ // Paper start - move final shutdown items here ++ LOGGER.info("Flushing Chunk IO"); + com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.close(true, true); // Paper ++ LOGGER.info("Closing Thread Pool"); ++ SystemUtils.shutdownServerThreadPool(); // Paper ++ LOGGER.info("Closing Server"); ++ try { ++ net.minecrell.terminalconsole.TerminalConsoleAppender.close(); // Paper - Use TerminalConsoleAppender ++ } catch (Exception e) { ++ } ++ this.exit(); ++ // Paper end + } + + public String getServerIp() { +@@ -1024,6 +1055,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas + + protected void w() { + try { ++ long serverStartTime = SystemUtils.getMonotonicNanos(); // Paper + if (this.init()) { + this.nextTick = SystemUtils.getMonotonicMillis(); + this.serverPing.setMOTD(new ChatComponentText(this.motd)); +@@ -1031,6 +1063,18 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas + this.a(this.serverPing); + + // Spigot start ++ // Paper start - move done tracking ++ LOGGER.info("Running delayed init tasks"); ++ this.server.getScheduler().mainThreadHeartbeat(this.ticks); // 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) (SystemUtils.getMonotonicNanos() - 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 +@@ -1086,6 +1130,12 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas + this.a((CrashReport) null); + } + } 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 ) +@@ -1117,14 +1167,14 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas + } catch (Throwable throwable1) { + MinecraftServer.LOGGER.error("Exception stopping the server", throwable1); + } finally { +- 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.exit(); ++ //this.exit(); // Paper - moved into stop + } + + } +@@ -1180,6 +1230,12 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas + + @Override + public TickTask postToMainThread(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.ticks, runnable); + } + +@@ -1422,6 +1478,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas + try { + crashreport = CrashReport.a(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 +@@ -1879,7 +1936,8 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas + this.resourcePackRepository.a(collection); + this.saveData.a(a(this.resourcePackRepository)); + datapackresources.i(); +- this.getPlayerList().savePlayers(); ++ if (Thread.currentThread() != this.serverThread) return; // Paper ++ //this.getPlayerList().savePlayers(); // Paper - we don't need to do this + this.getPlayerList().reload(); + this.customFunctionData.a(this.dataPackResources.a()); + this.ak.a(this.dataPackResources.h()); +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +index 557f80accfa36b495c9a8cffdab2e248c1cbb514..ec1f36736d79d4054ad7ff4da4e3659f35c811d6 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -279,7 +279,7 @@ public class DedicatedServer extends MinecraftServer implements IMinecraftServer + long j = SystemUtils.getMonotonicNanos() - 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.GameRuleBoolean) this.getGameRules().get(GameRules.ANNOUNCE_ADVANCEMENTS)).a(dedicatedserverproperties.announcePlayerAchievements, (MinecraftServer) this); + } +@@ -407,6 +407,7 @@ public class DedicatedServer extends MinecraftServer implements IMinecraftServer + //this.remoteStatusListener.b(); // Paper - don't wait for remote connections + } + ++ hasFullyShutdown = true; // Paper + System.exit(0); // CraftBukkit + } + +@@ -740,7 +741,7 @@ public class DedicatedServer extends MinecraftServer implements IMinecraftServer + @Override + public void stop() { + super.stop(); +- SystemUtils.h(); ++ //SystemUtils.h(); // Paper - moved into super + } + + @Override +diff --git a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +index 8050be2ed04fb0b8141f92595680407bba65dad5..bb9c6e9aeb1f30af01338476ba1dd618b14124d5 100644 +--- a/src/main/java/net/minecraft/server/level/PlayerChunkMap.java ++++ b/src/main/java/net/minecraft/server/level/PlayerChunkMap.java +@@ -536,6 +536,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d { + MutableBoolean mutableboolean = new MutableBoolean(); + + do { ++ boolean isShuttingDown = world.getMinecraftServer().hasStopped(); // Paper + mutableboolean.setFalse(); + list.stream().map((playerchunk) -> { + CompletableFuture completablefuture; +diff --git a/src/main/java/net/minecraft/server/level/WorldServer.java b/src/main/java/net/minecraft/server/level/WorldServer.java +index ae99f3fb3a4b37c737fb276590004b2e10beab5a..06fa9b91cc103a5d5f39ab8fcfb5ccad4cf0e5de 100644 +--- a/src/main/java/net/minecraft/server/level/WorldServer.java ++++ b/src/main/java/net/minecraft/server/level/WorldServer.java +@@ -177,7 +177,7 @@ public class WorldServer extends World implements GeneratorAccessSeed { + private final Queue<Entity> entitiesToAdd = Queues.newArrayDeque(); + public final List<EntityPlayer> players = Lists.newArrayList(); // Paper - private -> public + public final ChunkProviderServer chunkProvider; // Paper - public +- boolean tickingEntities; ++ public boolean tickingEntities; // Paper - expose for watchdog + // Paper start + List<java.lang.Runnable> afterEntityTickingTasks = Lists.newArrayList(); + public void doIfNotEntityTicking(java.lang.Runnable run) { +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index f35825d4a8574ea75b46be36b9929f8e12405217..48d4f8310d79d0eb78f4ace42c8778ee4addf7d0 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -507,7 +507,7 @@ public abstract class PlayerList { + cserver.getPluginManager().callEvent(playerQuitEvent); + entityplayer.getBukkitEntity().disconnect(playerQuitEvent.getQuitMessage()); + +- entityplayer.playerTick(); // SPIGOT-924 ++ if (server.isMainThread()) entityplayer.playerTick(); // 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/IAsyncTaskHandler.java b/src/main/java/net/minecraft/util/thread/IAsyncTaskHandler.java +index ca23ca14d8011fc8daa7e20f2eaa550a8ff92c53..158ea6d77698d62ba795aff6c061a80652e42e03 100644 +--- a/src/main/java/net/minecraft/util/thread/IAsyncTaskHandler.java ++++ b/src/main/java/net/minecraft/util/thread/IAsyncTaskHandler.java +@@ -135,6 +135,7 @@ public abstract class IAsyncTaskHandler<R extends Runnable> implements Mailbox<R + try { + r0.run(); + } catch (Exception exception) { ++ if (exception.getCause() instanceof ThreadDeath) throw exception; // Paper + IAsyncTaskHandler.LOGGER.fatal("Error executing task on {}", this.bj(), exception); + } + +diff --git a/src/main/java/net/minecraft/world/level/World.java b/src/main/java/net/minecraft/world/level/World.java +index cc41dcd85760b57bb8076b37e9a907d1cb4e12c7..efcfc8f0f45901d14ac8fdf8ed7b0bd67f8f94da 100644 +--- a/src/main/java/net/minecraft/world/level/World.java ++++ b/src/main/java/net/minecraft/world/level/World.java +@@ -858,6 +858,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + + gameprofilerfiller.exit(); + } catch (Throwable throwable) { ++ if (throwable instanceof ThreadDeath) throw throwable; // Paper + // Paper start - Prevent tile entity and entity crashes + String msg = "TileEntity threw exception at " + tileentity.getWorld().getWorld().getName() + ":" + tileentity.getPosition().getX() + "," + tileentity.getPosition().getY() + "," + tileentity.getPosition().getZ(); + System.err.println(msg); +@@ -932,6 +933,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable { + try { + consumer.accept(entity); + } catch (Throwable throwable) { ++ if (throwable instanceof ThreadDeath) throw throwable; // Paper + // Paper start - Prevent tile entity and entity crashes + String msg = "Entity threw exception at " + entity.world.getWorld().getName() + ":" + entity.locX() + "," + entity.locY() + "," + entity.locZ(); + System.err.println(msg); +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 4828d356ca01cba5964c6397584d56643dbc0dae..55890ff463eb122934e8ba1fc550cf0cccaf8451 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -1834,7 +1834,7 @@ public final class CraftServer implements Server { + + @Override + public boolean isPrimaryThread() { +- return Thread.currentThread().equals(console.serverThread); // Paper - Fix issues with detecting main thread properly ++ return Thread.currentThread().equals(console.serverThread) || Thread.currentThread().equals(net.minecraft.server.MinecraftServer.getServer().shutdownThread); // Paper - Fix issues with detecting main thread properly, the only time Watchdog will be used is during a crash shutdown which is a "try our best" scenario + } + + // Paper start +diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java +index 100772ba60cb7e717c5e626a4d7b44c15375b0e5..f9a1f1a83be91e302e9a2d29fefc5b21d9f0e590 100644 +--- a/src/main/java/org/bukkit/craftbukkit/Main.java ++++ b/src/main/java/org/bukkit/craftbukkit/Main.java +@@ -12,6 +12,8 @@ import java.util.logging.Level; + import java.util.logging.Logger; + import joptsimple.OptionParser; + import joptsimple.OptionSet; ++import net.minecraft.util.ExceptionSuppressor; ++import net.minecraft.world.level.lighting.LightEngineLayerEventListener; + import net.minecrell.terminalconsole.TerminalConsoleAppender; // Paper + + public class Main { +@@ -150,6 +152,36 @@ public class Main { + + OptionSet options = null; + ++ // Paper start - preload logger classes to avoid plugins mixing versions ++ tryPreloadClass("com.destroystokyo.paper.log.LogFullPolicy"); ++ tryPreloadClass("org.apache.logging.log4j.core.Core"); ++ 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) { +@@ -245,8 +277,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(LightEngineLayerEventListener.Void.class.getName()); ++ tryPreloadClass(LightEngineLayerEventListener.class.getName()); ++ tryPreloadClass(ExceptionSuppressor.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 449e99d1b673870ed6892f6ab2c715a2db35c35d..c7ed6e0f8a989cec97700df2b15198c9c481c549 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java +@@ -12,12 +12,27 @@ 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; + server.close(); ++ while (!server.hasFullyShutdown) Thread.sleep(1000); ++ } catch (InterruptedException e) { ++ e.printStackTrace(); ++ // Paper end + } finally { + 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 b45d7e5c108c7a8541fcbc9ad92d1a79a94746a1..6a408dc9286a60c3ca7830f88171919fb0fe6363 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 58e50bf0fb0f309227e1f4c1f6bb11c01d8e08d3..30a665c090f419985e1d0f49df9e8d110c83943a 100644 +--- a/src/main/java/org/spigotmc/WatchdogThread.java ++++ b/src/main/java/org/spigotmc/WatchdogThread.java +@@ -13,6 +13,7 @@ import org.bukkit.Bukkit; + public class WatchdogThread extends Thread + { + ++ public static final boolean DISABLE_WATCHDOG = Boolean.getBoolean("disable.watchdog"); // Paper + private static WatchdogThread instance; + private long timeoutTime; + private boolean restart; +@@ -41,6 +42,7 @@ public class WatchdogThread extends Thread + { + if ( instance == null ) + { ++ if (timeoutTime <= 0) timeoutTime = 300; // Paper + instance = new WatchdogThread( timeoutTime * 1000L, restart ); + instance.start(); + } else +@@ -71,12 +73,13 @@ public class WatchdogThread extends Thread + // Paper start + Logger log = Bukkit.getServer().getLogger(); + long currentTime = monotonicMillis(); +- if ( lastTick != 0 && timeoutTime > 0 && currentTime > lastTick + earlyWarningEvery && !Boolean.getBoolean("disable.watchdog") ) ++ MinecraftServer server = MinecraftServer.getServer(); ++ if (lastTick != 0 && timeoutTime > 0 && hasStarted && (!server.isRunning() || (currentTime > lastTick + earlyWarningEvery && !DISABLE_WATCHDOG) )) + { +- 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 +@@ -118,7 +121,7 @@ public class WatchdogThread extends Thread + log.log( Level.SEVERE, "------------------------------" ); + log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Paper!):" ); // Paper + ChunkTaskManager.dumpAllChunkLoadInfo(); // Paper +- dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE ), log ); ++ dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( server.serverThread.getId(), Integer.MAX_VALUE ), log ); + log.log( Level.SEVERE, "------------------------------" ); + // + // Paper start - Only print full dump on long timeouts +@@ -139,9 +142,24 @@ public class WatchdogThread extends Thread + + if ( isLongTimeout ) + { +- if ( 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.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 476f4a5cbe664ddd05474cb88553018bd334a5b8..8af159abd3d0cc94cf155fec5b384c42f69551bf 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" /> |