From ecd72053fbe65d8c9034da82c023af44d2ca5f94 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Sun, 18 Jan 2026 18:00:10 +0100 Subject: [PATCH 1/6] close socket async --- .../System/Net/Http/GenericLoopbackServer.cs | 21 +++++++------------ .../Net/Http/Http2LoopbackConnection.cs | 8 +++---- .../HttpClientHandlerTest.AutoRedirect.cs | 4 ++-- .../tests/System/Net/Http/LoopbackServer.cs | 4 ++-- .../HttpClientHandlerTest.Http2.cs | 4 ++-- .../HttpClientHandlerTest.RequestRetry.cs | 2 +- .../HttpClientHandlerTest.ResponseDrain.cs | 2 +- .../HttpClientMiniStressTest.cs | 2 +- .../tests/FunctionalTests/MetricsTest.cs | 2 +- .../FunctionalTests/SocketsHttpHandlerTest.cs | 6 +++--- .../BrowserWebSockets/BrowserInterop.cs | 6 +++--- .../WebSocketHandshakeHelper.cs | 2 +- .../tests/SendReceiveTest.Loopback.cs | 4 ++-- 13 files changed, 30 insertions(+), 37 deletions(-) diff --git a/src/libraries/Common/tests/System/Net/Http/GenericLoopbackServer.cs b/src/libraries/Common/tests/System/Net/Http/GenericLoopbackServer.cs index f92e651ec0a675..d9bedd6a691a1b 100644 --- a/src/libraries/Common/tests/System/Net/Http/GenericLoopbackServer.cs +++ b/src/libraries/Common/tests/System/Net/Http/GenericLoopbackServer.cs @@ -88,10 +88,11 @@ public void Dispose() _socket?.Dispose(); _websocket?.Dispose(); } - public void Close() + + public async Task CloseAsync() { _socket?.Close(); - CloseWebSocket(); + await CloseWebSocketAsync(); } public EndPoint? LocalEndPoint => _socket?.LocalEndPoint; @@ -108,28 +109,20 @@ public async Task WaitForCloseAsync(CancellationToken cancellationToken) } } - public void Shutdown(SocketShutdown how) + public async Task ShutdownAsync(SocketShutdown how) { _socket?.Shutdown(how); - CloseWebSocket(); + await CloseWebSocketAsync(); } - private void CloseWebSocket() + private async Task CloseWebSocketAsync() { if (_websocket == null) return; var state = _websocket.State; if (state != WebSocketState.Open && state != WebSocketState.Connecting && state != WebSocketState.CloseSent) return; - try - { - var task = _websocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "closing remoteLoop", CancellationToken.None); - // Block and wait for the task to complete synchronously - Task.WaitAll(task); - } - catch (Exception) - { - } + await _websocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "closing remoteLoop", CancellationToken.None); } } diff --git a/src/libraries/Common/tests/System/Net/Http/Http2LoopbackConnection.cs b/src/libraries/Common/tests/System/Net/Http/Http2LoopbackConnection.cs index 58bc1a8ccc5141..ed06cff5373b15 100644 --- a/src/libraries/Common/tests/System/Net/Http/Http2LoopbackConnection.cs +++ b/src/libraries/Common/tests/System/Net/Http/Http2LoopbackConnection.cs @@ -155,7 +155,7 @@ private async Task ReadPrefixAsync() // so that SocketsHttpHandler will not induce retry. // The contents of what we send don't really matter, as long as it is interpreted by SocketsHttpHandler as an invalid response. await _connectionStream.WriteAsync("HTTP/2.0 400 Bad Request\r\n\r\n"u8.ToArray()); - _connectionSocket.Shutdown(SocketShutdown.Send); + await _connectionSocket.ShutdownAsync(SocketShutdown.Send); // If WinHTTP doesn't support streaming a request without a length then it will fallback // to HTTP/1.1. Throwing an exception to detect this case in WinHttpHandler tests. throw new Exception("HTTP/1.1 request sent to HTTP/2 connection."); @@ -397,9 +397,9 @@ public async Task WaitForClientDisconnectAsync(bool ignoreUnexpectedFrames = fal _ignoreWindowUpdates = false; } - public void ShutdownSend() + public async Task ShutdownSendAsync() { - _connectionSocket?.Shutdown(SocketShutdown.Send); + await _connectionSocket?.ShutdownAsync(SocketShutdown.Send); } // This will cause a server-initiated shutdown of the connection. @@ -408,7 +408,7 @@ public void ShutdownSend() public async Task WaitForConnectionShutdownAsync(bool ignoreUnexpectedFrames = false) { // Shutdown our send side, so the client knows there won't be any more frames coming. - ShutdownSend(); + await ShutdownSendAsync(); await WaitForClientDisconnectAsync(ignoreUnexpectedFrames: ignoreUnexpectedFrames); } diff --git a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.AutoRedirect.cs b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.AutoRedirect.cs index 8161d1a8198659..c45d954d92b97d 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.AutoRedirect.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.AutoRedirect.cs @@ -108,7 +108,7 @@ await LoopbackServer.CreateServerAsync(async (redirServer, redirUrl) => // Send Connection: close so the client will close connection after request is sent, // meaning we can just read to the end to get the content await connection.ReadRequestHeaderAndSendResponseAsync((HttpStatusCode)statusCode, $"Location: {redirUrl}\r\nConnection: close\r\n"); - connection.Socket.Shutdown(SocketShutdown.Send); + await connection.Socket.ShutdownAsync(SocketShutdown.Send); await connection.ReadToEndAsync(); }); @@ -124,7 +124,7 @@ await LoopbackServer.CreateServerAsync(async (redirServer, redirUrl) => // Send Connection: close so the client will close connection after request is sent, // meaning we can just read to the end to get the content receivedRequest = await connection.ReadRequestHeaderAndSendResponseAsync(additionalHeaders: "Connection: close\r\n"); - connection.Socket.Shutdown(SocketShutdown.Send); + await connection.Socket.ShutdownAsync(SocketShutdown.Send); receivedContent = await connection.ReadToEndAsync(); }); diff --git a/src/libraries/Common/tests/System/Net/Http/LoopbackServer.cs b/src/libraries/Common/tests/System/Net/Http/LoopbackServer.cs index 0ac084afc4e3e2..1d9fa08456b2dc 100644 --- a/src/libraries/Common/tests/System/Net/Http/LoopbackServer.cs +++ b/src/libraries/Common/tests/System/Net/Http/LoopbackServer.cs @@ -155,7 +155,7 @@ public async Task EstablishConnectionAsync() } catch (Exception) { - closableWrapper?.Close(); + await closableWrapper?.CloseAsync(); throw; } } @@ -679,7 +679,7 @@ public override async ValueTask DisposeAsync() // This seems to help avoid connection reset issues caused by buffered data // that has not been sent/acked when the graceful shutdown timeout expires. // This may throw if the socket was already closed, so eat any exception. - _socket?.Shutdown(SocketShutdown.Send); + await _socket?.ShutdownAsync(SocketShutdown.Send); } catch (Exception) { } diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs index e96eb1fe25dc25..416cd342435a69 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs @@ -1077,7 +1077,7 @@ await Http2LoopbackServer.CreateClientAndServerAsync(async uri => // Server sends GOAWAY frame await connection.SendGoAway(streamId, ProtocolErrors.ENHANCE_YOUR_CALM); - connection.ShutdownSend(); + await connection.ShutdownSendAsync(); }); } @@ -1487,7 +1487,7 @@ public async Task GoAwayFrame_AllPendingStreamsValid_RequestsSucceedAndConnectio await connection.SendResponseDataAsync(streamId3, new byte[5], endStream: true); // We will not send any more frames, so send EOF now, and ensure the client handles this properly. - connection.ShutdownSend(); + await connection.ShutdownSendAsync(); // Receive all responses HttpResponseMessage response1 = await sendTask1; diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.RequestRetry.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.RequestRetry.cs index 8198fb8818a08e..f2ddaec419770b 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.RequestRetry.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.RequestRetry.cs @@ -142,7 +142,7 @@ await LoopbackServer.CreateClientAndServerAsync(async url => await server.AcceptConnectionAsync(async connection => { // Shut down the listen socket so no additional connections can happen - server.ListenSocket.Close(); + await server.ListenSocket.CloseAsync(); // Initial response await connection.ReadRequestHeaderAndSendResponseAsync(content: SimpleContent); diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.ResponseDrain.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.ResponseDrain.cs index 6c174266884e68..c28adb9dbe9f28 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.ResponseDrain.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.ResponseDrain.cs @@ -55,7 +55,7 @@ await LoopbackServer.CreateClientAndServerAsync( { await server.AcceptConnectionAsync(async connection => { - server.ListenSocket.Close(); // Shut down the listen socket so attempts at additional connections would fail on the client + await server.ListenSocket.CloseAsync(); // Shut down the listen socket so attempts at additional connections would fail on the client string response = LoopbackServer.GetContentModeResponse(mode, simpleContent); await connection.ReadRequestHeaderAndSendCustomResponseAsync(response); diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientMiniStressTest.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientMiniStressTest.cs index b739faf0dd72d8..e19c321dfd0a55 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientMiniStressTest.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientMiniStressTest.cs @@ -79,7 +79,7 @@ await server.AcceptConnectionAsync(async connection => Assert.Equal(numBytes, await connection.ReadBlockAsync(postData, 0, numBytes)); await connection.WriteStringAsync(responseText).ConfigureAwait(false); - connection.Socket.Shutdown(SocketShutdown.Send); + await connection.Socket.ShutdownAsync(SocketShutdown.Send); }); (await postAsync.ConfigureAwait(false)).Dispose(); diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/MetricsTest.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/MetricsTest.cs index bbe6c0bfbf9fb9..a70230c3ec9808 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/MetricsTest.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/MetricsTest.cs @@ -1116,7 +1116,7 @@ await LoopbackServer.CreateClientAndServerAsync(async uri => await IgnoreExceptions(async () => { LoopbackServer.Connection connection = await server.EstablishConnectionAsync().WaitAsync(cancelServerCts.Token); - connection.Socket.Shutdown(SocketShutdown.Send); + await connection.Socket.ShutdownAsync(SocketShutdown.Send); }); }); } diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs index d2f89a212f4ed0..b218cca0ee8dcb 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs @@ -592,7 +592,7 @@ await LoopbackServer.CreateClientAndServerAsync( string response = LoopbackServer.GetContentModeResponse(mode, content); await server.AcceptConnectionAsync(async connection => { - server.ListenSocket.Close(); // Shut down the listen socket so attempts at additional connections would fail on the client + await server.ListenSocket.CloseAsync(); // Shut down the listen socket so attempts at additional connections would fail on the client await connection.ReadRequestHeaderAndSendCustomResponseAsync(response); await connection.ReadRequestHeaderAndSendCustomResponseAsync(response); }); @@ -1941,7 +1941,7 @@ await server.AcceptConnectionAsync(async (LoopbackServer.Connection connection) string bigString = string.Concat(Enumerable.Repeat("abcdefghijklmnopqrstuvwxyz", 1000)); Task lotsOfDataSent = connection.SendResponseAsync(Encoding.ASCII.GetBytes(bigString)); - connection.Socket.Shutdown(SocketShutdown.Send); + await connection.Socket.ShutdownAsync(SocketShutdown.Send); await copyTask; await lotsOfDataSent; Assert.Equal("ghijklmnopqrstuvwxyz" + bigString, Encoding.ASCII.GetString(ms.ToArray())); @@ -2165,7 +2165,7 @@ await Http2LoopbackServerFactory.CreateServerAsync(async (server, url) => await request2; // Close underlying socket from first connection. - socket.Close(); + await socket.CloseAsync(); } }); } diff --git a/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/BrowserWebSockets/BrowserInterop.cs b/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/BrowserWebSockets/BrowserInterop.cs index a131a76aeb1fff..1c7a5716b82aa0 100644 --- a/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/BrowserWebSockets/BrowserInterop.cs +++ b/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/BrowserWebSockets/BrowserInterop.cs @@ -26,12 +26,12 @@ internal static partial class BrowserInterop { return null; } - if (!webSocket.HasProperty("close_status")) + if (!webSocket.HasProperty("closeStatus")) { return null; } - int status = webSocket.GetPropertyAsInt32("close_status"); + int status = webSocket.GetPropertyAsInt32("closeStatus"); return (WebSocketCloseStatus)status; } @@ -42,7 +42,7 @@ internal static partial class BrowserInterop return null; } - string? description = webSocket.GetPropertyAsString("close_status_description"); + string? description = webSocket.GetPropertyAsString("closeStatusDescription"); return description; } diff --git a/src/libraries/System.Net.WebSockets.Client/tests/LoopbackServer/WebSocketHandshakeHelper.cs b/src/libraries/System.Net.WebSockets.Client/tests/LoopbackServer/WebSocketHandshakeHelper.cs index e97d2feaf6f3b3..07e599949fbe0a 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/LoopbackServer/WebSocketHandshakeHelper.cs +++ b/src/libraries/System.Net.WebSockets.Client/tests/LoopbackServer/WebSocketHandshakeHelper.cs @@ -193,7 +193,7 @@ public static async Task SendHttp11ServerResponseAndEosAsync(WebSocketRequestDat } // send server EOS (half-closing from server side) - requestData.Http11Connection!.Socket.Shutdown(SocketShutdown.Send); + await requestData.Http11Connection!.Socket.ShutdownAsync(SocketShutdown.Send); } public static async Task SendHttp2ServerResponseAndEosAsync(WebSocketRequestData requestData, bool eosInHeadersFrame, Func? requestDataCallback, CancellationToken cancellationToken) diff --git a/src/libraries/System.Net.WebSockets.Client/tests/SendReceiveTest.Loopback.cs b/src/libraries/System.Net.WebSockets.Client/tests/SendReceiveTest.Loopback.cs index f54a4202d099d1..c786cce6160e01 100644 --- a/src/libraries/System.Net.WebSockets.Client/tests/SendReceiveTest.Loopback.cs +++ b/src/libraries/System.Net.WebSockets.Client/tests/SendReceiveTest.Loopback.cs @@ -83,8 +83,8 @@ private async Task RunClient_SendReceive_ConnectionClosedPrematurely_ReceiveAsyn await pendingReceiveAsyncPosted.Task.WaitAsync(TimeSpan.FromMilliseconds(TimeOutMilliseconds)); // Close the underlying connection prematurely (without sending a WebSocket Close frame). - connection.Socket.Shutdown(SocketShutdown.Both); - connection.Socket.Close(); + await connection.Socket.ShutdownAsync(SocketShutdown.Both); + await connection.Socket.CloseAsync(); }); // Initiate a connection attempt. From 9f8b1c2e3125804632cd9dbb071c811a27edc4f6 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Sun, 18 Jan 2026 18:28:35 +0100 Subject: [PATCH 2/6] fix --- .../Net/WebSockets/BrowserWebSockets/BrowserInterop.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/BrowserWebSockets/BrowserInterop.cs b/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/BrowserWebSockets/BrowserInterop.cs index 1c7a5716b82aa0..a131a76aeb1fff 100644 --- a/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/BrowserWebSockets/BrowserInterop.cs +++ b/src/libraries/System.Net.WebSockets.Client/src/System/Net/WebSockets/BrowserWebSockets/BrowserInterop.cs @@ -26,12 +26,12 @@ internal static partial class BrowserInterop { return null; } - if (!webSocket.HasProperty("closeStatus")) + if (!webSocket.HasProperty("close_status")) { return null; } - int status = webSocket.GetPropertyAsInt32("closeStatus"); + int status = webSocket.GetPropertyAsInt32("close_status"); return (WebSocketCloseStatus)status; } @@ -42,7 +42,7 @@ internal static partial class BrowserInterop return null; } - string? description = webSocket.GetPropertyAsString("closeStatusDescription"); + string? description = webSocket.GetPropertyAsString("close_status_description"); return description; } From a4d4a91d58a450ec9d145d5e16f90e72d1e2a1c3 Mon Sep 17 00:00:00 2001 From: pavelsavara Date: Mon, 19 Jan 2026 11:16:24 +0100 Subject: [PATCH 3/6] fix --- .../Common/tests/System/Net/Http/Http2LoopbackConnection.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libraries/Common/tests/System/Net/Http/Http2LoopbackConnection.cs b/src/libraries/Common/tests/System/Net/Http/Http2LoopbackConnection.cs index ed06cff5373b15..8b018da6d55b72 100644 --- a/src/libraries/Common/tests/System/Net/Http/Http2LoopbackConnection.cs +++ b/src/libraries/Common/tests/System/Net/Http/Http2LoopbackConnection.cs @@ -399,7 +399,10 @@ public async Task WaitForClientDisconnectAsync(bool ignoreUnexpectedFrames = fal public async Task ShutdownSendAsync() { - await _connectionSocket?.ShutdownAsync(SocketShutdown.Send); + if (_connectionSocket != null) + { + await _connectionSocket.ShutdownAsync(SocketShutdown.Send); + } } // This will cause a server-initiated shutdown of the connection. From 66999cd71caba6b683a4578989a4e0f826f4ceda Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Tue, 20 Jan 2026 20:07:01 +0100 Subject: [PATCH 4/6] Update src/libraries/Common/tests/System/Net/Http/LoopbackServer.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/libraries/Common/tests/System/Net/Http/LoopbackServer.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libraries/Common/tests/System/Net/Http/LoopbackServer.cs b/src/libraries/Common/tests/System/Net/Http/LoopbackServer.cs index 1d9fa08456b2dc..e0dbd180cddfb1 100644 --- a/src/libraries/Common/tests/System/Net/Http/LoopbackServer.cs +++ b/src/libraries/Common/tests/System/Net/Http/LoopbackServer.cs @@ -155,7 +155,10 @@ public async Task EstablishConnectionAsync() } catch (Exception) { - await closableWrapper?.CloseAsync(); + if (closableWrapper is not null) + { + await closableWrapper.CloseAsync().ConfigureAwait(false); + } throw; } } From 158318397fecd8bf90ac104b7766c2f8cb86f41d Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Tue, 20 Jan 2026 20:07:17 +0100 Subject: [PATCH 5/6] Update src/libraries/Common/tests/System/Net/Http/LoopbackServer.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/libraries/Common/tests/System/Net/Http/LoopbackServer.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libraries/Common/tests/System/Net/Http/LoopbackServer.cs b/src/libraries/Common/tests/System/Net/Http/LoopbackServer.cs index e0dbd180cddfb1..ac80fe227c4579 100644 --- a/src/libraries/Common/tests/System/Net/Http/LoopbackServer.cs +++ b/src/libraries/Common/tests/System/Net/Http/LoopbackServer.cs @@ -682,7 +682,10 @@ public override async ValueTask DisposeAsync() // This seems to help avoid connection reset issues caused by buffered data // that has not been sent/acked when the graceful shutdown timeout expires. // This may throw if the socket was already closed, so eat any exception. - await _socket?.ShutdownAsync(SocketShutdown.Send); + if (_socket is not null) + { + await _socket.ShutdownAsync(SocketShutdown.Send).ConfigureAwait(false); + } } catch (Exception) { } From 135b5878ede0938caefaa48ab08e1929618ea96d Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Tue, 20 Jan 2026 20:07:34 +0100 Subject: [PATCH 6/6] Update src/libraries/Common/tests/System/Net/Http/GenericLoopbackServer.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../tests/System/Net/Http/GenericLoopbackServer.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/libraries/Common/tests/System/Net/Http/GenericLoopbackServer.cs b/src/libraries/Common/tests/System/Net/Http/GenericLoopbackServer.cs index d9bedd6a691a1b..1401642867d349 100644 --- a/src/libraries/Common/tests/System/Net/Http/GenericLoopbackServer.cs +++ b/src/libraries/Common/tests/System/Net/Http/GenericLoopbackServer.cs @@ -122,7 +122,14 @@ private async Task CloseWebSocketAsync() var state = _websocket.State; if (state != WebSocketState.Open && state != WebSocketState.Connecting && state != WebSocketState.CloseSent) return; - await _websocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "closing remoteLoop", CancellationToken.None); + try + { + await _websocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "closing remoteLoop", CancellationToken.None); + } + catch (Exception) + { + // Ignore exceptions during WebSocket close in test cleanup. + } } }