From 1ca56119dc18ec38ff8815c32b2fece4fede1f38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=82ngelo=20Tadeucci?= Date: Tue, 24 Feb 2026 05:27:09 -0300 Subject: [PATCH] Stop send worker after send failure Add a volatile sendFailed flag to Session to record the first SendRaw failure and prevent the send queue from continuing to drain after a failed write. sendFailed is set on write timeout and on IO/Socket/other write exceptions. SendWorker now checks sendFailed (in addition to disposed) and breaks out of its loop when set. This avoids continuing to process/encrypt queued packets after the connection is known-bad and reduces wasted work or potential blocking. --- Maple2.Server.Core/Network/Session.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Maple2.Server.Core/Network/Session.cs b/Maple2.Server.Core/Network/Session.cs index 554a4cc4..e1ea5e78 100644 --- a/Maple2.Server.Core/Network/Session.cs +++ b/Maple2.Server.Core/Network/Session.cs @@ -37,6 +37,7 @@ public abstract class Session : IDisposable { private bool disposed; private int disconnecting; // 0 = not disconnecting, 1 = disconnect in progress/already triggered (reentrancy guard) + private volatile bool sendFailed; // set on first SendRaw failure to stop send queue drain private readonly uint siv; private readonly uint riv; @@ -335,6 +336,7 @@ private void SendRaw(ByteWriter packet) { // Use async write with timeout to prevent indefinite blocking Task writeTask = networkStream.WriteAsync(packet.Buffer, 0, packet.Length); if (!writeTask.Wait(SEND_TIMEOUT_MS)) { + sendFailed = true; Logger.Warning("SendRaw timeout after {Timeout}ms, disconnecting account={AccountId} char={CharacterId}", SEND_TIMEOUT_MS, AccountId, CharacterId); @@ -356,10 +358,12 @@ private void SendRaw(ByteWriter packet) { throw writeTask.Exception?.GetBaseException() ?? new Exception("Write task faulted"); } } catch (Exception ex) when (ex.InnerException is IOException or SocketException || ex is IOException or SocketException) { - // Expected when client closes the connection (e.g., during migration) + // Connection was closed by the client or is no longer valid + sendFailed = true; Logger.Debug("SendRaw connection closed account={AccountId} char={CharacterId}", AccountId, CharacterId); Disconnect(); } catch (Exception ex) { + sendFailed = true; Logger.Warning(ex, "[LIFECYCLE] SendRaw write failed account={AccountId} char={CharacterId}", AccountId, CharacterId); Disconnect(); } @@ -368,7 +372,7 @@ private void SendRaw(ByteWriter packet) { private void SendWorker() { try { foreach ((byte[] packet, int length) in sendQueue.GetConsumingEnumerable()) { - if (disposed) break; + if (disposed || sendFailed) break; // Encrypt outside lock, then send with timeout PoolByteWriter encryptedPacket;