From 242090becb92372f6be1a0b03280ebeac8f08ae4 Mon Sep 17 00:00:00 2001 From: Blank Date: Wed, 5 Aug 2026 21:16:57 +0800 Subject: [PATCH] =?UTF-8?q?fix(connection):=20=E4=BF=AE=E5=A4=8D=20SocketS?= =?UTF-8?q?ender=20=E6=B1=A0=E5=8C=96=E5=A4=8D=E7=94=A8=E5=AF=BC=E8=87=B4?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E5=99=A8=20callBack=20null=20=E5=B4=A9?= =?UTF-8?q?=E6=BA=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Linear: GFX-499 --- .../Sockets/SocketSender.cs | 10 ++++ .../TcpPipeConnection.cs | 50 +++++++++++-------- 2 files changed, 40 insertions(+), 20 deletions(-) diff --git a/src/GameFrameX.SuperSocket.Connection/Sockets/SocketSender.cs b/src/GameFrameX.SuperSocket.Connection/Sockets/SocketSender.cs index 0f3b70a53..4e066483d 100644 --- a/src/GameFrameX.SuperSocket.Connection/Sockets/SocketSender.cs +++ b/src/GameFrameX.SuperSocket.Connection/Sockets/SocketSender.cs @@ -166,8 +166,18 @@ public void OnCompleted(Action continuation, object state, short token, /// Attempts to reset the state of the sender. /// /// true if the state was successfully reset; otherwise, false. + /// + /// invokes this when an instance is returned to the pool. + /// Besides the buffer, the continuation and + /// must also be cleared; otherwise residual state + /// from a previous send makes the next caller's misjudge the operation + /// as completed, leaving the real native send unobserved and corrupting the overlapped state. + /// public bool TryReset() { + _continuation = null; + UserToken = null; + if (BufferList != null) { BufferList = null; diff --git a/src/GameFrameX.SuperSocket.Connection/TcpPipeConnection.cs b/src/GameFrameX.SuperSocket.Connection/TcpPipeConnection.cs index 0efce2cfb..1363317dc 100644 --- a/src/GameFrameX.SuperSocket.Connection/TcpPipeConnection.cs +++ b/src/GameFrameX.SuperSocket.Connection/TcpPipeConnection.cs @@ -14,6 +14,8 @@ public class TcpPipeConnection : PipeConnection private readonly ObjectPool _socketSenderPool; + private SocketSender _socketSender; + /// /// Initializes a new instance of the class with the specified socket, options, and socket sender pool. /// @@ -28,6 +30,12 @@ public TcpPipeConnection(Socket socket, ConnectionOptions options, ObjectPool @@ -35,6 +43,25 @@ public TcpPipeConnection(Socket socket, ConnectionOptions options, ObjectPool protected override void OnClosed() { + var socketSender = _socketSender; + _socketSender = null; + + if (socketSender != null) + { + var pool = _socketSenderPool; + + if (pool != null) + { + // Returning triggers the now-complete TryReset, clearing any residual + // IValueTaskSource state before the next connection reuses the instance. + pool.Return(socketSender); + } + else + { + socketSender.Dispose(); + } + } + _socket = null; base.OnClosed(); } @@ -66,26 +93,9 @@ private async ValueTask ReceiveAsync(Socket socket, Memory memory, So /// The total number of bytes sent. protected override async ValueTask SendOverIOAsync(ReadOnlySequence buffer, CancellationToken cancellationToken) { - var socketSenderPool = _socketSenderPool; - - var socketSender = socketSenderPool?.Get() ?? new SocketSender(); - - try - { - var sentBytes = await socketSender.SendAsync(_socket, buffer).ConfigureAwait(false); - - if (socketSenderPool != null) - { - socketSenderPool.Return(socketSender); - socketSender = null; - } - - return sentBytes; - } - finally - { - socketSender?.Dispose(); - } + // The sender is exclusive to this connection (see constructor). It is no longer + // borrowed/returned per send, so SAEA instances are never shared across connections. + return await _socketSender.SendAsync(_socket, buffer).ConfigureAwait(false); } ///