diff --git a/common/src/main/java/xyz/jonesdev/sonar/common/protocol/SonarPacketDecoder.java b/common/src/main/java/xyz/jonesdev/sonar/common/protocol/SonarPacketDecoder.java index 4e4d6b31..6c31d433 100644 --- a/common/src/main/java/xyz/jonesdev/sonar/common/protocol/SonarPacketDecoder.java +++ b/common/src/main/java/xyz/jonesdev/sonar/common/protocol/SonarPacketDecoder.java @@ -87,7 +87,15 @@ public void channelRead(final @NotNull ChannelHandlerContext ctx, final Object m // Let our verification handler process the packet if (listener != null) { - listener.handle(packet); + try { + listener.handle(packet); + } catch (QuietDecoderException expected) { + // fail() already called disconnect via closeWith() and scheduled + // a delayed close. Don't let the exception propagate to + // TailExceptionsHandler — it would close the channel immediately, + // bypassing the delay and potentially discarding the disconnect packet. + return; + } // Make sure to let the timeout handler know about this packet ctx.fireChannelRead(packet); } diff --git a/common/src/main/java/xyz/jonesdev/sonar/common/util/ProtocolUtil.java b/common/src/main/java/xyz/jonesdev/sonar/common/util/ProtocolUtil.java index eca3824d..acc6240a 100644 --- a/common/src/main/java/xyz/jonesdev/sonar/common/util/ProtocolUtil.java +++ b/common/src/main/java/xyz/jonesdev/sonar/common/util/ProtocolUtil.java @@ -220,16 +220,25 @@ public static void checkNettyVersion() { public static void closeWith(final @NotNull Channel channel, final @NotNull ProtocolVersion protocolVersion, final @NotNull Object msg) { - if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_8)) { - channel.eventLoop().execute(() -> { - channel.config().setAutoRead(false); - channel.eventLoop().schedule(() -> { - channel.writeAndFlush(msg).addListener(ChannelFutureListener.CLOSE); - }, 250L, TimeUnit.MILLISECONDS); - }); - } else { - channel.writeAndFlush(msg).addListener(ChannelFutureListener.CLOSE); - } + // Prevent reading any more packets from the client + channel.config().setAutoRead(false); + + final long delay = protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_8) ? 250L : 50L; + + // Vanilla Minecraft adds a small delay between writing the + // disconnect/transfer packet and closing the connection. + // Without this, the TCP close can arrive at the client before + // the packet is processed, causing "Connection Reset". + channel.writeAndFlush(msg).addListener((ChannelFutureListener) future -> { + final Channel ch = future.channel(); + if (ch.isActive()) { + ch.eventLoop().schedule(() -> { + if (ch.isActive()) { + ch.close(); + } + }, delay, TimeUnit.MILLISECONDS); + } + }); } public static @NotNull UUID readUUID(final @NotNull ByteBuf byteBuf) {