HTTP3: Dispose HTTP3 connection when HttpClientHandler is disposed (#56943)
authorGeoff Kizer <geoffrek@microsoft.com>
Fri, 6 Aug 2021 04:43:16 +0000 (21:43 -0700)
committerGitHub <noreply@github.com>
Fri, 6 Aug 2021 04:43:16 +0000 (21:43 -0700)
* dispose Http3Connection when HttpClient is disposed

Co-authored-by: Geoffrey Kizer <geoffrek@windows.microsoft.com>
src/libraries/Common/tests/System/Net/Http/Http3LoopbackConnection.cs
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs
src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http3.cs

index ca53bbe..492aa58 100644 (file)
@@ -195,7 +195,7 @@ namespace System.Net.Test.Common
         }
 
         // Wait for the client to close the connection, e.g. after we send a GOAWAY, or after the HttpClient is disposed.
-        public async Task WaitForClientDisconnectAsync()
+        public async Task WaitForClientDisconnectAsync(bool refuseNewRequests = true)
         {
             while (true)
             {
@@ -204,6 +204,11 @@ namespace System.Net.Test.Common
                 try
                 {
                     stream = await AcceptRequestStreamAsync().ConfigureAwait(false);
+
+                    if (!refuseNewRequests)
+                    {
+                        throw new Exception("Unexpected request stream received while waiting for client disconnect");
+                    }
                 }
                 catch (QuicConnectionAbortedException abortException) when (abortException.ErrorCode == H3_NO_ERROR)
                 {
index 3389946..ef131c8 100644 (file)
@@ -1931,6 +1931,12 @@ namespace System.Net.Http
                     _associatedHttp2ConnectionCount -= (_availableHttp2Connections?.Count ?? 0);
                     _availableHttp2Connections?.Clear();
 
+                    if (_http3Connection is not null)
+                    {
+                        toDispose.Add(_http3Connection);
+                        _http3Connection = null;
+                    }
+
                     if (_authorityExpireTimer != null)
                     {
                         _authorityExpireTimer.Dispose();
index 566d29e..fd18490 100644 (file)
@@ -379,6 +379,40 @@ namespace System.Net.Http.Functional.Tests
             await serverTask;
         }
 
+        [Fact]
+        public async Task DisposeHttpClient_Http3ConnectionIsClosed()
+        {
+            using Http3LoopbackServer server = CreateHttp3LoopbackServer();
+
+            Task serverTask = Task.Run(async () =>
+            {
+                using Http3LoopbackConnection connection = (Http3LoopbackConnection)await server.EstablishGenericConnectionAsync();
+                HttpRequestData request = await connection.ReadRequestDataAsync();
+                await connection.SendResponseAsync();
+
+                await connection.WaitForClientDisconnectAsync(refuseNewRequests: false);
+            });
+
+            Task clientTask = Task.Run(async () =>
+            {
+                using HttpClient client = CreateHttpClient();
+                using HttpRequestMessage request = new()
+                {
+                    Method = HttpMethod.Get,
+                    RequestUri = server.Address,
+                    Version = HttpVersion30,
+                    VersionPolicy = HttpVersionPolicy.RequestVersionExact
+                };
+
+                using HttpResponseMessage response = await client.SendAsync(request);
+                Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+
+                // Return and let the HttpClient be disposed
+            });
+
+            await new[] { clientTask, serverTask }.WhenAllOrAnyFailed(20_000);
+        }
+
         [OuterLoop]
         [ConditionalTheory(nameof(IsMsQuicSupported))]
         [MemberData(nameof(InteropUris))]