HttpClient.Timeout overrides the TotalRequestTimeout when using AddStandardResilienceHandler
Description
The HttpClient.Timeout interferes with the TotalRequestTimeout when using HttpClient with standard pipeline. If HttpClient.Timeout is less than TotalRequestTimeout, then the TotalRequestTimeout is ignored.
This causes mis-alignment and unexpected behavior for anyone setting the TotalRequestTimeout to a value greater than 100 seconds, which is the default for HttpClient.Timeout.
Calling AddStandardResilienceHandler should disable the HttpClient.Timeout by setting the value to Timeout.InfiniteTimeSpan, because the overall timeout is already enforced by standard pipeline. This also saves some allocations due to HttpClient not needing to create CancellationTokenSource.
Reproduction Steps
Execute this code:
var client = new ServiceCollection()
.AddHttpClient("my-client", c => c.BaseAddress = new Uri("https://jsonplaceholder.typicode.com"))
.AddStandardResilienceHandler()
.Configure(options =>
{
options.AttemptTimeout.Timeout = TimeSpan.FromSeconds(1);
options.TotalRequestTimeout.Timeout = TimeSpan.FromSeconds(150);
options.Retry.ShouldHandle = _ => new ValueTask<bool>(watch.Elapsed < TimeSpan.FromSeconds(100));
options.Retry.MaxRetryAttempts = int.MaxValue;
})
.Services.BuildServiceProvider()
.GetRequiredService<IHttpClientFactory>()
.CreateClient("my-client");
await client.GetStringAsync("/", default);
This should be successful. Instead, the TaskCancelledException is thrown.
Expected behavior
Above example should not throw exception.
Actual behavior
Above example throws TaskCancelledException.
Regression?
No response
Known Workarounds
Manually disable HttpClient timeout:
var client = new ServiceCollection()
.AddHttpClient("my-client", c =>
{
c.BaseAddress = new Uri("https://jsonplaceholder.typicode.com");
c.Timeout = Timeout.InfiniteTimeSpan;
})
.AddStandardResilienceHandler();
Configuration
.NET SDK: Version: 8.0.100 Commit: 57efcf1350 Workload version: 8.0.100-manifests.6a1e483a
Runtime Environment: OS Name: Windows OS Version: 10.0.22621 OS Platform: Windows RID: win-x64 Base Path: C:\Program Files\dotnet\sdk\8.0.100\
Other information
No response
Why is it better to use Polly's timeout policy implementation over built in HttpClient timeout? There is also a second possible solution to just make polly set the HttpClient.Timeout to the right value.
Why is it better to use Polly's timeout policy implementation over built in HttpClient timeout?
Few reasons why this is beneficial:
- When Polly strategy is triggered, the resilience telemetry is reported alongside the timeout.
- TaskCanelledException vs TimeoutRejectedException, the latter gives more details.
- Saving extra allocation of
CancellationTokenSourceby HttpClient: https://source.dot.net/#System.Net.Http/System/Net/Http/HttpClient.cs,805
There is also a second possible solution to just make polly set the HttpClient.Timeout to the right value.
This would lead to race conditions between Polly's timeout and HttpClient's timeout. Sometimes the timeout would be triggered by HttpClient, other times by Polly.
The fix for current bug was reverted because it introduced the following bug #4924. When we fix #4924 we'll get back the fix for the current bug.