From 56ac79cf32bb65923c653d8fb7a1f6680ee7bfa8 Mon Sep 17 00:00:00 2001 From: n9nn62p75t-wq Date: Sun, 18 Oct 2026 20:44:39 +0200 Subject: [PATCH] Don't block the server thread when a keepalive kick moves to the player region. keepConnectionAlive runs on the global tick. disconnect() then waited in managedBlock for that player's region, which cannot run until the tick returns. Queue the kick onto the region and return instead. --- .../ServerCommonPacketListenerImpl.java.patch | 37 +++++++++++++++---- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch index 0033bad9..bfb40900 100644 --- a/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch +++ b/shreddedpaper-server/minecraft-patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch @@ -11,12 +11,18 @@ import io.netty.channel.ChannelFutureListener; import net.minecraft.CrashReport; import net.minecraft.CrashReportCategory; -@@ -276,7 +_,7 @@ +@@ -52,3 +_,4 @@ + private volatile int latency; // Paper - improve keepalives - make volatile + private final io.papermc.paper.util.KeepAlive keepAlive; // Paper - improve keepalives + private volatile boolean suspendFlushingOnServerThread = false; ++ private final java.util.concurrent.atomic.AtomicBoolean shreddedDisconnectQueued = new java.util.concurrent.atomic.AtomicBoolean(); // ShreddedPaper - keepalive kick is handed to the player region +@@ -276,7 +_,8 @@ this.disconnect(DISCONNECT_UNEXPECTED_QUERY, io.papermc.paper.connection.DisconnectionReason.INVALID_COOKIE); // Paper - kick event cause } - protected void keepConnectionAlive() { + public void keepConnectionAlive() { // ShreddedPaper - make public ++ if (this.shreddedDisconnectQueued.get()) return; // ShreddedPaper - already queued, do not log another timeout every tick Profiler.get().push("keepAlive"); long now = Util.getMillis(); // Paper start - improve keepalives @@ -29,25 +35,40 @@ org.bukkit.craftbukkit.util.Waitable waitable = new org.bukkit.craftbukkit.util.Waitable() { @Override protected @Nullable Object evaluate() { -@@ -393,9 +_,20 @@ +@@ -393,9 +_,34 @@ } }; - this.server.processQueue.add(waitable); -+ // ShreddedPaper - run on player's thread ++ // ShreddedPaper start - run on the player's region, and never wait from a tick thread + if (this instanceof ServerGamePacketListenerImpl serverGamePacketListener) { + if (TickThread.isTickThread()) { -+ serverGamePacketListener.player.level().getChunkSource().tickingRegions.execute(RegionPos.forChunk(serverGamePacketListener.player.chunkPosition()), waitable); -+ } else { -+ ShreddedPaper.runSync(serverGamePacketListener.player, waitable); // this.server.processQueue.add(waitable); ++ // Caller does not own this player (keepalive runs on the global server thread). ++ // managedBlock() here deadlocks: the region cannot tick until this server tick returns. ++ if (!this.shreddedDisconnectQueued.compareAndSet(false, true)) { ++ return; ++ } ++ try { ++ serverGamePacketListener.player.level().getChunkSource().tickingRegions.scheduleTask(RegionPos.forChunk(serverGamePacketListener.player.chunkPosition()), () -> { ++ try { ++ waitable.run(); ++ } finally { ++ ServerCommonPacketListenerImpl.this.shreddedDisconnectQueued.set(false); ++ } ++ }); ++ } catch (Throwable throwable) { ++ this.shreddedDisconnectQueued.set(false); ++ throw throwable; ++ } ++ return; + } ++ ShreddedPaper.runSync(serverGamePacketListener.player, waitable); // this.server.processQueue.add(waitable); + } else { + this.server.processQueue.add(waitable); + } -+ // ShreddedPaper end - run on player's thread ++ // ShreddedPaper end - run on the player's region, and never wait from a tick thread try { -+ if (TickThread.isTickThread()) this.server.managedBlock(waitable::isDone); // ShreddedPaper - don't block when waiting waitable.get(); } catch (InterruptedException e) { Thread.currentThread().interrupt();