From d0c709eb1464f6ca549951c5fe63edc13fa22cf3 Mon Sep 17 00:00:00 2001 From: vendasankarsf3945 Date: Mon, 3 Aug 2026 15:35:06 +0530 Subject: [PATCH 1/2] Improve reliability of HubConnectionTests.LongPollingUsesHttp2ByDefault --- .../FunctionalTests/HubConnectionTests.cs | 96 +++++++++++++------ 1 file changed, 66 insertions(+), 30 deletions(-) diff --git a/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubConnectionTests.cs b/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubConnectionTests.cs index 533bea20e523..d809852d7e4f 100644 --- a/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubConnectionTests.cs +++ b/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubConnectionTests.cs @@ -2361,47 +2361,83 @@ public async Task CanBlockOnAsyncOperationsWithOneAtATimeSynchronizationContext( } [ConditionalFact] - [QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/50180")] public async Task LongPollingUsesHttp2ByDefault() { - await using (var server = await StartServer(configureKestrelServerOptions: o => + // Event-based synchronization for async log messages prevents race condition + // between log emission and resource disposal + var logsSeen = false; + var logsTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + Action handler = null; + handler = context => { - o.ConfigureEndpointDefaults(o2 => + if (logsSeen) { - o2.Protocols = Server.Kestrel.Core.HttpProtocols.Http1AndHttp2; - o2.UseHttps(); - }); - o.ConfigureHttpsDefaults(httpsOptions => + return; + } + + var message = context.Message; + if ((message.Contains("Request starting HTTP/2 POST") && message.Contains("/negotiate?")) || + (message.Contains("Request starting HTTP/2 POST") && message.Contains("?id=")) || + (message.Contains("Request finished HTTP/2 GET") && message.Contains("?id=")) || + (message.Contains("Request finished HTTP/2 DELETE") && message.Contains("?id="))) { - httpsOptions.ServerCertificate = TestCertificateHelper.GetTestCert(); - }); - })) + logsSeen = true; + logsTcs.TrySetResult(); + } + }; + + TestSink.MessageLogged += handler; + + try { - var hubConnection = new HubConnectionBuilder() - .WithLoggerFactory(LoggerFactory) - .WithUrl(server.Url + "/default", HttpTransportType.LongPolling, o => o.HttpMessageHandlerFactory = h => + await using (var server = await StartServer(configureKestrelServerOptions: o => + { + o.ConfigureEndpointDefaults(o2 => { - ((HttpClientHandler)h).ServerCertificateCustomValidationCallback = (_, _, _, _) => true; - return h; - }) - .Build(); - try + o2.Protocols = Server.Kestrel.Core.HttpProtocols.Http1AndHttp2; + o2.UseHttps(); + }); + o.ConfigureHttpsDefaults(httpsOptions => + { + httpsOptions.ServerCertificate = TestCertificateHelper.GetTestCert(); + }); + })) { - await hubConnection.StartAsync().DefaultTimeout(); - var httpProtocol = await hubConnection.InvokeAsync(nameof(TestHub.GetHttpProtocol)).DefaultTimeout(); + var hubConnection = new HubConnectionBuilder() + .WithLoggerFactory(LoggerFactory) + .WithUrl(server.Url + "/default", HttpTransportType.LongPolling, o => o.HttpMessageHandlerFactory = h => + { + ((HttpClientHandler)h).ServerCertificateCustomValidationCallback = (_, _, _, _) => true; + return h; + }) + .Build(); + try + { + await hubConnection.StartAsync().DefaultTimeout(); + var httpProtocol = await hubConnection.InvokeAsync(nameof(TestHub.GetHttpProtocol)).DefaultTimeout(); - Assert.Equal("HTTP/2", httpProtocol); - } - catch (Exception ex) - { - LoggerFactory.CreateLogger().LogError(ex, "{ExceptionType} from test", ex.GetType().FullName); - throw; - } - finally - { - await hubConnection.DisposeAsync().DefaultTimeout(); + Assert.Equal("HTTP/2", httpProtocol); + + // Wait for HTTP/2 logs to flush before server disposal + await logsTcs.Task.DefaultTimeout(); + } + catch (Exception ex) + { + LoggerFactory.CreateLogger().LogError(ex, "{ExceptionType} from test", ex.GetType().FullName); + throw; + } + finally + { + await hubConnection.DisposeAsync().DefaultTimeout(); + } } } + finally + { + // Cleanup: remove event handler to prevent memory leaks + TestSink.MessageLogged -= handler; + } // negotiate is HTTP2 Assert.Contains(TestSink.Writes, context => context.Message.Contains("Request starting HTTP/2 POST") && context.Message.Contains("/negotiate?")); From 723c79fe70d9eaebe450b4d77e850480d0680498 Mon Sep 17 00:00:00 2001 From: vendasankarsf3945 Date: Wed, 5 Aug 2026 11:50:10 +0530 Subject: [PATCH 2/2] Removed the comments --- .../csharp/Client/test/FunctionalTests/HubConnectionTests.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubConnectionTests.cs b/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubConnectionTests.cs index d809852d7e4f..edb2bd5f5385 100644 --- a/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubConnectionTests.cs +++ b/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubConnectionTests.cs @@ -2363,8 +2363,6 @@ public async Task CanBlockOnAsyncOperationsWithOneAtATimeSynchronizationContext( [ConditionalFact] public async Task LongPollingUsesHttp2ByDefault() { - // Event-based synchronization for async log messages prevents race condition - // between log emission and resource disposal var logsSeen = false; var logsTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -2435,7 +2433,6 @@ public async Task LongPollingUsesHttp2ByDefault() } finally { - // Cleanup: remove event handler to prevent memory leaks TestSink.MessageLogged -= handler; }