-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Closed
Labels
area-System.Net.Httpbugin-prThere is an active PR which will close this issue when it is mergedThere is an active PR which will close this issue when it is merged
Milestone
Description
Description
Hello,
I am writing a HTTP stress tool using C# and HttpClient, and in some rare cases, a QuicException arises when using HTTP/3.
Reproduction Steps
Server: dotnet new webapi with HTTP/3 enabled, using default GET /weatherforecast endpoint.
Console application to run stress test below. I had to dotnet run 10 times until the Exception showed up, because it doesn't always happen. (long code warning)
using System.Net;
using System.Threading.Channels;
namespace ExampleParallelHttp3;
public static class Program
{
public static async Task Main(string[] args)
{
Console.WriteLine("Hello, World!");
var hc = MakeHttpClient();
var channelReader = StartRepetition(hc, 25000, 20, MakeConsoleCancellationToken());
int i = 1;
await foreach (var result in channelReader.ReadAllAsync())
{
if (result.Item3 is null)
{
Console.WriteLine($"i = {i++}, status code = {result.Item1}");
}
else
{
Console.WriteLine($"i = {i++}, exception = \n{result.Item3}\n");
break;
}
}
Console.WriteLine("Finished");
}
private static CancellationToken MakeConsoleCancellationToken()
{
// Add this to your C# console app's Main method to give yourself
// a CancellationToken that is canceled when the user hits Ctrl+C.
var cts = new CancellationTokenSource();
Console.CancelKeyPress += (s, e) =>
{
Console.WriteLine("Canceling...");
cts.Cancel();
e.Cancel = true;
};
return cts.Token;
}
private static HttpClient MakeHttpClient()
{
SocketsHttpHandler httpHandler = new()
{
PooledConnectionLifetime = TimeSpan.FromMinutes(20),
AutomaticDecompression = DecompressionMethods.All
};
httpHandler.SslOptions.RemoteCertificateValidationCallback =
(sender, certificate, chain, sslPolicyErrors) => true;
HttpClient httpClient = new(httpHandler, disposeHandler: false)
{
Timeout = TimeSpan.FromMinutes(5)
};
return httpClient;
}
public static ChannelReader<(HttpStatusCode?, string?, Exception?)> StartRepetition(HttpClient hc, int numReps, int maxDop, CancellationToken cancellationToken)
{
var channel = CreateChannel(numReps, maxDop);
Task.Factory.StartNew(async () =>
{
try
{
await ExecuteParallelRequestsAsync(hc, channel.Writer, numReps, maxDop, cancellationToken);
}
catch (TaskCanceledException) { }
catch (OperationCanceledException) { }
catch (Exception ex)
{
(HttpStatusCode?, string?, Exception?) result = (null, null, ex);
await channel.Writer.WriteAsync(result);
}
finally { channel.Writer.Complete(); }
}, TaskCreationOptions.LongRunning);
return channel.Reader;
}
private static async Task ExecuteParallelRequestsAsync(HttpClient hc, ChannelWriter<(HttpStatusCode?, string?, Exception?)> channelWriter, int numReps, int maxDop, CancellationToken cancellationToken)
{
ParallelOptions options = new();
options.MaxDegreeOfParallelism = maxDop;
options.CancellationToken = cancellationToken;
await Parallel.ForAsync(0, numReps, options, async (i, ct) =>
{
try
{
HttpRequestMessage req = new(HttpMethod.Get, "https://localhost:5001/weatherforecast")
{
Version = new(3, 0),
VersionPolicy = HttpVersionPolicy.RequestVersionExact
};
var res = await hc.SendAsync(req, ct);
await channelWriter.WriteAsync((res.StatusCode, await res.Content.ReadAsStringAsync(ct), null), ct);
}
catch (Exception ex)
{
await channelWriter.WriteAsync((null, null, ex), ct);
}
});
}
private static Channel<(HttpStatusCode?, string?, Exception?)> CreateChannel(int numReps, int maxDop)
{
BoundedChannelOptions channelOpts = new(numReps)
{
SingleReader = true,
SingleWriter = maxDop == 1
};
var channel = Channel.CreateBounded<(HttpStatusCode?, string?, Exception?)>(channelOpts);
return channel;
}
}Expected behavior
Exception shouldn't happen, I guess
Actual behavior
Error log in server:
crit: Microsoft.AspNetCore.Server.Kestrel[0]
Unexpected exception in HttpConnection.ProcessRequestsAsync.
System.Net.Quic.QuicException: An internal error has occurred. StreamShutdown failed: QUIC_STATUS_INVALID_PARAMETER
at System.Net.Quic.ThrowHelper.ThrowMsQuicException(Int32 status, String message)
at System.Net.Quic.QuicStream.Abort(QuicAbortDirection abortDirection, Int64 errorCode)
at Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal.QuicStreamContext.Abort(ConnectionAbortedException abortReason)
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3.Http3FrameWriter.Abort(ConnectionAbortedException error)
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3.Http3Stream.AbortCore(Exception exception, Http3ErrorCode errorCode)
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3.Http3Connection.ProcessRequestsAsync[TContext](IHttpApplication`1 application)
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.HttpConnection.ProcessRequestsAsync[TContext](IHttpApplication`1 httpApplication)
Error log in console application:
i = 4, exception =
System.Net.Http.HttpRequestException: An error occurred while sending the request.
---> System.Net.Quic.QuicException: Operation aborted.
at System.Net.Quic.ResettableValueTaskSource.TryComplete(Exception exception, Boolean final)
at System.Net.Quic.QuicStream.HandleEventShutdownComplete(_SHUTDOWN_COMPLETE_e__Struct& data)
at System.Net.Quic.QuicStream.HandleStreamEvent(QUIC_STREAM_EVENT& streamEvent)
at System.Net.Quic.QuicStream.NativeCallback(QUIC_HANDLE* connection, Void* context, QUIC_STREAM_EVENT* streamEvent)
--- End of stack trace from previous location ---
at System.Net.Quic.ResettableValueTaskSource.System.Threading.Tasks.Sources.IValueTaskSource.GetResult(Int16 token)
at System.Net.Http.Http3RequestStream.FlushSendBufferAsync(Boolean endStream, CancellationToken cancellationToken)
at System.Net.Http.Http3RequestStream.SendAsync(CancellationToken cancellationToken)
--- End of inner exception stack trace ---
at System.Net.Http.Http3RequestStream.SendAsync(CancellationToken cancellationToken)
at System.Net.Http.Http3RequestStream.SendAsync(CancellationToken cancellationToken)
at System.Net.Http.Http3Connection.SendAsync(HttpRequestMessage request, Int64 queueStartingTimestamp, CancellationToken cancellationToken)
at System.Net.Http.Http3Connection.SendAsync(HttpRequestMessage request, Int64 queueStartingTimestamp, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.TrySendUsingHttp3Async(HttpRequestMessage request, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.SendWithVersionDetectionAndRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken)
at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
at System.Net.Http.DecompressionHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
at System.Net.Http.HttpClient.<SendAsync>g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)
at ExampleParallelHttp3.Program.<>c__DisplayClass4_0.<<ExecuteParallelRequestsAsync>b__0>d.MoveNext() in /home/alexandre/Projetos/ExampleParallelHttp3/Program.cs:line 99
Regression?
No response
Known Workarounds
No response
Configuration
~> dotnet --info
.NET SDK:
Version: 8.0.101
Commit: 6eceda187b
Workload version: 8.0.100-manifests.69afb982
Ambiente de runtime:
OS Name: debian
OS Version: 12
OS Platform: Linux
RID: linux-x64
Base Path: /usr/share/dotnet/sdk/8.0.101/
Host:
Version: 8.0.1
Architecture: x64
Commit: bf5e279d92
libmsquic version: 2.3.1
Other information
I did not test this on Windows
Metadata
Metadata
Assignees
Labels
area-System.Net.Httpbugin-prThere is an active PR which will close this issue when it is mergedThere is an active PR which will close this issue when it is merged