How can I get the retry count within a delegate executed through Polly retry policy?
Asked Answered
B

1

15

I'm implementing Polly to retry requests in my C# web app. My sample code is included in this post. The code works as expected but the last parameter passed to CreateFile() (currently hard-coded as 0) needs to be the value of retryAttempt. How can I get the value of retryAttempt within the Execute's Action?

return Policy
    .Handle<HttpException>(x => x.StatusCode == (HttpStatusCode)429)
    .Or<StorageException>()
    .WaitAndRetry(maxRetryCount, retryAttempt => TimeSpan.FromMilliseconds(Math.Pow(retryIntervalMs, retryAttempt)))
    .Execute(() => CreateFile(fileContent, containerName, fileName, connectionString, 0));
Bryson answered 20/12, 2018 at 23:49 Comment(1)
Note also that Polly has some specific retry overloads helpful for 429 responses. if the 429 response specifies how long to wait. The retry policy can be made to wait for exactly the retry-after duration specified in the 429 response.Crocus
C
21

Polly does not provide an .Execute(...) overload where retry count is one of the input parameters to the delegate passed to .Execute(...). This is because retry is only one of many Polly policies, whereas the shape of .Execute(...) overloads must be general to all policy types.

For the use case described in the question, simply:

int count = 0;
return Policy
    .Handle<HttpException>(x => x.StatusCode == (HttpStatusCode)429)
    .Or<StorageException>()
    .WaitAndRetry(maxRetryCount, retryAttempt => TimeSpan.FromMilliseconds(Math.Pow(retryIntervalMs, retryAttempt)))
    .Execute(() => CreateFile(fileContent, containerName, fileName, connectionString, count++));

An alternative approach uses Polly's execution-scoped Polly.Context: an instance of this travels with each execution, and is available to all parts of an execution.

Retry policy already passes the retry count to the onRetry delegate, so the policy can capture this into the execution-scoped Context:

var retryPolicyCapturingCountIntoContext =
    Policy
        .Handle<HttpException>(x => x.StatusCode == (HttpStatusCode)429)
        .Or<StorageException>()
        .WaitAndRetry(
            maxRetryCount,
            retryAttempt => TimeSpan.FromMilliseconds(Math.Pow(retryIntervalMs, retryAttempt)),
            onRetry: (response, delay, retryCount, context) =>
            {
                context["retrycount"] = retryCount;
            });

In the delegate executed through the policy, we can pick the retry count out of Context (taking care to handle the case when no retries have yet happened):

retryPolicyCapturingCountIntoContext
    .Execute(context =>
    {
        int retryCount = (context.TryGetValue("retrycount", out var retryObject) && retryObject is int count) ? count : 0;
        CreateFile(fileContent, containerName, fileName, connectionString, retryCount);
    }, new Context());

If you prefer to avoid the noise of the context.TryGetValue(...) defensive code, you can alternatively choose to ensure you always initialise context["retrycount"] before you start an execution:

var myContext = new Polly.Context { {"retrycount ", 0} };
retryPolicyCapturingCountIntoContext
    .Execute(
         context => CreateFile(fileContent, containerName, fileName, connectionString, (int)context["retrycount"]),
         myContext);


For users who want to capture the retry count of each retry as it happens, say for logging, see the Polly retry examples showing how retryCount is passed as an input parameter to the onRetry delegate which can be configured on the policy. Further examples here.

For users who want to capture the overall number of retries used for an operation to succeed, in a generalised way - say for telemetry as part of some general execution-dispatch infrastructure code - see these examples in Steve Gordon's blog, which use the Context-based approach.

Crocus answered 21/12, 2018 at 7:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.