-1

I have defined some Typed API Client via Dependency Injection in Start up like below.

public void ConfigureHttpClients(IServiceCollection services)
{
   services.AddHttpClient<IMyHttpClient,MyHttpClient>()    
           .AddPolicyHandler(GetRetryPolicy(Configuration));
}

And Get Retry Policy is defined like this :

private static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy(IConfiguration configuration)
{
    int numberOfretries = Convert.ToInt32(configuration["PollyRetries"]); // Value is set to 3
    TimeSpan delay = TimeSpan.FromSeconds(Convert.ToInt32(configuration["PollyMaxDelaySeconds"])); // Delay is set to 3

    return HttpPolicyExtensions
        .HandleTransientHttpError()
        .WaitAndRetryAsync(
        numberOfretries,
        retryAttempt => delay,
        onRetry: (response, span, retryCount, context) =>
        {
            Log.Information("Attempt # {retryCount}", retryCount);                 
        });
}

Retry Count is set to 3 and Delay is set to 3 seconds.

MyHttpClient is defined like this :

public class MyHttpClient : IMyHttpClient
{
   public MyHttpClient (HttpClient httpClient, IMemoryCache cache)
   {
     _httpClient = httpClient,
     _cache = cache
   }
}

But from the below image you can see even though the max retry is set to 3 the retry count is getting reset after 3 attempts and continues to retry until timeout

WebServerLogs

The Packages I am using related to Polly are -

<PackageReference Include="Microsoft.Extensions.Http.Polly" />
<PackageReference Include="Polly.Extensions.Http" />

Why Polly is retrying even though max limit is reached?
What can I do to resolve this ?

I have tried changing the package versions. But it did not help. Also looked at the Microsoft Documentation for Polly Implementation - implement-http-call-retries-exponential-backoff-polly. The implementation is exactly same as I have mentioned before.

If I wrap my client call with policy execute method like this :

_resiliencyPolicy = HttpPolicyExtensions
    .HandleTransientHttpError()
    .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(3));

await _resiliencyPolicy.ExecuteAsync(async () =>
{
   await _myHttpClient.PostAsync(payload).ConfigureAwait(false);
}).ConfigureAwait(false);  

Then it only retries for 3 times after initial call. But configuring the HttpClient directly in startup.cs with policy, it continues to retry

Peter Csala
  • 17,736
  • 16
  • 35
  • 75
  • You don't need both extension packages. It is enough to refer to the microsoft one. – Peter Csala Jun 20 '23 at 16:32
  • Could you please share with us how do you use the typed client? – Peter Csala Jun 20 '23 at 16:33
  • I am simply calling the post method like this - await _myHttpClient.PostAsync(payload).ConfigureAwait(false); – SoumyaGhosh Jun 20 '23 at 16:34
  • I have removed the Polly.Extensions.Http package. Yes it is enough but it does not still resolve the issue. The issue persists – SoumyaGhosh Jun 20 '23 at 16:44
  • 1
    I could not reproduce it. I've used .NET 7, Polly 7.2.4 and Microsoft.Extensions.Http.Polly 7.0.7. It was working fine, stopped after the 3rd retry. Could you please create a [Minimal, Reproducible Example](https://stackoverflow.com/help/minimal-reproducible-example)? – Peter Csala Jun 20 '23 at 19:22
  • Hi, I have posted the answer as I found the root issue of it. But still have one question around it. Can you please take a look at my answer ? Thanks in advance. – SoumyaGhosh Jun 25 '23 at 03:51

2 Answers2

0

Update : I was able to figure out the root cause of the HttpClient retrying multiple times (exceeding the max retry). Actually in Startup.cs, these httpclient configuration was done inside a wrapper method called ConfigureHttpClient as I mentioned before. But this ConfigureHttpClient method was called two times from two different places in Startup.cs. That applied those policies on the same HttpClient multiple times.

Now not sure about if the policies were applied 2 times on the httpclient as it did not seem so, because the client was retrying until the timeout happens. So exactly not sure what is the consequence if the policies are added multiple times to the same client. Can anybody explain this ? But once I remove the extra call and the ConfigureHttpClient is being called only once, then it only retried for 3 times and it stopped.

Peter Csala
  • 17,736
  • 16
  • 35
  • 75
0

If you look at the logs you can see the following callback logs:

21.27.21 Attempt # 1
21.27.25 Attempt # 2
21.27.28 Attempt # 3
21.27.32 Attempt # 1
21.27.36 Attempt # 1
21.27.39 Attempt # 2
21.27.42 Attempt # 3
21.27.46 Attempt # 2
21.27.49 Attempt # 1
21.27.53 Attempt # 2
21.27.56 Attempt # 3
21.28.00 Attempt # 3
21.28.03 Attempt # 1
21.28.07 Attempt # 2
21.28.10 Attempt # 3

At first glance it does not make sense why do you have two Attempt # 1 subsequent lines, or why do you have an Attempt # 2 right after an Attempt # 3.

After you have confirmed that you have registered the policy twice it all makes sense. You have an inner and outer retry loops. So, by adding some indentation it reveals what happens

  21.27.21 Attempt # 1
  21.27.25 Attempt # 2
  21.27.28 Attempt # 3
21.27.32 Attempt # 1
  21.27.36 Attempt # 1
  21.27.39 Attempt # 2
  21.27.42 Attempt # 3
21.27.46 Attempt # 2
  21.27.49 Attempt # 1
  21.27.53 Attempt # 2
  21.27.56 Attempt # 3
21.28.00 Attempt # 3
  21.28.03 Attempt # 1
  21.28.07 Attempt # 2
  21.28.10 Attempt # 3

So, a retry count 3 means at most 4 attempts. The initial attempt + 3 retries. That's why you see 4 sequences of the inner attempts.

It can break with a timeout since if you add up all attempts and delays it might exceed the default timeout of HttpClient. This could be easily verified if you decrease the retry count to 2 and/or the sleep duration to 2 seconds.

Peter Csala
  • 17,736
  • 16
  • 35
  • 75
  • Thank you for the explanation. Although I still have some doubt. My startup.cs did not contain any outer or inner loop. The stratucture was like this - startup.cs ->root method ConfigureServices(IServiceCollection services) { ConfigureHttpClients(services) -> This is where the clients are registered. AddDependencies(services) -> Inside this method the ConfigureHttpClients were called one more time. } private void AddDependencies(IServiceCollection services) { ConfigureHttpClient(services) } – SoumyaGhosh Jun 25 '23 at 14:08
  • Are you saying once I register the client and attach the policy, then next time when I re register the client and attach again, it then finds the existing registration and attach the policies again. I checked the logs it was about 15 times for the retries. Shouldn't have retried for 12 times then ? – SoumyaGhosh Jun 25 '23 at 14:14
  • @SoumyaGhosh Since you haven't shared with us how does your Startup look like that's why I can't state anything for sure about your setup. The AddPolicyHandler registers a messagehandler under the hood. By calling it multiple times you are chaining them. If the policies are different then the ordering matters. – Peter Csala Jun 25 '23 at 14:45