From 5bf65680a1d9009b0681ad462ba62516efc3a0b6 Mon Sep 17 00:00:00 2001 From: Shane Freeder Date: Wed, 9 Sep 2026 21:38:09 +0000 Subject: [PATCH] Restore detailed entity information in watchdog dumps Watchdog dumps lost their per-entity detail in 1.21.4. The instrumentation survived - ServerLevel still tracks the currently ticking entity, and Entity still records the move() start position and move vector under posLock - but the code that printed it was dropped while the "Detail more information in watchdog dumps" feature patch was inlined into the per-file patches, along with the four Entity accessors that exposed the move state. This restores the readout and those accessors. It lives in WatchdogThread rather than FeatureHooks since none of it is chunk-system dependent. The dump is wrapped in a catch-all: it reads concurrently mutating entity state off the server thread, and an exception there would otherwise cost us the thread dump and the halt that follows it. --- .../0001-Moonrise-optimisation-patches.patch | 16 ++-- .../0003-Entity-Activation-Range-2.0.patch | 8 +- ...017-Optimise-EntityScheduler-ticking.patch | 4 +- .../minecraft/world/entity/Entity.java.patch | 19 ++++- .../java/org/spigotmc/WatchdogThread.java | 74 +++++++++++++++++++ 5 files changed, 106 insertions(+), 15 deletions(-) diff --git a/paper-server/patches/features/0001-Moonrise-optimisation-patches.patch b/paper-server/patches/features/0001-Moonrise-optimisation-patches.patch index a5f3d6a17457..417ebbcbb383 100644 --- a/paper-server/patches/features/0001-Moonrise-optimisation-patches.patch +++ b/paper-server/patches/features/0001-Moonrise-optimisation-patches.patch @@ -29298,7 +29298,7 @@ index 09b6da207530987b483444cebeadc2f1c9a15247..3666c3efd188508153b6482db425648e + // Paper end - block counting } diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java -index ec8b47eebc30a73adb3195aa91fd5b82518cac2d..4b74c91b47318d048abc53952ebeae7615f50524 100644 +index d6772f96af5a103c41fcc776f478f79ca72da447..23001241b9d9927b7207f40ad6d61811ff30f9eb 100644 --- a/net/minecraft/world/entity/Entity.java +++ b/net/minecraft/world/entity/Entity.java @@ -164,7 +164,7 @@ public abstract class Entity @@ -29549,7 +29549,7 @@ index ec8b47eebc30a73adb3195aa91fd5b82518cac2d..4b74c91b47318d048abc53952ebeae76 public Entity(final EntityType type, final Level level) { this.type = type; -@@ -1484,34 +1590,76 @@ public abstract class Entity +@@ -1501,34 +1607,76 @@ public abstract class Entity } private Vec3 collide(final Vec3 movement) { @@ -29649,7 +29649,7 @@ index ec8b47eebc30a73adb3195aa91fd5b82518cac2d..4b74c91b47318d048abc53952ebeae76 } private static float[] collectCandidateStepUpHeights( -@@ -2832,17 +2980,106 @@ public abstract class Entity +@@ -2849,17 +2997,106 @@ public abstract class Entity return false; } @@ -29766,7 +29766,7 @@ index ec8b47eebc30a73adb3195aa91fd5b82518cac2d..4b74c91b47318d048abc53952ebeae76 } public InteractionResult interact(final Player player, final InteractionHand hand, final Vec3 location) { -@@ -4449,15 +4686,17 @@ public abstract class Entity +@@ -4466,15 +4703,17 @@ public abstract class Entity } public Iterable getIndirectPassengers() { @@ -29792,7 +29792,7 @@ index ec8b47eebc30a73adb3195aa91fd5b82518cac2d..4b74c91b47318d048abc53952ebeae76 } public int countPlayerPassengers() { -@@ -4768,6 +5007,15 @@ public abstract class Entity +@@ -4785,6 +5024,15 @@ public abstract class Entity } public final void setPosRaw(double x, double y, double z, boolean forceBoundingBoxUpdate) { @@ -29808,7 +29808,7 @@ index ec8b47eebc30a73adb3195aa91fd5b82518cac2d..4b74c91b47318d048abc53952ebeae76 if (!checkPosition(this, x, y, z)) { return; } -@@ -4917,6 +5165,12 @@ public abstract class Entity +@@ -4934,6 +5182,12 @@ public abstract class Entity @Override public final void setRemoved(final Entity.RemovalReason reason, org.bukkit.event.entity.EntityRemoveEvent.@Nullable Cause cause) { // CraftBukkit - add Bukkit remove cause @@ -29821,7 +29821,7 @@ index ec8b47eebc30a73adb3195aa91fd5b82518cac2d..4b74c91b47318d048abc53952ebeae76 org.bukkit.craftbukkit.event.CraftEventFactory.callEntityRemoveEvent(this, cause); // CraftBukkit final boolean alreadyRemoved = this.removalReason != null; // Paper - Folia schedulers if (this.removalReason == null) { -@@ -4928,7 +5182,7 @@ public abstract class Entity +@@ -4945,7 +5199,7 @@ public abstract class Entity this.stopRiding(); } @@ -29830,7 +29830,7 @@ index ec8b47eebc30a73adb3195aa91fd5b82518cac2d..4b74c91b47318d048abc53952ebeae76 this.levelCallback.onRemove(reason); this.onRemoval(reason); // Paper start - Folia schedulers -@@ -4966,7 +5220,7 @@ public abstract class Entity +@@ -4983,7 +5237,7 @@ public abstract class Entity public boolean shouldBeSaved() { return (this.removalReason == null || this.removalReason.shouldSave()) && !this.isPassenger() diff --git a/paper-server/patches/features/0003-Entity-Activation-Range-2.0.patch b/paper-server/patches/features/0003-Entity-Activation-Range-2.0.patch index 6ed4e07f3f37..6a0233096903 100644 --- a/paper-server/patches/features/0003-Entity-Activation-Range-2.0.patch +++ b/paper-server/patches/features/0003-Entity-Activation-Range-2.0.patch @@ -355,7 +355,7 @@ index 0000000000000000000000000000000000000000..829f7685904fd741e00403647ae76d32 + } +} diff --git a/net/minecraft/server/level/ServerLevel.java b/net/minecraft/server/level/ServerLevel.java -index e5328f08d6a323aa1307ed791a88916b4588939c..1afbe7a877f8055702809bc33b110bdf2933f999 100644 +index 4fce291cb7cd6eb2f4e47237fe247a5d666da0ad..6cf92a7ae011fe4fa55cd744dce8d5f8fb49b1ec 100644 --- a/net/minecraft/server/level/ServerLevel.java +++ b/net/minecraft/server/level/ServerLevel.java @@ -880,6 +880,7 @@ public class ServerLevel extends Level implements WorldGenLevel, ServerEntityGet @@ -463,7 +463,7 @@ index 271ed1128959287f6c1b6ca59ded377b88f5afff..f03fa06c0ba56f7f5e1e45bc1568a490 public void tick() { super.tick(); diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java -index a5792bc27fdc85ecf388ab44c0b1b3055fc41d21..4a18f74c7513f0ac6a3415163165d9239ee5b567 100644 +index 23001241b9d9927b7207f40ad6d61811ff30f9eb..1484558ea50760fbee7a31df413637ce685ea001 100644 --- a/net/minecraft/world/entity/Entity.java +++ b/net/minecraft/world/entity/Entity.java @@ -383,6 +383,15 @@ public abstract class Entity @@ -496,7 +496,7 @@ index a5792bc27fdc85ecf388ab44c0b1b3055fc41d21..4a18f74c7513f0ac6a3415163165d923 SynchedEntityData.Builder entityDataBuilder = new SynchedEntityData.Builder(this); entityDataBuilder.define(DATA_SHARED_FLAGS_ID, (byte)0); entityDataBuilder.define(DATA_AIR_SUPPLY_ID, this.getMaxAirSupply()); -@@ -1141,6 +1157,10 @@ public abstract class Entity +@@ -1158,6 +1174,10 @@ public abstract class Entity } else { if (moverType == MoverType.PISTON) { delta = this.limitPistonMovement(delta); @@ -507,7 +507,7 @@ index a5792bc27fdc85ecf388ab44c0b1b3055fc41d21..4a18f74c7513f0ac6a3415163165d923 if (delta.equals(Vec3.ZERO)) { return; } -@@ -1156,6 +1176,13 @@ public abstract class Entity +@@ -1173,6 +1193,13 @@ public abstract class Entity this.stuckSpeedMultiplier = Vec3.ZERO; this.setDeltaMovement(Vec3.ZERO); } diff --git a/paper-server/patches/features/0017-Optimise-EntityScheduler-ticking.patch b/paper-server/patches/features/0017-Optimise-EntityScheduler-ticking.patch index c7b9ca610cfc..945dbdd9890a 100644 --- a/paper-server/patches/features/0017-Optimise-EntityScheduler-ticking.patch +++ b/paper-server/patches/features/0017-Optimise-EntityScheduler-ticking.patch @@ -66,10 +66,10 @@ index 6de2929fda582cc4ceecd7bfe0b440ac6608669d..2381f8571f6e614dba1cfb62a1d396be io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.ADVENTURE_CLICK_MANAGER.handleQueue(this.tickCount); // Paper io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.DIALOG_CLICK_MANAGER.handleQueue(this.tickCount); // Paper diff --git a/net/minecraft/world/entity/Entity.java b/net/minecraft/world/entity/Entity.java -index f8f479bd0ce2498c0cf4beab07d961247735e23c..d958c4717739e588c5f8985dec5d215c745f49fc 100644 +index 1484558ea50760fbee7a31df413637ce685ea001..db67d4c41ad6174e9e543c5476b6d40aae1e7f04 100644 --- a/net/minecraft/world/entity/Entity.java +++ b/net/minecraft/world/entity/Entity.java -@@ -5237,6 +5237,11 @@ public abstract class Entity +@@ -5254,6 +5254,11 @@ public abstract class Entity this.getBukkitEntity().taskScheduler.retire(); } // Paper end - Folia schedulers diff --git a/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch b/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch index ba31084206e7..55455898bb4f 100644 --- a/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/entity/Entity.java.patch @@ -492,7 +492,7 @@ } public boolean isFree(final double xa, final double ya, final double za) { -@@ -709,7 +_,28 @@ +@@ -709,7 +_,45 @@ return this.onGround; } @@ -504,6 +504,23 @@ + private double moveStartX; + private double moveStartY; + private double moveStartZ; ++ ++ @Nullable ++ public final Vec3 getMoveVector() { ++ return this.moveVector; ++ } ++ ++ public final double getMoveStartX() { ++ return this.moveStartX; ++ } ++ ++ public final double getMoveStartY() { ++ return this.moveStartY; ++ } ++ ++ public final double getMoveStartZ() { ++ return this.moveStartZ; ++ } + // Paper end - detailed watchdog information + public void move(final MoverType moverType, Vec3 delta) { diff --git a/paper-server/src/main/java/org/spigotmc/WatchdogThread.java b/paper-server/src/main/java/org/spigotmc/WatchdogThread.java index ab6c553f0f5c..de3f9113a075 100644 --- a/paper-server/src/main/java/org/spigotmc/WatchdogThread.java +++ b/paper-server/src/main/java/org/spigotmc/WatchdogThread.java @@ -109,6 +109,7 @@ public void run() { logger.log(Level.SEVERE, "------------------------------"); logger.log(Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Paper!):"); // Paper FeatureHooks.dumpAllChunkLoadInfo(MinecraftServer.getServer(), isLongTimeout); // Paper - log detailed tick information + WatchdogThread.dumpTickingInfo(logger); WatchdogThread.dumpThread(ManagementFactory.getThreadMXBean().getThreadInfo(MinecraftServer.getServer().getRunningThread().threadId(), Integer.MAX_VALUE), logger); logger.log(Level.SEVERE, "------------------------------"); @@ -156,6 +157,79 @@ public void run() { } } + private static void dumpTickingInfo(Logger logger) { + // The state read below is mutated by the (hung) server thread while we read it, so it may be torn or null + // in ways the normal invariants disallow. Never let that take down the watchdog itself - a failed dump must + // not stop us from printing the thread dump and, on a long timeout, halting the server. + try { + // ticking entities + for (net.minecraft.world.entity.Entity entity : net.minecraft.server.level.ServerLevel.getCurrentlyTickingEntities()) { + WatchdogThread.dumpEntity(entity, logger); + net.minecraft.world.entity.Entity vehicle = entity.getVehicle(); + if (vehicle != null) { + logger.log(Level.SEVERE, "Detailing vehicle for above entity:"); + WatchdogThread.dumpEntity(vehicle, logger); + } + } + + // packet processors + for (net.minecraft.network.PacketListener packetListener : net.minecraft.network.PacketProcessor.getCurrentPacketProcessors()) { + if (packetListener instanceof net.minecraft.server.network.ServerGamePacketListenerImpl gamePacketListener) { + net.minecraft.server.level.ServerPlayer player = gamePacketListener.player; + long totalPackets = net.minecraft.network.PacketProcessor.getTotalProcessedPackets(); + if (player == null) { + logger.log(Level.SEVERE, "Handling packet for player connection or ticking player connection (null player): " + packetListener); + } else { + WatchdogThread.dumpEntity(player, logger); + net.minecraft.world.entity.Entity vehicle = player.getVehicle(); + if (vehicle != null) { + logger.log(Level.SEVERE, "Detailing vehicle for above entity:"); + WatchdogThread.dumpEntity(vehicle, logger); + } + } + logger.log(Level.SEVERE, "Total packets processed on the main thread for all players: " + totalPackets); + } else { + logger.log(Level.SEVERE, "Handling packet for connection: " + packetListener); + } + } + } catch (Throwable thr) { + logger.log(Level.SEVERE, "Failed to dump ticking information", thr); + } + } + + private static void dumpEntity(final net.minecraft.world.entity.Entity entity, Logger logger) { + double posX, posY, posZ; + net.minecraft.world.phys.Vec3 mot; + double moveStartX, moveStartY, moveStartZ; + net.minecraft.world.phys.Vec3 moveVec; + synchronized (entity.posLock) { + posX = entity.getX(); + posY = entity.getY(); + posZ = entity.getZ(); + mot = entity.getDeltaMovement(); + moveStartX = entity.getMoveStartX(); + moveStartY = entity.getMoveStartY(); + moveStartZ = entity.getMoveStartZ(); + moveVec = entity.getMoveVector(); + } + + String entityType = net.minecraft.world.entity.EntityType.getKey(entity.getType()).toString(); + java.util.UUID entityUUID = entity.getUUID(); + net.minecraft.world.level.Level world = entity.level(); + + logger.log(Level.SEVERE, "Ticking entity: " + entityType + ", entity class: " + entity.getClass().getName()); + logger.log(Level.SEVERE, "Entity status: removed: " + entity.isRemoved() + ", valid: " + entity.valid + ", alive: " + entity.isAlive() + ", is passenger: " + entity.isPassenger()); + logger.log(Level.SEVERE, "Entity UUID: " + entityUUID); + logger.log(Level.SEVERE, "Position: world: '" + (world == null ? "unknown world?" : world.getWorld().getName()) + "' at location (" + posX + ", " + posY + ", " + posZ + ")"); + logger.log(Level.SEVERE, "Velocity: " + (mot == null ? "unknown velocity" : mot.toString()) + " (in blocks per tick)"); + logger.log(Level.SEVERE, "Entity AABB: " + entity.getBoundingBox()); + if (moveVec != null) { + logger.log(Level.SEVERE, "Move call information: "); + logger.log(Level.SEVERE, "Start position: (" + moveStartX + ", " + moveStartY + ", " + moveStartZ + ")"); + logger.log(Level.SEVERE, "Move vector: " + moveVec.toString()); + } + } + private static void dumpThread(ThreadInfo thread, Logger logger) { logger.log(Level.SEVERE, "------------------------------");