From 02cf349075d49509ec0d06951bfe0f90d07ad947 Mon Sep 17 00:00:00 2001 From: Adrian <68704415+4drian3d@users.noreply.github.com> Date: Sun, 19 Oct 2025 09:43:40 -0500 Subject: [PATCH] Fixed disconnecting players in the middle of a backend server reconfiguration (#1669) --- .../backend/ConfigSessionHandler.java | 8 +++++- .../backend/LoginSessionHandler.java | 2 +- .../connection/client/ConnectedPlayer.java | 27 +++++++++---------- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ConfigSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ConfigSessionHandler.java index 90bb1a30..8ab38e58 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ConfigSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ConfigSessionHandler.java @@ -257,7 +257,13 @@ public class ConfigSessionHandler implements MinecraftSessionHandler { @Override public boolean handle(DisconnectPacket packet) { serverConn.disconnect(); - resultFuture.complete(ConnectionRequestResults.forDisconnect(packet, serverConn.getServer())); + // If the player receives a DisconnectPacket without a connection to a server in progress, + // it means that the backend server has kicked the player during reconfiguration + if (serverConn.getPlayer().getConnectionInFlight() != null) { + resultFuture.complete(ConnectionRequestResults.forDisconnect(packet, serverConn.getServer())); + } else { + serverConn.getPlayer().handleConnectionException(serverConn.getServer(), packet, true); + } return true; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java index 612e9c25..14884af4 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java @@ -165,7 +165,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler { } if (player.getConnection().getActiveSessionHandler() instanceof ClientPlaySessionHandler clientPlaySessionHandler) { smc.setAutoReading(false); - clientPlaySessionHandler.doSwitch().thenAcceptAsync((unused) -> smc.setAutoReading(true), smc.eventLoop()); + clientPlaySessionHandler.doSwitch().thenRunAsync(() -> smc.setAutoReading(true), smc.eventLoop()); } else { // Initial login - the player is already in configuration state. server.getEventManager().fireAndForget(new PlayerEnteredConfigurationEvent(player, serverConn)); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java index 17f4fb5c..0807789f 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java @@ -817,9 +817,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, createConnectionRequest(res.getServer(), previousConnection).connect() .whenCompleteAsync((status, throwable) -> { if (throwable != null) { - handleConnectionException( - status != null ? status.getAttemptedConnection() : res.getServer(), throwable, - true); + handleConnectionException(res.getServer(), throwable, true); return; } @@ -1497,7 +1495,16 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, VelocityServerConnection con = new VelocityServerConnection(vrs, previousServer, ConnectedPlayer.this, server); connectionInFlight = con; - return con.connect().whenCompleteAsync((result, exception) -> this.resetIfInFlightIs(con), + + return con.connect().whenCompleteAsync((result, exception) -> { + if (result != null && !result.isSuccessful() && !result.isSafe()) { + handleConnectionException(result.getAttemptedConnection(), + // The only way for the reason to be null is if the result is safe + DisconnectPacket.create(result.getReasonComponent().orElseThrow(), + getProtocolVersion(), connection.getState()), false); + } + this.resetIfInFlightIs(con); + }, connection.eventLoop()); }, connection.eventLoop()); }); @@ -1511,22 +1518,14 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, @Override public CompletableFuture connect() { - return this.internalConnect().whenCompleteAsync((status, throwable) -> { - if (status != null && !status.isSuccessful()) { - if (!status.isSafe()) { - handleConnectionException(status.getAttemptedConnection(), throwable, false); - } - } - }, connection.eventLoop()).thenApply(x -> x); + return this.internalConnect().thenApply(x -> x); } @Override public CompletableFuture connectWithIndication() { return internalConnect().whenCompleteAsync((status, throwable) -> { if (throwable != null) { - // TODO: The exception handling from this is not very good. Find a better way. - handleConnectionException(status != null ? status.getAttemptedConnection() : toConnect, - throwable, true); + handleConnectionException(toConnect, throwable, true); return; }