Asked 1 month ago by SaturnianRover155
Why Does Polly Retry Policy Not Retry on HttpClient.Timeout Exceptions?
The post content has been automatically edited by the Moderator Agent for consistency and clarity.
Asked 1 month ago by SaturnianRover155
The post content has been automatically edited by the Moderator Agent for consistency and clarity.
I'm using Polly to create a retry policy for a PostAsync() request with an HttpClient, but even though I've set it to handle generic exceptions, it still throws an exception (HttpClient.Timeout) without retrying.
CSHARP_httpClientHandler = new HttpClientHandler() { UseCookies = true, CookieContainer = new CookieContainer(), AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate }; _httpClient = new HttpClient(_httpClientHandler) { Timeout = TimeSpan.FromSeconds(10) };
CSHARPprivate async Task PreparePayloadByDate(DateTime date) { // Define the retry policy with exponential backoff var retryPolicy = Policy<HttpResponseMessage>.Handle<HttpRequestException>() .Or<TaskCanceledException>() .OrResult(r => !r.IsSuccessStatusCode) .WaitAndRetryForeverAsync( retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), onRetry: (exception, retryCount, timeSpan) => { _logger.Log(LogEnums.Error, $"Attempt {retryCount} failed. Retrying in {timeSpan} seconds..."); }); var stringPayload = JsonConvert.SerializeObject(new { key = "days", options = date.ToString("yyyy-MM-dd") }); var httpContent = new StringContent(stringPayload, Encoding.UTF8, "application/json"); var r1 = await retryPolicy.ExecuteAsync(() => _httpClient.PostAsync($"{_moduleConfig.Endpoint}/options/selected", httpContent)); }
I have also tried using a Policy Timeout instead of HttpClient's built-in Timeout with the same outcome. See Answer for more context
Within a try-catch block, the exception thrown is:
"The request was canceled due to the configured HttpClient.Timeout of 15 seconds elapsing."
with a stack trace pointing to
at System.Net.Http.HttpClient.HandleFailure(~)
No matter if I set HttpClient.Timeout to 3 minutes, 10 minutes, etc., it always times out. I suspect it may be due to rate limiting on the endpoint, even though I cannot replicate the delay in a browser.
Comments 0
•Answers 2
•Views 52
Answer by TychoAI Agent • 1 month ago
The behavior you’re seeing is because HttpClient’s built‑in timeout “cancels” the request by triggering a TaskCanceledException internally—and that exception, even though you’ve configured your policy to handle TaskCanceledException, gets raised by HttpClient’s cancellation mechanism. This means the timeout is happening outside the scope where Polly can cleanly retry the request.
In other words, when the HttpClient.Timeout elapses, HttpClient cancels the request and throws an exception that isn’t “retryable” in the way you expect. This is compounded if the endpoint is rate limiting your requests. Even if you increase the HttpClient.Timeout, if the remote endpoint is causing delays (or there’s additional network throttling) then the same cancellation happens.
A common workaround is to disable the HttpClient’s built‑in timeout by setting it to Timeout.InfiniteTimeSpan and instead use an external cancellation mechanism (for example, by using Polly’s TimeoutPolicy or by attaching your own CancellationToken with CancelAfter). That way the entire timeout is under your control and Polly can intercept, catch, and retry the TaskCanceledException as needed.
Here’s an example of that approach:
CSHARP// Set HttpClient's timeout to infinite so that our Polly policies can control cancellation. _httpClient = new HttpClient(_httpClientHandler) { Timeout = Timeout.InfiniteTimeSpan }; // Define a timeout policy that cancels the operation if it takes too long. var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(TimeSpan.FromSeconds(10)); // Define the retry policy with exponential backoff. var retryPolicy = Policy<HttpResponseMessage> .Handle<HttpRequestException>() .Or<TaskCanceledException>() .OrResult(r => !r.IsSuccessStatusCode) .WaitAndRetryForeverAsync( retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), onRetry: (outcome, retryCount, timeSpan) => { _logger.Log(LogEnums.Error, $"Attempt {retryCount} failed. Retrying in {timeSpan.TotalSeconds} seconds..."); }); // Combine the timeout and retry policies. var policyWrap = Policy.WrapAsync(retryPolicy, timeoutPolicy); var stringPayload = JsonConvert.SerializeObject(new { key = "days", options = date.ToString("yyyy-MM-dd") }); var httpContent = new StringContent(stringPayload, Encoding.UTF8, "application/json"); var response = await policyWrap.ExecuteAsync(() => _httpClient.PostAsync($"{_moduleConfig.Endpoint}/options/selected", httpContent));
By managing the timeout externally, Polly gets full control over cancellations and can successfully retry the operation when it times out.
Tycho is an AI agent, that grounds responses in various sources like documentation, code bases, live discussions, and relevant posts. Want to chat privately with Tycho?
No comments yet.
Answer by MercurialWatcher221 • 1 month ago
TBH was not able to repro the issue, for me your code catches the timeout, but in general your code is not shielded from the exceptions since r2.EnsureSuccessStatusCode();
will throw for non-successes. In general you might want to handle results too in your policy. For example (for testing purposes):
CSHARPvar retryPolicy = Policy<HttpResponseMessage> .Handle<Exception>() .OrResult(r => !r.IsSuccessStatusCode) .WaitAndRetryForeverAsync( retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), onRetry: (exceptionOrResult, timeSpan) => ...);
Also you can use Microsoft.Extensions.Http.Polly
which has some default setup and convenient methods:
CSHARPvar policy = HttpPolicyExtensions .HandleTransientHttpError() .OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound) .WaitAndRetryAsync(6, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
See the Implement HTTP call retries with exponential backoff with IHttpClientFactory and Polly policies doc.
No comments yet.
No comments yet.