Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,283 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com>
Date: Sun, 14 Jun 2026 20:49:01 +0900
Subject: [PATCH] Paper: Disconnect State Fixes

Original license: GPL-3.0-only
Original project: https://github.com/PaperMC/Paper
Paper commit: https://github.com/PaperMC/Paper/commit/8021488ef48a4aae5246cd3d2f97b8573bffdad9

Backported from Paper 26.1.2

diff --git a/net/minecraft/network/Connection.java b/net/minecraft/network/Connection.java
index 4e16552680f86cf1589f862cb38c0f43df344e6a..c33bd4338d7164acd566c6fbcd33ddbad44f334a 100644
--- a/net/minecraft/network/Connection.java
+++ b/net/minecraft/network/Connection.java
@@ -90,6 +90,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
// Paper start - NetworkClient implementation
public int protocolVersion;
public java.net.InetSocketAddress virtualHost;
+ public boolean handleConnectionDisconnectOnNextTick = false; // Paper - Force kill connection ticking
private static boolean enableExplicitFlush = Boolean.getBoolean("paper.explicit-flush"); // Paper - Disable explicit network manager flushing
// Paper end
// Paper start - add utility methods
@@ -521,7 +522,8 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
}
} catch (final Exception e) {
LOGGER.error("NetworkException: {}", player, e);
- this.disconnect(Component.translatable("disconnect.genericReason", "Internal Exception: " + e.getMessage()));
+ Component reason = Component.translatable("disconnect.genericReason", "Internal Exception: " + e.getMessage());
+ this.send(new ClientboundDisconnectPacket(reason), PacketSendListener.thenRun(() -> this.disconnect(reason)));
packet.onPacketDispatchFinish(player, null);
}
// Paper end - Optimize network
@@ -672,7 +674,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
}

if (this.isConnected()) {
- this.channel.close(); // We can't wait as this may be called from an event loop.
+ this.channel.close().awaitUninterruptibly();
this.disconnectionDetails = disconnectionDetails;
}
}
diff --git a/net/minecraft/network/PacketProcessor.java b/net/minecraft/network/PacketProcessor.java
index d501168e40f951ee37efcbc464c3ab1307051060..eda0baf2c7736a1fe2694a72677c407f606b6b92 100644
--- a/net/minecraft/network/PacketProcessor.java
+++ b/net/minecraft/network/PacketProcessor.java
@@ -93,7 +93,6 @@ public class PacketProcessor implements AutoCloseable {
public void handle() {
packetProcessing.push(this.listener); // Paper - detailed watchdog information
try { // Paper - detailed watchdog information
- if (this.listener instanceof net.minecraft.server.network.ServerCommonPacketListenerImpl serverCommonPacketListener && serverCommonPacketListener.processedDisconnect) return; // Paper - Don't handle sync packets for kicked players
if (this.listener.shouldHandleMessage(this.packet)) {
try {
this.packet.handle(this.listener);
diff --git a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
index 59a5c6bd8da82c293bba739fba43dc12d28fa8a4..160e86ff0462c847a36e9e32b7c87483a53cff37 100644
--- a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
@@ -50,7 +50,6 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
private volatile boolean suspendFlushingOnServerThread = false;
// CraftBukkit start
public final org.bukkit.craftbukkit.CraftServer cserver;
- public boolean processedDisconnect;
// CraftBukkit end
public final java.util.Map<java.util.UUID, net.kyori.adventure.resource.ResourcePackCallback> packCallbacks = new java.util.concurrent.ConcurrentHashMap<>(); // Paper - adventure resource pack callbacks
// Gale start - Purpur - send multiple keep-alive packets
@@ -145,20 +144,14 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
if (ka.challengeId() == packet.getId()) {
itr.remove();

- if (!this.processedDisconnect) {
- LOGGER.info("Disconnecting {} for sending keepalive response ({}) out-of-order!", this.playerProfile().name(), packet.getId());
- this.disconnectAsync(TIMEOUT_DISCONNECTION_MESSAGE, io.papermc.paper.connection.DisconnectionReason.TIMEOUT);
- return;
- }
- break;
+ LOGGER.info("Disconnecting {} for sending keepalive response ({}) out-of-order!", this.playerProfile().name(), packet.getId());
+ this.disconnectAsync(TIMEOUT_DISCONNECTION_MESSAGE, io.papermc.paper.connection.DisconnectionReason.TIMEOUT);
+ return;
}
}

- if (!this.processedDisconnect) {
- LOGGER.info("Disconnecting {} for sending keepalive response ({}) without matching challenge!", this.playerProfile().name(), packet.getId());
- this.disconnectAsync(TIMEOUT_DISCONNECTION_MESSAGE, io.papermc.paper.connection.DisconnectionReason.TIMEOUT);
- return;
- }
+ LOGGER.info("Disconnecting {} for sending keepalive response ({}) without matching challenge!", this.playerProfile().name(), packet.getId());
+ this.disconnectAsync(TIMEOUT_DISCONNECTION_MESSAGE, io.papermc.paper.connection.DisconnectionReason.TIMEOUT);
// Paper end - improve keepalives
}

@@ -314,11 +307,11 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
// Paper start - improve keepalives
// Gale start - Purpur - send multiple keep-alive packets
if (org.galemc.gale.configuration.GaleGlobalConfiguration.get().misc.keepalive.sendMultiple) {
- if (this.checkIfClosed(millis) && !this.processedDisconnect) {
+ if (this.checkIfClosed(millis)) {
long currTime = System.nanoTime();
if ((currTime - this.keepAlive.lastKeepAliveTx) >= java.util.concurrent.TimeUnit.SECONDS.toNanos(1L)) { // 1 second
this.keepAlive.lastKeepAliveTx = currTime;
- if (this.keepAlivePending && !this.processedDisconnect && keepAlives.size() * 1000L >= KEEPALIVE_LIMIT) {
+ if (this.keepAlivePending && keepAlives.size() * 1000L >= KEEPALIVE_LIMIT) {
LOGGER.info("{} was kicked due to keepalive timeout!", this.playerProfile().name());
this.disconnect(TIMEOUT_DISCONNECTION_MESSAGE, io.papermc.paper.connection.DisconnectionReason.TIMEOUT);
} else if (this.checkIfClosed(millis)) {
@@ -330,7 +323,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
}
} else {
// Gale end - Purpur - send multiple keep-alive packets
- if (this.checkIfClosed(millis) && !this.processedDisconnect) {
+ if (this.checkIfClosed(millis)) {
long currTime = System.nanoTime();

if ((currTime - this.keepAlive.lastKeepAliveTx) >= java.util.concurrent.TimeUnit.SECONDS.toNanos(1L)) {
@@ -400,7 +393,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
}
// Leaf end - Mirai - Configurable chat message signatures
// CraftBukkit start
- if (packet == null || this.processedDisconnect) { // Spigot
+ if (packet == null) {
return;
} else if (packet instanceof net.minecraft.network.protocol.game.ClientboundSetDefaultSpawnPositionPacket defaultSpawnPositionPacket && this instanceof ServerGamePacketListenerImpl serverGamePacketListener) {
serverGamePacketListener.player.compassTarget = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(defaultSpawnPositionPacket.respawnData().pos(), serverGamePacketListener.getPlayer().level());
@@ -435,7 +428,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack

public void disconnect(DisconnectionDetails disconnectionDetails) {
// CraftBukkit start - fire PlayerKickEvent
- if (this.processedDisconnect) {
+ if (!this.connection.isConnected()) {
return;
}
if (!this.cserver.isPrimaryThread()) {
@@ -506,10 +499,8 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
new ClientboundDisconnectPacket(disconnectionDetails.reason()),
PacketSendListener.thenRun(() -> this.connection.disconnect(disconnectionDetails))
);
- this.onDisconnect(disconnectionDetails);
this.connection.setReadOnly();
- // CraftBukkit - Don't wait
- this.server.scheduleOnMain(this.connection::handleDisconnection); // Paper
+ this.connection.handleConnectionDisconnectOnNextTick = true; // Paper - Force kill connection ticking. Let this close the connection
}

// Paper start - add proper async disconnect
diff --git a/net/minecraft/server/network/ServerConnectionListener.java b/net/minecraft/server/network/ServerConnectionListener.java
index 33e1cdb00d648c992af03a976899253141314b27..4aaaea148b16e3ea19691163ba0194c71d1fee11 100644
--- a/net/minecraft/server/network/ServerConnectionListener.java
+++ b/net/minecraft/server/network/ServerConnectionListener.java
@@ -226,6 +226,13 @@ public class ServerConnectionListener {
Connection connection = iterator.next();
if (!connection.isConnecting()) {
if (connection.isConnected()) {
+ // Paper start - Force kill connection ticking
+ // also call this multiple times, doesnt matter
+ // See MC-307764
+ if (connection.handleConnectionDisconnectOnNextTick) {
+ continue;
+ }
+ // Paper end - Force kill connection ticking
try {
connection.tick();
} catch (Exception var7) {
diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
index c0c0244b13c88676e2d8c27b53f52ebc52b03f81..e57a0742289181e717121be4d49210fa85123f0b 100644
--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -360,7 +360,6 @@ public class ServerGamePacketListenerImpl

@Override
public void tick() {
- if (this.isDisconnected()) return; // Paper
if (this.ackBlockChangesUpTo > -1) {
this.send(new ClientboundBlockChangedAckPacket(this.ackBlockChangesUpTo));
this.ackBlockChangesUpTo = -1;
@@ -2319,13 +2318,6 @@ public class ServerGamePacketListenerImpl

@Override
public void onDisconnect(DisconnectionDetails details) {
- // CraftBukkit start - Rarely it would send a disconnect line twice
- if (this.processedDisconnect) {
- return;
- } else {
- this.processedDisconnect = true;
- }
- // CraftBukkit end
LOGGER.info("{} lost connection: {}", this.player.getPlainTextName(), details.reason().getString());
// Paper start - Fix kick event leave message not being sent
final net.kyori.adventure.text.Component quitMessage = details.quitMessage().map(io.papermc.paper.adventure.PaperAdventure::asAdventure).orElse(null);
@@ -3915,7 +3907,7 @@ public class ServerGamePacketListenerImpl
}

public final boolean isDisconnected() {
- return (!this.player.joining && !this.connection.isConnected()) || this.processedDisconnect; // Paper - Fix duplication bugs
+ return (!this.player.joining && !this.connection.isConnected());
}

@Override
diff --git a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
index 875fb1360350c3b182758fa64d9ddecd6db4b3c3..bb4c722000f6e2d5b834e72b844bc79445f6ee1d 100644
--- a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
+++ b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
@@ -71,7 +71,6 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener,
private int velocityLoginMessageId = -1; // Paper - Add Velocity IP Forwarding Support
public java.util.@Nullable UUID requestedUuid; // Paper
private final io.papermc.paper.connection.PaperPlayerLoginConnection paperLoginConnection; // Paper - Config API
- private volatile boolean disconnecting = false; // Paper - Fix disconnect still ticking login

public ServerLoginPacketListenerImpl(MinecraftServer server, Connection connection, boolean transferred) {
this.server = server;
@@ -84,7 +83,6 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener,

@Override
public void tick() {
- if (this.disconnecting) return; // Paper - Fix disconnect being called multiple times due to not awaiting for disconnect
// Paper start - login cookie API
// Don't block the connection
if (this.paperLoginConnection.isAwaitingCookies()) {
@@ -111,7 +109,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener,
int slowLoginTimeout = org.galemc.gale.configuration.GaleGlobalConfiguration.get().misc.premiumAccountSlowLoginTimeout;
if (this.tick++ >= (slowLoginTimeout < 1 ? MAX_TICKS_BEFORE_LOGIN : slowLoginTimeout)) {
// Gale end - make slow login timeout configurable
- this.disconnect(Component.translatable("multiplayer.disconnect.slow_login"));
+ this.disconnectAsync(Component.translatable("multiplayer.disconnect.slow_login")); // Paper
}
}

@@ -127,6 +125,26 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener,
return this.connection.isConnected();
}

+ // Paper start
+ public void disconnectAsync(Component reason) {
+ try {
+ // Gale start - Pufferfish - do not log disconnections with null id
+ if (!org.galemc.gale.configuration.GaleGlobalConfiguration.get().logToConsole.nullIdDisconnections && this.authenticatedProfile != null && this.authenticatedProfile.id() == null) {
+ var reasonString = reason.getString();
+
+ if ("Disconnected".equals(reasonString) || Component.translatable("multiplayer.disconnect.generic").getString().equals(reasonString)) {
+ return;
+ }
+ }
+ // Gale end - Pufferfish - do not log disconnections with null id
+ LOGGER.info("Disconnecting {}: {}", this.getUserName(), reason.getString());
+ this.connection.send(new ClientboundLoginDisconnectPacket(reason), PacketSendListener.thenRun(() -> this.connection.disconnect(reason)));
+ this.connection.handleConnectionDisconnectOnNextTick = true;
+ } catch (Exception var3) {
+ LOGGER.error("Error whilst disconnecting player", (Throwable)var3);
+ }
+ }
+ // Paper end
public void disconnect(Component reason) {
try {
// Gale start - Pufferfish - do not log disconnections with null id
@@ -141,7 +159,6 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener,
LOGGER.info("Disconnecting {}: {}", this.getUserName(), reason.getString());
this.connection.send(new ClientboundLoginDisconnectPacket(reason));
this.connection.disconnect(reason);
- this.disconnecting = true; // Paper - Fix disconnect still ticking login
} catch (Exception var3) {
LOGGER.error("Error whilst disconnecting player", (Throwable)var3);
}
@@ -241,7 +258,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener,
PlayerList playerList = this.server.getPlayerList();
Component component = org.bukkit.craftbukkit.event.CraftEventFactory.handleLoginResult(playerList.canPlayerLogin(this.connection.getRemoteAddress(), new NameAndId(profile)), this.paperLoginConnection, this.connection, profile, this.server, true); // Paper
if (component != null) {
- this.disconnect(component);
+ this.disconnectAsync(component); // Paper
} else {
if (this.server.getCompressionThreshold() >= 0 && !this.connection.isMemoryConnection()) {
this.connection
diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java
index 284940e73feb577c28848fa5cce2c541e3c5d02b..ccf2a7d2fbff4bfc9b6e4606059b4b2863071a08 100644
--- a/net/minecraft/server/players/PlayerList.java
+++ b/net/minecraft/server/players/PlayerList.java
@@ -623,7 +623,6 @@ public abstract class PlayerList {
this.cserver.getPluginManager().callEvent(playerQuitEvent);
player.getBukkitEntity().disconnect();

- if (this.server.isSameThread()) player.doTick(); // SPIGOT-924 // Paper - Improved watchdog support; don't tick during emergency shutdowns
// CraftBukkit end

// Paper start - Configurable player collision; Remove from collideRule team if needed