-
Notifications
You must be signed in to change notification settings - Fork 4.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[HTTP/3] map QuicException
to OCE
in case of a cancellation
#98220
Comments
Tagging subscribers to this area: @dotnet/ncl Issue DetailsDescriptionHello, I am writing a HTTP stress tool using C# and HttpClient, and in some rare cases, a QuicException arises when using HTTP/3. Reproduction StepsServer: Console application to run stress test below. I had to 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 behaviorException shouldn't happen, I guess Actual behaviorError log in server:
Error log in console application:
Regression?No response Known WorkaroundsNo response Configuration
libmsquic version: 2.3.1 Other informationI did not test this on Windows
|
Tagging subscribers to this area: @dotnet/ncl Issue DetailsDescriptionHello, I am writing a HTTP stress tool using C# and HttpClient, and in some rare cases, a QuicException arises when using HTTP/3. Reproduction StepsServer: Console application to run stress test below. I had to 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 behaviorException shouldn't happen, I guess Actual behaviorError log in server:
Error log in console application:
Regression?No response Known WorkaroundsNo response Configuration
libmsquic version: 2.3.1 Other informationI did not test this on Windows
|
Moving to Quic area given the |
The server exception is dotnet/aspnetcore#45105, please upvote it there so that we know this troubles our users. And I assume that the client exception happens when you cancel the token. So the only thing here is the way we bubble up the exception when CT gets cancelled. We should wrap it in OCE instead of HRE. Moving back to HTTP as this is in S.N.Http. |
Tagging subscribers to this area: @dotnet/ncl Issue DetailsDescriptionHello, I am writing a HTTP stress tool using C# and HttpClient, and in some rare cases, a QuicException arises when using HTTP/3. Reproduction StepsServer: Console application to run stress test below. I had to 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 behaviorException shouldn't happen, I guess Actual behaviorError log in server:
Error log in console application:
Regression?No response Known WorkaroundsNo response Configuration
libmsquic version: 2.3.1 Other informationI did not test this on Windows
|
QuicException
to OCE
in case of a cancellation
Based on the recent HTTP stress report: #42211 (comment) we should address the same problem on the response stream level. I.e.: on this stack:
|
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 defaultGET /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)Expected behavior
Exception shouldn't happen, I guess
Actual behavior
Error log in server:
Error log in console application:
Regression?
No response
Known Workarounds
No response
Configuration
libmsquic version: 2.3.1
Other information
I did not test this on Windows
The text was updated successfully, but these errors were encountered: