mirror of
https://github.com/PaperMC/Paper.git
synced 2024-11-29 19:52:55 +01:00
544 lines
31 KiB
Diff
544 lines
31 KiB
Diff
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.
|
|
|
|
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 bcb6a3b3cd17ce5db9aaf6bd3ec7a0ec1b44b979..4f3cc14d48690bb183d09bb7a5ba1e23e8a0c08a 100644
|
|
--- a/src/main/java/net/minecraft/CrashReport.java
|
|
+++ b/src/main/java/net/minecraft/CrashReport.java
|
|
@@ -234,6 +234,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 7f3359745089584448b8f32f0570cf86b73076c7..101c465397176d873f7a7871c84c8fb5a9897b0b 100644
|
|
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
|
|
@@ -303,7 +303,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;
|
|
@@ -316,6 +316,9 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
public static long currentTickLong = 0L; // Paper - track current tick as a long
|
|
public boolean isIteratingOverLevels = false; // Paper - Throw exception on world create while being ticked
|
|
|
|
+ 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
|
|
@@ -933,6 +936,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
// CraftBukkit start
|
|
private boolean hasStopped = false;
|
|
private boolean hasLoggedStop = false; // Paper - Debugging
|
|
+ public volatile boolean hasFullyShutdown = false; // Paper
|
|
private final Object stopLock = new Object();
|
|
public final boolean hasStopped() {
|
|
synchronized (this.stopLock) {
|
|
@@ -948,6 +952,19 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
this.hasStopped = true;
|
|
}
|
|
if (!hasLoggedStop && isDebugging()) io.papermc.paper.util.TraceUtil.dumpTraceForThread("Server stopped"); // Paper - Debugging
|
|
+ // 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();
|
|
@@ -1004,7 +1021,21 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
}
|
|
// Spigot end
|
|
|
|
+ // Paper start - move final shutdown items here
|
|
+ LOGGER.info("Flushing Chunk IO");
|
|
+ // Paper end - move final shutdown items here
|
|
io.papermc.paper.chunk.system.io.RegionFileIOThread.close(true); // Paper - rewrite chunk system
|
|
+ // Paper start - move final shutdown items here
|
|
+ LOGGER.info("Closing Thread Pool");
|
|
+ Util.shutdownExecutors(); // Paper
|
|
+ LOGGER.info("Closing Server");
|
|
+ try {
|
|
+ net.minecrell.terminalconsole.TerminalConsoleAppender.close(); // Paper - Use TerminalConsoleAppender
|
|
+ } catch (Exception e) {
|
|
+ }
|
|
+ io.papermc.paper.log.CustomLogManager.forceReset(); // Paper - Reset loggers after shutdown
|
|
+ this.onServerExit();
|
|
+ // Paper end - move final shutdown items here
|
|
}
|
|
|
|
public String getLocalIp() {
|
|
@@ -1101,6 +1132,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");
|
|
}
|
|
@@ -1110,6 +1142,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( this.recentTps, 20 );
|
|
// Paper start - further improve server tick loop
|
|
@@ -1204,6 +1248,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
JvmProfiler.INSTANCE.onServerTick(this.smoothedTickTimeMillis);
|
|
}
|
|
} 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);
|
|
CrashReport crashreport = MinecraftServer.constructOrExtractCrashReport(throwable);
|
|
|
|
@@ -1228,15 +1278,15 @@ 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
|
|
- io.papermc.paper.log.CustomLogManager.forceReset(); // Paper - Reset loggers after shutdown
|
|
- this.onServerExit();
|
|
+ //io.papermc.paper.log.CustomLogManager.forceReset(); // Paper - Reset loggers after shutdown
|
|
+ //this.onServerExit(); // Paper - moved into stop
|
|
}
|
|
|
|
}
|
|
@@ -1343,6 +1393,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);
|
|
}
|
|
|
|
@@ -2178,7 +2234,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
|
this.worldData.setDataConfiguration(worlddataconfiguration);
|
|
this.resources.managers.updateRegistryTags();
|
|
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/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
|
|
index 89c2c26afc5f06c4f57716cadbebabb8854f3635..eb4fc900164d1fb3a78653ae8bc42ea30323f5b7 100644
|
|
--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
|
|
+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
|
|
@@ -325,7 +325,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.overworld()); // CraftBukkit - per-world
|
|
}
|
|
@@ -459,7 +459,8 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
|
|
// this.remoteStatusListener.stop(); // Paper - don't wait for remote connections
|
|
}
|
|
|
|
- System.exit(0); // CraftBukkit
|
|
+ hasFullyShutdown = true; // Paper
|
|
+ System.exit(this.abnormalExit ? 70 : 0); // CraftBukkit // Paper
|
|
}
|
|
|
|
@Override
|
|
@@ -840,7 +841,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 29b35b8c0a6001d626e327a82eaff26d068660db..60ba289e724463129dfb27aa5e3b6daf3dd7386e 100644
|
|
--- a/src/main/java/net/minecraft/server/players/PlayerList.java
|
|
+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
|
|
@@ -611,7 +611,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 - Configurable player collision; 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 2510589400b3012b827efcab477c6483d9d55901..43487a9ee202c5b0e5a416519939111f77b3059c 100644
|
|
--- a/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java
|
|
+++ b/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java
|
|
@@ -150,6 +150,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 ff8d947c5a498e097a4c480a5dc9af71b0be5ee9..ee49f59ba8ce5708fc5e244eba7b1f910cf69263 100644
|
|
--- a/src/main/java/net/minecraft/world/level/Level.java
|
|
+++ b/src/main/java/net/minecraft/world/level/Level.java
|
|
@@ -926,6 +926,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 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, 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 424c4613e202c6ba50fa0de65d2526d400a8e299..2a8609e33716949ff1877b6d10f64a9d7a7c81e9 100644
|
|
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
|
|
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
|
|
@@ -1179,6 +1179,7 @@ public class LevelChunk extends ChunkAccess {
|
|
|
|
gameprofilerfiller.pop();
|
|
} catch (Throwable throwable) {
|
|
+ if (throwable instanceof ThreadDeath) throw throwable; // 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, throwable);
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java
|
|
index 3b8a2de79c79302626f43f320b15bae52416cc37..c097f5d5fbd51cbbc01bbd54101905c59b3f3a4c 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/Main.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/Main.java
|
|
@@ -187,6 +187,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) {
|
|
@@ -298,8 +328,65 @@ 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());
|
|
+ tryPreloadClass(io.papermc.paper.chunk.system.RegionizedPlayerChunkLoader.PlayerChunkLoaderData.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 c6e8441e299f477ddb22c1ce2618710763978f1a..e8e93538dfd71de86515d9405f728db1631e949a 100644
|
|
--- a/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java
|
|
@@ -12,11 +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
|
|
+ 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 e3b262add194a126e731c68e68f3139a00cacacb..da7d5efd76c9ef92e9ce22860fec791890a687be 100644
|
|
--- a/src/main/java/org/spigotmc/RestartCommand.java
|
|
+++ b/src/main/java/org/spigotmc/RestartCommand.java
|
|
@@ -138,7 +138,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 9e5d08f57aa448552d100ca892c211d44441ef68..577f29519156f33b49ee319a64a52249630e28d8 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
|
|
@@ -136,9 +139,24 @@ 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
|
|
+ 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 637d64da9938e51a97338b9253b43889585c67bb..d2a75850af9c6ad2aca66a5f994f1b587d73eac4 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">
|
|
+<Configuration status="WARN" shutdownHook="disable">
|
|
<Appenders>
|
|
<Queue name="ServerGuiConsole">
|
|
<PatternLayout pattern="[%d{HH:mm:ss} %level]: %msg{nolookups}%n" />
|