From 625a4507b5cf80076c01373ad57a5bd42a9e504c Mon Sep 17 00:00:00 2001 From: ManickaP Date: Mon, 16 Feb 2026 16:06:42 +0100 Subject: [PATCH] Fix HttpListener tests blocking xunit workers (#21870) Replace blocking patterns that cause deadlocks when xunit parallel execution is enabled: - Replace .Result with await on task completions - Replace Task.Run(() => GetContext()) with GetContextAsync() - Replace Task.Run(() => Receive()) with ReceiveAsync() - Convert synchronous Socket.Send() to SendAsync() in async methods - Re-enable parallel test execution by removing DisableTestParallelization and MaxParallelThreads = 1 - Fix disposal order in HttpListenerRequestTests to close the client socket before the listener, avoiding a 1s linger timeout per test Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../tests/HttpListenerAuthenticationTests.cs | 19 +++++++++---------- .../tests/HttpListenerContextTests.cs | 2 +- .../tests/HttpListenerRequestTests.cs | 6 +++--- .../tests/HttpListenerResponseTests.cs | 6 +++--- .../tests/HttpRequestStreamTests.cs | 4 ++-- .../tests/HttpResponseStreamTests.cs | 8 ++++---- .../tests/InvalidClientRequestTests.cs | 7 ++++--- .../tests/XUnitAssemblyAttributes.cs | 4 +--- 8 files changed, 27 insertions(+), 29 deletions(-) diff --git a/src/libraries/System.Net.HttpListener/tests/HttpListenerAuthenticationTests.cs b/src/libraries/System.Net.HttpListener/tests/HttpListenerAuthenticationTests.cs index 0e947993237bf5..a7ac34efbd9bd1 100644 --- a/src/libraries/System.Net.HttpListener/tests/HttpListenerAuthenticationTests.cs +++ b/src/libraries/System.Net.HttpListener/tests/HttpListenerAuthenticationTests.cs @@ -6,7 +6,6 @@ using System.Net.Http.Headers; using System.Security.Authentication.ExtendedProtection; using System.Text; -using System.Threading; using System.Threading.Tasks; using Xunit; @@ -405,13 +404,9 @@ public void Realm_SetDisposed_ThrowsObjectDisposedException() public async Task AuthenticationFailure(HttpClient client, HttpStatusCode errorCode) { Task clientTask = client.GetAsync(_factory.ListeningUrl); - - // The server task will hang forever if it is not cancelled. - var tokenSource = new CancellationTokenSource(); - Task serverTask = Task.Run(() => _listener.GetContext(), tokenSource.Token); + Task serverTask = _listener.GetContextAsync(); Task resultTask = await Task.WhenAny(clientTask, serverTask); - tokenSource.Cancel(); if (resultTask == serverTask) { await serverTask; @@ -419,8 +414,10 @@ public async Task AuthenticationFailure(HttpClient client, Assert.Same(clientTask, resultTask); - Assert.Equal(errorCode, clientTask.Result.StatusCode); - return clientTask.Result; + HttpResponseMessage response = await clientTask; + Assert.Equal(errorCode, response.StatusCode); + + return response; } public async Task AuthenticationFailureAsyncContext(HttpClient client, HttpStatusCode errorCode) @@ -436,8 +433,10 @@ public async Task AuthenticationFailureAsyncContext(HttpCli Assert.Same(clientTask, resultTask); - Assert.Equal(errorCode, clientTask.Result.StatusCode); - return clientTask.Result; + HttpResponseMessage response = await clientTask; + Assert.Equal(errorCode, response.StatusCode); + + return response; } private async Task ValidateNullUser() diff --git a/src/libraries/System.Net.HttpListener/tests/HttpListenerContextTests.cs b/src/libraries/System.Net.HttpListener/tests/HttpListenerContextTests.cs index d2ea5caa16c14b..6903b4a112226b 100644 --- a/src/libraries/System.Net.HttpListener/tests/HttpListenerContextTests.cs +++ b/src/libraries/System.Net.HttpListener/tests/HttpListenerContextTests.cs @@ -243,7 +243,7 @@ private async Task GetSocketContext(string[] headers, Func GetRequest(string requestType, string query, string[] headers, string content = "Text\r\n", string httpVersion = "1.1") { - Client.Send(Factory.GetContent(httpVersion, requestType, query, content, headers, true)); + await Client.SendAsync(Factory.GetContent(httpVersion, requestType, query, content, headers, true)); HttpListener listener = Factory.GetListener(); return (await listener.GetContextAsync()).Request; diff --git a/src/libraries/System.Net.HttpListener/tests/HttpListenerResponseTests.cs b/src/libraries/System.Net.HttpListener/tests/HttpListenerResponseTests.cs index e8c731a55abaa8..4ad4099323feaf 100644 --- a/src/libraries/System.Net.HttpListener/tests/HttpListenerResponseTests.cs +++ b/src/libraries/System.Net.HttpListener/tests/HttpListenerResponseTests.cs @@ -50,7 +50,7 @@ protected string GetClientResponse(int expectedLength) protected async Task GetResponse(string httpVersion = "1.1") { - Client.Send(Factory.GetContent(httpVersion, "POST", null, "Give me a context, please", null, headerOnly: false)); + await Client.SendAsync(Factory.GetContent(httpVersion, "POST", null, "Give me a context, please", null, headerOnly: false)); HttpListenerContext context = await Factory.GetListener().GetContextAsync(); return context.Response; } @@ -157,7 +157,7 @@ public async Task Redirect_Disposed_ThrowsObjectDisposedException() [ConditionalFact(nameof(Helpers) + "." + nameof(Helpers.IsWindowsImplementation))] // [ActiveIssue("https://github.com/dotnet/runtime/issues/21808", TestPlatforms.AnyUnix)] public async Task Abort_Invoke_ForciblyTerminatesConnection() { - Client.Send(Factory.GetContent("1.1", "POST", null, "Give me a context, please", null, headerOnly: false)); + await Client.SendAsync(Factory.GetContent("1.1", "POST", null, "Give me a context, please", null, headerOnly: false)); HttpListenerContext context = await Factory.GetListener().GetContextAsync(); HttpListenerResponse response = context.Response; Stream outputStream = response.OutputStream; @@ -415,7 +415,7 @@ public async Task CloseResponseEntity_SendToClosedConnection_DoesNotThrow(bool w using (Socket client = factory.GetConnectedSocket()) { // Send a header to the HttpListener to give it a context. - client.Send(factory.GetContent(RequestTypes.POST, Text, headerOnly: true)); + await client.SendAsync(factory.GetContent(RequestTypes.POST, Text, headerOnly: true)); HttpListener listener = factory.GetListener(); HttpListenerContext context = await listener.GetContextAsync(); diff --git a/src/libraries/System.Net.HttpListener/tests/HttpRequestStreamTests.cs b/src/libraries/System.Net.HttpListener/tests/HttpRequestStreamTests.cs index c01db058874e3b..7e5cfdf878a016 100644 --- a/src/libraries/System.Net.HttpListener/tests/HttpRequestStreamTests.cs +++ b/src/libraries/System.Net.HttpListener/tests/HttpRequestStreamTests.cs @@ -663,7 +663,7 @@ public async Task Read_FromClosedConnectionAsynchronously_ThrowsHttpListenerExce // If the content is missing, then the HttpListener needs // to get the content. However, the socket has been closed // before the reading of the content, so reading should fail. - client.Send(_factory.GetContent(RequestTypes.POST, Text, headerOnly: true)); + await client.SendAsync(_factory.GetContent(RequestTypes.POST, Text, headerOnly: true)); HttpListenerContext context = await _listener.GetContextAsync(); // Disconnect the Socket from the HttpListener. @@ -689,7 +689,7 @@ public async Task Read_FromClosedConnectionSynchronously_ThrowsHttpListenerExcep // If the content is missing, then the HttpListener needs // to get the content. However, the socket has been closed // before the reading of the content, so reading should fail. - client.Send(_factory.GetContent(RequestTypes.POST, Text, headerOnly: true)); + await client.SendAsync(_factory.GetContent(RequestTypes.POST, Text, headerOnly: true)); HttpListenerContext context = await _listener.GetContextAsync(); // Disconnect the Socket from the HttpListener. diff --git a/src/libraries/System.Net.HttpListener/tests/HttpResponseStreamTests.cs b/src/libraries/System.Net.HttpListener/tests/HttpResponseStreamTests.cs index 62fb2be6840843..b269f064e4ff0b 100644 --- a/src/libraries/System.Net.HttpListener/tests/HttpResponseStreamTests.cs +++ b/src/libraries/System.Net.HttpListener/tests/HttpResponseStreamTests.cs @@ -390,7 +390,7 @@ public async Task Write_HeadersToClosedConnectionAsynchronously_ThrowsHttpListen using (Socket client = factory.GetConnectedSocket()) { // Send a header to the HttpListener to give it a context. - client.Send(factory.GetContent(RequestTypes.POST, Text, headerOnly: true)); + await client.SendAsync(factory.GetContent(RequestTypes.POST, Text, headerOnly: true)); HttpListener listener = factory.GetListener(); listener.IgnoreWriteExceptions = ignoreWriteExceptions; HttpListenerContext context = await listener.GetContextAsync(); @@ -428,7 +428,7 @@ public async Task Write_HeadersToClosedConnectionSynchronously_ThrowsHttpListene using (Socket client = factory.GetConnectedSocket()) { // Send a header to the HttpListener to give it a context. - client.Send(factory.GetContent(RequestTypes.POST, Text, headerOnly: true)); + await client.SendAsync(factory.GetContent(RequestTypes.POST, Text, headerOnly: true)); HttpListener listener = factory.GetListener(); listener.IgnoreWriteExceptions = ignoreWriteExceptions; HttpListenerContext context = await listener.GetContextAsync(); @@ -468,7 +468,7 @@ public async Task Write_ContentToClosedConnectionAsynchronously_ThrowsHttpListen using (Socket client = factory.GetConnectedSocket()) { // Send a header to the HttpListener to give it a context. - client.Send(factory.GetContent(RequestTypes.POST, Text, headerOnly: true)); + await client.SendAsync(factory.GetContent(RequestTypes.POST, Text, headerOnly: true)); HttpListener listener = factory.GetListener(); listener.IgnoreWriteExceptions = ignoreWriteExceptions; HttpListenerContext context = await listener.GetContextAsync(); @@ -508,7 +508,7 @@ public async Task Write_ContentToClosedConnectionSynchronously_ThrowsHttpListene using (Socket client = factory.GetConnectedSocket()) { // Send a header to the HttpListener to give it a context. - client.Send(factory.GetContent(RequestTypes.POST, Text, headerOnly: true)); + await client.SendAsync(factory.GetContent(RequestTypes.POST, Text, headerOnly: true)); HttpListener listener = factory.GetListener(); listener.IgnoreWriteExceptions = ignoreWriteExceptions; HttpListenerContext context = await listener.GetContextAsync(); diff --git a/src/libraries/System.Net.HttpListener/tests/InvalidClientRequestTests.cs b/src/libraries/System.Net.HttpListener/tests/InvalidClientRequestTests.cs index 739c5b2711f345..67e7c187225fba 100644 --- a/src/libraries/System.Net.HttpListener/tests/InvalidClientRequestTests.cs +++ b/src/libraries/System.Net.HttpListener/tests/InvalidClientRequestTests.cs @@ -129,16 +129,17 @@ public async Task GetContext_InvalidRequest_DoesNotGetContext() string fullRequest = $"{requestLineWithPathAndQuery}\r\n{host}{string.Join("\r\n", headers ?? new string[0])}{content}\r\n"; Task serverTask = Factory.GetListener().GetContextAsync(); - client.Send(Encoding.Default.GetBytes(fullRequest)); + await client.SendAsync(Encoding.Default.GetBytes(fullRequest)); byte[] errorMessageBytes = new byte[512]; - Task clientTask = Task.Run(() => client.Receive(errorMessageBytes)); + Task clientTask = client.ReceiveAsync(errorMessageBytes); Task completedTask = await Task.WhenAny(clientTask, serverTask); // Ignore the specific error message - just make sure that this failed. Assert.Same(clientTask, completedTask); - string errorMessage = Encoding.Default.GetString(errorMessageBytes, 0, clientTask.Result); + int bytesReceived = await clientTask; + string errorMessage = Encoding.Default.GetString(errorMessageBytes, 0, bytesReceived); Assert.Contains(expectedMessage, errorMessage); Assert.False(serverTask.IsCompleted, $"Server task was completed: {serverTask.Status}"); diff --git a/src/libraries/System.Net.HttpListener/tests/XUnitAssemblyAttributes.cs b/src/libraries/System.Net.HttpListener/tests/XUnitAssemblyAttributes.cs index 31091db97b8784..cc0c22363d45b2 100644 --- a/src/libraries/System.Net.HttpListener/tests/XUnitAssemblyAttributes.cs +++ b/src/libraries/System.Net.HttpListener/tests/XUnitAssemblyAttributes.cs @@ -3,6 +3,4 @@ using Xunit; -// [ActiveIssue("https://github.com/dotnet/runtime/issues/21870")]: Disabling parallel execution of HttpListener tests -// until all of the hangs can be addressed -[assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly, DisableTestParallelization = true, MaxParallelThreads = 1)] +[assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)]