Search code examples
c#dotnet-httpclientpollyretrypolicydelegatinghandler

Polly "retry" throws TaskCanceledException on first failure retry attempt


I have implemented Polly in it's own "retry" HttpClient DelegateHandler in a dll written to .NET Standard 2.0. I have the Polly v7.2.3 package. My HttpClient is running separate from an HttpClientFactory since only one instance will ever exist during the short lifetime of the dll.

My problem is this: The code executes great when my internet is working. However, when I disconnect my internet it throws a TaskCanceledException on the first retry and does not retry any more. Here are the relevant parts of my code...

inside the ctor of my typed HttpClient:

this.Client = new System.Net.Http.HttpClient(
new ATCacheDelegatingHandler(
    new RetryPolicyDelegatingHandler(
        new HttpClientHandler()))));

inside my Retry delegating handler:

this.RetryPolicy =
Policy.Handle<HttpRequestException>()
    .Or<TaskCanceledException>()
    .WaitAndRetryAsync(numRetries,
        retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt-1) * 15));

So I did my research here on SO and found this very promising explanation and solution that suggested I call Dispose on the result. HttpClient Polly WaitAndRetry policy

Here is my updated code using that solution. The call to WaitAndRetryAsync complains it is unable to resolve the OnRetry method because it is looking for an 'Action<Exception, TimeSpan>'

private void WaitAndRetry(int numRetries)
{
    this.RetryPolicy =
        Policy.Handle<HttpRequestException>()
            .Or<TaskCanceledException>()
            .WaitAndRetryAsync(numRetries,
                retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt-1) * 15)
                , OnRetry); // reference to the method below
}

// unable to match to these parameters from the "WaitAndRetryAsync" call above
private Task OnRetry(DelegateResult<HttpResponseMessage> response, TimeSpan span, int retryCount, Context context)
{
    if (response == null)
        return Task.CompletedTask;

    // this is the "Dispose" call from that SO solution I referenced above
    response.Result?.Dispose();
    return Task.CompletedTask;
}

Unfortunately there is NO support for a DelegateResult<HttpResponseMessage> parameter in the version of Polly I am using. All onRetry support expects the first parameter to be an "Exception". I am dead in the water using the Dispose solution if I can't access the disposable object.

Update: I want to be able to call Dispose() to affect the fix from the other StackOverflow feedback. But I can't because the onRetry method does not support the same set of parameters (i.e., the "response" object). It looks like the Polly API has changed. If so, what is the new way to gain access to the response so I can Dispose of it? Either that or is there another way to resolve the error I am getting?

So I am stuck trying to get this solution working or finding another way to resolve this exception. I welcome any feedback on how to specify the object to Dispose. Alternative approaches are also welcome.


Solution

  • All you need to do is to specify the HttpResponseMessage as a return type when you declare your policy.

    IAsyncPolicy<HttpResponseMessage> retryPolicy = Policy<HttpResponseMessage>
        .Handle<HttpRequestException>()
        .Or<TaskCanceledException>()
        .WaitAndRetryAsync(numRetries,
        retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt - 1) * 15), 
            OnRetry);
    

    So, instead of Policy.Handle... use Policy<HttpResponseMessage>.Handle...