}
// 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)
{
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)
{
_associatedHttp2ConnectionCount -= (_availableHttp2Connections?.Count ?? 0);
_availableHttp2Connections?.Clear();
+ if (_http3Connection is not null)
+ {
+ toDispose.Add(_http3Connection);
+ _http3Connection = null;
+ }
+
if (_authorityExpireTimer != null)
{
_authorityExpireTimer.Dispose();
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))]