2021-06-11 12:02:28 +00:00
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Sun, 12 Apr 2020 15:50:48 -0400
Subject: [PATCH] Improved Watchdog Support
Forced Watchdog Crash support and Improve Async Shutdown
If the request to shut down the server is received while we are in
a watchdog hang, immediately treat it as a crash and begin the shutdown
process. Shutdown process is now improved to also shutdown cleanly when
not using restart scripts either.
If a server is deadlocked, a server owner can send SIGUP (or any other signal
the JVM understands to shut down as it currently does) and the watchdog
will no longer need to wait until the full timeout, allowing you to trigger
a close process and try to shut the server down gracefully, saving player and
world data.
Previously there was no way to trigger this outside of waiting for a full watchdog
timeout, which may be set to a really long time...
Additionally, fix everything to do with shutting the server down asynchronously.
Previously, nearly everything about the process was fragile and unsafe. Main might
not have actually been frozen, and might still be manipulating state.
Or, some reuest might ask main to do something in the shutdown but main is dead.
Or worse, other things might start closing down items such as the Console or Thread Pool
before we are fully shutdown.
This change tries to resolve all of these issues by moving everything into the stop
method and guaranteeing only one thread is stopping the server.
We then issue Thread Death to the main thread of another thread initiates the stop process.
We have to ensure Thread Death propagates correctly though to stop main completely.
This is to ensure that if main isn't truely stuck, it's not manipulating state we are trying to save.
This also moves all plugins who register "delayed init" tasks to occur just before "Done" so they
are properly accounted for and wont trip watchdog on init.
diff --git a/src/main/java/com/destroystokyo/paper/Metrics.java b/src/main/java/com/destroystokyo/paper/Metrics.java
2021-06-14 01:06:38 +00:00
index e3b74dbdf8e14219a56fab939f3174e0c2f66de6..218f5bafeed8551b55b91c7fccaf6935c8b631ca 100644
2021-06-11 12:02:28 +00:00
--- 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
2021-11-24 22:26:32 +00:00
index c54530d1c66845b190a9cb6d06f985943bb4dbe1..35c9b3e6c5a2d11b4dbd491b16647df105960d1a 100644
2021-06-11 12:02:28 +00:00
--- a/src/main/java/net/minecraft/CrashReport.java
+++ b/src/main/java/net/minecraft/CrashReport.java
2021-11-24 22:26:32 +00:00
@@ -228,6 +228,7 @@ public class CrashReport {
2021-06-11 12:02:28 +00:00
}
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
2021-11-24 22:26:32 +00:00
index 64370c9c5e443f8e84e7f2f018c56f0223eb4b24..cab914abbc6c655895f09d71f253889a0dcc4e02 100644
2021-06-11 12:02:28 +00:00
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
2021-11-24 11:06:34 +00:00
@@ -297,7 +297,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
2021-06-14 01:06:38 +00:00
public java.util.Queue<Runnable> processQueue = new java.util.concurrent.ConcurrentLinkedQueue<Runnable>();
2021-06-11 12:02:28 +00:00
public int autosavePeriod;
public Commands vanillaCommandDispatcher;
- private boolean forceTicks;
+ public boolean forceTicks; // Paper
// CraftBukkit end
// Spigot start
public static final int TPS = 20;
2021-11-24 11:06:34 +00:00
@@ -308,6 +308,9 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
2021-06-11 12:02:28 +00:00
// Spigot end
Merge tuinity (#6413)
This PR contains all of Tuinity's patches. Very notable ones are:
- Highly optimised collisions
- Optimised entity lookups by bounding box (Mojang made regressions in 1.17, this brings it back to 1.16)
- Starlight https://github.com/PaperMC/Starlight
- Rewritten dataconverter system https://github.com/PaperMC/DataConverter
- Random block ticking optimisation (wrongly dropped from Paper 1.17)
- Chunk ticking optimisations
- Anything else I've forgotten in the 60 or so patches
If you are a previous Tuinity user, your config will not migrate. You must do it yourself. The config options have simply been moved into paper.yml, so it will be an easy migration. However, please note that the chunk loading options in tuinity.yml are NOT compatible with the options in paper.yml.
* Port tuinity, initial patchset
* Update gradle to 7.2
jmp said it fixes rebuildpatches not working for me. it fucking better
* Completely clean apply
* Remove tuinity config, add per player api patch
* Remove paper reobf mappings patch
* Properly update gradlew
* Force clean rebuild
* Mark fixups
Comments and ATs still need to be done
* grep -r "Tuinity"
* Fixup
* Ensure gameprofile lastaccess is written only under the state lock
* update URL for dataconverter
* Only clean rebuild tuinity patches
might fix merge conflicts
* Use UTF-8 for gradlew
* Clean rb patches again
* Convert block ids used as item ids
Neither the converters of pre 1.13 nor DFU handled these cases,
as by the time they were written the game at the time didn't
consider these ids valid - they would be air. Because of this,
some worlds have logspam since only DataConverter (not DFU or
legacy converters) will warn when an invalid id has been
seen.
While quite a few do need to now be considered as air, quite a lot
do not. So it makes sense to add conversion for these items, instead
of simply suppressing or ignoring the logs. I've now added id -> string conversion
for all block ids that could be used as items that existed in the game
before 1.7.10 (I have no interest in tracking down the
exact version block ids stopped working) that were on
https://minecraft-ids.grahamedgecombe.com/
Items that did not directly convert to new items will
be instead converted to air: stems, wheat crops, piston head,
tripwire wire block
* Fix LightPopulated parsing in V1466
The DFU code was checking if the number existed, not if it
didn't exist. I misread the original code.
* Always parse protochunk light sources unless it is marked as non-lit
Chunks not marked as lit will always go through the light engine,
so they should always have their block sources parsed.
* Update custom names to JSON for players
Missed this fix from CB, as it was inside
the DataFixers class.
I decided to double check all of the CB changes again:
DataFixers.java was the only area I missed, as I had inspected all
datafixer diffs and implemented them all into DataConverter. I also
checked Bootstrap.java again, and re-evaluated their changes. I had
previously done this, but determined that they were all bad.
The changes to make standing_sign block map to oak_sign block in
V1450 is bad, because that's not the item id V1450 accepts. Only
in 1.14 did oak_sign even exist, and as expected there is a converter
to rename all existing sign items/blocks.
The fix to register the portal block under id 1440 is useless, as
the flattenning logic will default to the lowest registered id - which
is the exact blockstate that CB registers into 1440. So it just
doesn't do anything.
The extra item ids in the id -> string converter are already added,
but I found this from EMC originally.
The change for the spawn egg id 23 -> Arrow is just wrong,
that id DOES correspond to TippedArrow, NOT Arrow. As
expected, the spawn egg already has a dedicated mapping for
Arrow, which is id 10 - which was Arrow's entity id.
I also ported a fix for the cooked_fished id update. This doesn't
really matter since there is already a dataconverter to fix this,
but the game didn't accept cooked_fished at the time. So I see
no harm.
* Review all converters and walkers
- Refactor V99 to have helper methods for defining entity/tile
entity types
- Automatically namespace all ids that should be namespaced.
While vanilla never saved non-namespaced data for things that
are namespaced, plugins/users might have.
- Synchronised the identity ensure map in HelperBlockFlatteningV1450
- Code style consistency
- Add missing log warning in V102 for ITEM_NAME type conversion
- Use getBoolean instead of getByte
- Use ConverterAbstractEntityRename for V143 TippedArrow -> Arrow
rename, as it will affect ENTITY_NAME type
- Always set isVillager to false in V502 for Zombie
- Register V808's converter under subversion 1 like DFU
- Register a breakpoint for V1.17.1. In the future, all final
versions of major releases will have a breakpoint so that
the work required to determine if a converter needs a breakpoint
is minimal
- Validate that a dataconverter is only registered for a version
that is registered
- ConverterFlattenTileEntity is actually ConverterFlattenEntity
It even registered the converters under TILE_ENTITY, instead of
ENTITY.
- Fix id comparison in V1492 STRUCTURE_FEATURE renamer
- Use ConverterAbstractStatsRename for V1510 stats renamer
At the time I had written that class, the abstract renamer didn't
exist.
- Ensure OwnerUUID is at least set to empty string in
V1904 if the ocelot is converted to a cat (this is
likely so that it retains a collar)
- Use generic read/write for Records in V1946
Records is actually a list, not a map. So reading map was
invalid.
* Always set light to zero when propagating decrease
This fixes an almost infinite loop where light values
would be spam queued on a very small subset on blocks.
This also likely fixes the memory issues people were
seeing.
* re-organize patches
* Apply and fix conflicts
* Revert some patches
getChunkAt retains chunks so that plugins don't spam loads
revert mc-4 fix will remain unless issues pop up
* Shuffle iterated chunks if per player is not enabled
Can help with some mob spawning stacking up at locations
* Make per player default, migrate all configs
* Adjust comments in fixups
* Rework config for player chunk loader
Old config is not compatible. Move all configs to be
under `settings` in paper.yml
The player chunk loader has been modified to
less aggressively load chunks, but to send
chunks at higher rates compared to tuinity. There are
new config entries to tune this behavior.
* Add back old constructor to CompressionEncoder/Decoder (fixes
Tuinity #358)
* Raise chunk loading default limits
* Reduce worldgen thread workers for lower core count cpus
* Raise limits for chunk loading config
Also place it under `chunk-loading`
* Disable max chunk send rate by default
* Fix conflicts and rebuild patches
* Drop default send rate again
Appears to be still causing problems for no known reason
* Raise chunk send limits to 100 per player
While a low limit fixes ping issues for some people, most people
do not suffer from this issue and thus should not suffer from
an extremely slow load-in rate.
* Rebase part 1
Autosquash the fixups
* Move not implemented up
* Fixup mc-dev fixes
Missed this one
* Rebase per player viewdistance api into the original api patch
* Remove old light engine patch part 1
The prioritisation must be kept from it, so that part
has been rebased into the priority patch.
Part 2 will deal with rebasing all of the patches _after_
* Rebase remaining patches for old light patch removal
* Remove other mid tick patch
* Remove Optimize-PlayerChunkMap-memory-use-for-visibleChunks.patch
Replaced by `Do not copy visible chunks`
* Revert AT for Vec3i setX/Y/Z
The class is immutable. set should not be exposed
* Remove old IntegerUtil class
* Replace old CraftChunk#getEntities patch
* Remove import for SWMRNibbleArray in ChunkAccess
* Finished merge checklist
* Remove ensureTickThread impl in urgency patch
Co-authored-by: Spottedleaf <Spottedleaf@users.noreply.github.com>
Co-authored-by: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
2021-08-31 11:02:11 +00:00
public static long currentTickLong = 0L; // Paper
2021-06-11 12:02:28 +00:00
+ 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 Thread(() -> {
2021-11-24 11:06:34 +00:00
@@ -914,6 +917,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
2021-06-11 12:02:28 +00:00
// CraftBukkit start
private boolean hasStopped = false;
+ public volatile boolean hasFullyShutdown = false; // Paper
private final Object stopLock = new Object();
public final boolean hasStopped() {
2021-06-14 01:06:38 +00:00
synchronized (this.stopLock) {
2021-11-24 11:06:34 +00:00
@@ -928,6 +932,19 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
2021-06-14 01:06:38 +00:00
if (this.hasStopped) return;
this.hasStopped = true;
2021-06-11 12:02:28 +00:00
}
+ // 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
MinecraftServer.LOGGER.info("Stopping server");
MinecraftTimings.stopServer(); // Paper
2021-11-24 11:06:34 +00:00
@@ -991,7 +1008,18 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
2021-06-14 01:06:38 +00:00
this.getProfileCache().save(false); // Paper
2021-06-11 12:02:28 +00:00
}
// 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");
2021-06-14 01:06:38 +00:00
+ Util.shutdownExecutors(); // Paper
2021-06-11 12:02:28 +00:00
+ LOGGER.info("Closing Server");
+ try {
+ net.minecrell.terminalconsole.TerminalConsoleAppender.close(); // Paper - Use TerminalConsoleAppender
+ } catch (Exception e) {
+ }
+ this.onServerExit();
+ // Paper end
}
public String getLocalIp() {
2021-11-24 11:06:34 +00:00
@@ -1084,6 +1112,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
2021-06-11 12:02:28 +00:00
protected void runServer() {
try {
+ long serverStartTime = Util.getNanos(); // Paper
if (this.initServer()) {
this.nextTickTime = Util.getMillis();
this.status.setDescription(new TextComponent(this.motd));
2021-11-24 11:06:34 +00:00
@@ -1091,6 +1120,18 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
2021-06-11 12:02:28 +00:00
this.updateStatusIcon(this.status);
// 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
2021-11-24 11:06:34 +00:00
@@ -1148,6 +1189,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
2021-06-11 12:02:28 +00:00
this.onServerCrash((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 )
2021-11-24 11:06:34 +00:00
@@ -1187,14 +1234,14 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
this.profileCache.clearExecutor();
}
2021-06-11 12:02:28 +00:00
- org.spigotmc.WatchdogThread.doStop(); // Spigot
+ //org.spigotmc.WatchdogThread.doStop(); // Spigot // Paper - move into stop
// CraftBukkit start - Restore terminal to original settings
try {
- net.minecrell.terminalconsole.TerminalConsoleAppender.close(); // Paper - Use TerminalConsoleAppender
+ //net.minecrell.terminalconsole.TerminalConsoleAppender.close(); // Paper - Move into stop
} catch (Exception ignored) {
}
// CraftBukkit end
- this.onServerExit();
2021-11-24 11:06:34 +00:00
+ //this.onServerExit(); // Paper - moved into stop
2021-06-11 12:02:28 +00:00
}
}
2021-11-24 11:06:34 +00:00
@@ -1233,6 +1280,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
2021-06-11 12:02:28 +00:00
@Override
public TickTask wrapRunnable(Runnable runnable) {
+ // Paper start - anything that does try to post to main during watchdog crash, run on watchdog
+ if (this.hasStopped && Thread.currentThread().equals(shutdownThread)) {
+ runnable.run();
+ runnable = () -> {};
+ }
+ // Paper end
return new TickTask(this.tickCount, runnable);
}
2021-11-24 11:06:34 +00:00
@@ -1461,6 +1514,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
2021-06-11 12:02:28 +00:00
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
2021-11-24 22:26:32 +00:00
@@ -1896,7 +1950,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
2021-06-11 12:02:28 +00:00
this.packRepository.setSelected(datapacks);
2021-06-14 01:06:38 +00:00
this.worldData.setDataPackConfig(MinecraftServer.getSelectedPacks(this.packRepository));
2021-06-11 12:02:28 +00:00
datapackresources.updateGlobals();
- this.getPlayerList().saveAll();
+ if (Thread.currentThread() != this.serverThread) return; // Paper
+ //this.getPlayerList().savePlayers(); // Paper - we don't need to do this
this.getPlayerList().reloadResources();
this.functionManager.replaceLibrary(this.resources.getFunctionLibrary());
this.structureManager.onResourceManagerReload(this.resources.getResourceManager());
diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
2021-11-24 11:06:34 +00:00
index 049eb5693dc98e1d0ec3bd88c73a41fdb2f59bff..0716aaf29f9d76240a0de4ca02daba442b36ec7d 100644
2021-06-11 12:02:28 +00:00
--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
2021-11-24 11:06:34 +00:00
@@ -281,7 +281,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
2021-06-11 12:02:28 +00:00
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) {
2021-11-24 11:06:34 +00:00
((GameRules.BooleanValue) this.getGameRules().getRule(GameRules.RULE_ANNOUNCE_ADVANCEMENTS)).set(dedicatedserverproperties.announcePlayerAchievements, this);
2021-06-11 12:02:28 +00:00
}
2021-11-24 11:06:34 +00:00
@@ -438,7 +438,8 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
2021-06-11 12:02:28 +00:00
//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
2021-11-24 11:06:34 +00:00
@@ -764,7 +765,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
2021-06-11 12:02:28 +00:00
@Override
public void stopServer() {
super.stopServer();
- Util.shutdownExecutors();
2021-11-24 11:06:34 +00:00
+ //Util.shutdownExecutors(); // Paper - moved into super
SkullBlockEntity.clear();
2021-06-11 12:02:28 +00:00
}
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
2021-11-26 22:58:39 +00:00
index 01f87e67d6760b295d7333973f39f8ff6ef3cee5..368dc3a7bd04b106db5b83d73dcab2e09c8d8af9 100644
2021-06-11 12:02:28 +00:00
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
2021-11-26 22:58:39 +00:00
@@ -552,6 +552,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
2021-06-11 12:02:28 +00:00
MutableBoolean mutableboolean = new MutableBoolean();
do {
+ boolean isShuttingDown = level.getServer().hasStopped(); // Paper
mutableboolean.setFalse();
list.stream().map((playerchunk) -> {
CompletableFuture completablefuture;
diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
2021-11-24 18:46:06 +00:00
index 8901bb31524613bf3fbec3cc5d3c228f8b3f43a4..4e26475bf0f541d6b2f8b6eccfeaeb9ee5b4b2b8 100644
2021-06-11 12:02:28 +00:00
--- a/src/main/java/net/minecraft/server/players/PlayerList.java
+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
2021-11-24 11:06:34 +00:00
@@ -512,7 +512,7 @@ public abstract class PlayerList {
2021-06-14 01:06:38 +00:00
this.cserver.getPluginManager().callEvent(playerQuitEvent);
2021-06-11 12:02:28 +00:00
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
2021-11-26 22:58:39 +00:00
index 7bf4bf5cb2c1b54a7e2733091f48f3a824336d36..dcce05d2f4ab16424db4ab103a12188e207a457b 100644
2021-06-11 12:02:28 +00:00
--- a/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java
+++ b/src/main/java/net/minecraft/util/thread/BlockableEventLoop.java
2021-06-14 01:06:38 +00:00
@@ -148,6 +148,7 @@ public abstract class BlockableEventLoop<R extends Runnable> implements Profiler
2021-06-11 12:02:28 +00:00
try {
task.run();
2021-06-14 01:06:38 +00:00
} catch (Exception var3) {
+ if (var3.getCause() instanceof ThreadDeath) throw var3; // Paper
LOGGER.fatal("Error executing task on {}", this.name(), var3);
2021-06-11 12:02:28 +00:00
}
2021-11-26 22:58:39 +00:00
2021-06-11 12:02:28 +00:00
diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
2021-11-24 22:26:32 +00:00
index 9842e4d39130f45481b643df38ee4b25076c5c08..4a77494ddb94d5fd2d82665c6bd4e513b66f1d9f 100644
2021-06-11 12:02:28 +00:00
--- a/src/main/java/net/minecraft/world/level/Level.java
+++ b/src/main/java/net/minecraft/world/level/Level.java
2021-11-24 18:46:06 +00:00
@@ -831,6 +831,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
2021-06-11 12:02:28 +00:00
try {
tickConsumer.accept(entity);
} catch (Throwable throwable) {
+ if (throwable instanceof ThreadDeath) throw throwable; // Paper
// Paper start - Prevent tile entity and entity crashes
2021-06-21 08:09:18 +00:00
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);
2021-06-14 01:06:38 +00:00
diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
2021-11-24 22:26:32 +00:00
index 1df4ddab06b6065464302bdc0e4eb0950964fa84..b92da719a5d35a60a2e13ccb0f55c41b242f9b50 100644
2021-06-14 01:06:38 +00:00
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
2021-11-24 22:26:32 +00:00
@@ -1068,6 +1068,7 @@ public class LevelChunk extends ChunkAccess {
2021-06-14 01:06:38 +00:00
gameprofilerfiller.pop();
} catch (Throwable throwable) {
+ if (throwable instanceof ThreadDeath) throw throwable; // Paper
// Paper start - Prevent tile entity and entity crashes
2021-06-21 08:09:18 +00:00
final String msg = String.format("BlockEntity threw exception at %s:%s,%s,%s", LevelChunk.this.getLevel().getWorld().getName(), this.getPos().getX(), this.getPos().getY(), this.getPos().getZ());
net.minecraft.server.MinecraftServer.LOGGER.error(msg, throwable);
2021-06-11 12:02:28 +00:00
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
2021-11-24 11:06:34 +00:00
index b9b5c7c3d4e991dd62ed7acb33e02c2523fe5d46..c7e2ce7d2fa80c6c97ebe94ec5c36c9eb7356f45 100644
2021-06-11 12:02:28 +00:00
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
2021-11-24 11:06:34 +00:00
@@ -2049,7 +2049,7 @@ public final class CraftServer implements Server {
2021-06-11 12:02:28 +00:00
@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
2021-11-24 11:06:34 +00:00
index 867c0802557375b63e69907bdb3ccd66644f1418..1ade239c9aacf2334c6dee773e0b37f3969723da 100644
2021-06-11 12:02:28 +00:00
--- 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.ExceptionCollector;
+import net.minecraft.world.level.lighting.LayerLightEventListener;
import net.minecrell.terminalconsole.TerminalConsoleAppender; // Paper
public class Main {
2021-11-24 11:06:34 +00:00
@@ -162,6 +164,36 @@ public class Main {
2021-06-11 12:02:28 +00:00
OptionSet options = null;
+ // Paper start - preload logger classes to avoid plugins mixing versions
+ tryPreloadClass("org.apache.logging.log4j.core.Core");
2021-08-12 17:55:20 +00:00
+ tryPreloadClass("org.apache.logging.log4j.core.appender.AsyncAppender");
2021-06-11 12:02:28 +00:00
+ 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) {
2021-11-24 11:06:34 +00:00
@@ -261,8 +293,64 @@ public class Main {
2021-06-11 12:02:28 +00:00
} 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(LayerLightEventListener.DummyLightLayerEventListener.class.getName());
+ tryPreloadClass(LayerLightEventListener.class.getName());
+ tryPreloadClass(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
2021-06-14 01:06:38 +00:00
index b4a19d80bbf71591f25729fd0e98590350cb31d0..d752720f2f234b9dbd2117333fee1bfad663ec02 100644
2021-06-11 12:02:28 +00:00
--- 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;
2021-06-14 01:06:38 +00:00
this.server.close();
2021-06-11 12:02:28 +00:00
+ 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
2021-06-14 01:06:38 +00:00
index a142a56a920e153ed84c08cece993f10d76f7793..92d97a5810a379b427a99b4c63fb9844d823a84f 100644
2021-06-11 12:02:28 +00:00
--- 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
2021-06-14 01:06:38 +00:00
index 1ffb208094f521883ef0e23baf5fb29380b14273..4d271cae88c16ed2419f896c728fdff612540500 100644
2021-06-11 12:02:28 +00:00
--- a/src/main/java/org/spigotmc/WatchdogThread.java
+++ b/src/main/java/org/spigotmc/WatchdogThread.java
2021-06-14 01:06:38 +00:00
@@ -12,6 +12,7 @@ import org.bukkit.Bukkit;
2021-06-11 12:02:28 +00:00
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;
2021-06-14 01:06:38 +00:00
@@ -40,6 +41,7 @@ public class WatchdogThread extends Thread
2021-06-11 12:02:28 +00:00
{
2021-06-14 01:06:38 +00:00
if ( WatchdogThread.instance == null )
2021-06-11 12:02:28 +00:00
{
+ if (timeoutTime <= 0) timeoutTime = 300; // Paper
2021-06-14 01:06:38 +00:00
WatchdogThread.instance = new WatchdogThread( timeoutTime * 1000L, restart );
WatchdogThread.instance.start();
2021-06-11 12:02:28 +00:00
} else
@@ -71,12 +73,13 @@ public class WatchdogThread extends Thread
// Paper start
Logger log = Bukkit.getServer().getLogger();
2021-06-14 01:06:38 +00:00
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
2021-06-11 12:02:28 +00:00
+ MinecraftServer server = MinecraftServer.getServer();
2021-06-14 01:06:38 +00:00
+ if ( this.lastTick != 0 && this.timeoutTime > 0 && WatchdogThread.hasStarted && (!server.isRunning() || (currentTime > this.lastTick + this.earlyWarningEvery && !DISABLE_WATCHDOG) )) // Paper - add property to disable
2021-06-11 12:02:28 +00:00
{
- 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
2021-06-14 01:06:38 +00:00
com.destroystokyo.paper.io.chunk.ChunkTaskManager.dumpAllChunkLoadInfo(); // Paper
- WatchdogThread.dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE ), log );
+ WatchdogThread.dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( server.serverThread.getId(), Integer.MAX_VALUE ), log );
2021-06-11 12:02:28 +00:00
log.log( Level.SEVERE, "------------------------------" );
//
// Paper start - Only print full dump on long timeouts
2021-06-14 01:06:38 +00:00
@@ -138,9 +141,25 @@ public class WatchdogThread extends Thread
2021-06-11 12:02:28 +00:00
if ( isLongTimeout )
{
2021-06-14 01:06:38 +00:00
- if ( this.restart && !MinecraftServer.getServer().hasStopped() )
2021-06-11 12:02:28 +00:00
+ 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
2021-08-12 17:55:20 +00:00
index 3dc317e466e1b93dff030794dd7f29ca1b266778..d285dbec16272db6b8a71865e05924ad66087407 100644
2021-06-11 12:02:28 +00:00
--- 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" />