Danation
Danation

Reputation: 793

Adjusting Timeout duration based on Retry Count

I'd like to use Polly to do the following: Attempt a request with a very short timeout. If it fails, retry with a longer timeout.

I see that Retry can access retryCount like this:

Policy
.Handle<SomeExceptionType>()
.Retry(3, (exception, retryCount, context) =>
{
    // do something 
});

And I see that Timeout can specify an int or TimeSpan, like this:

Policy.Timeout(TimeSpan.FromMilliseconds(2500))

I even see that you can pass a function in to the timeout, like this:

Policy.Timeout(() => myTimeoutProvider)) // Func<TimeSpan> myTimeoutProvider

The Func option seems the most promising, but where could it access the retry count? It's tempting to keep state outside of the policy, but that's dangerous if I ever want to share the policy in a thread safe manner.

Any advice?

Upvotes: 4

Views: 1508

Answers (2)

mountain traveller
mountain traveller

Reputation: 8156

You can use Polly Context to pass state data between different policies involved in an execution. A unique instance of Polly Context flows with every Polly execution, so this is entirely thread-safe.

More detail on this technique in this blog post.

For example:

const string RetryCountKey = "RetryCount";

RetryPolicy retryStoringRetryCount = Policy
    .Handle<Exception>()
    .Retry(3, (exception, retryCount, context) =>
    {
        Console.WriteLine("Storing retry count of " + retryCount + " in execution context.");
        context[RetryCountKey] = retryCount;
    });

TimeoutPolicy timeoutBasedOnRetryCount = Policy
    .Timeout(context =>
    {
        int tryCount;
        try
        {
            tryCount = (int) context[RetryCountKey];
        }
        catch
        {
            tryCount = 0; // choose your own default for when it is not set; also applies to first try, before any retries
        }

        int timeoutMs = 25 * (tryCount + 1);
        Console.WriteLine("Obtained retry count of " + tryCount + " from context, thus timeout is " + timeoutMs + " ms.");
        return TimeSpan.FromMilliseconds(timeoutMs);
    });

PolicyWrap policiesTogether = retryStoringRetryCount.Wrap(timeoutBasedOnRetryCount);

(Note: Of course this ^ can be made more concise. Set out here for maximum clarity.)

Here is a live dotnetfiddle example.

Upvotes: 6

RBreuer
RBreuer

Reputation: 1391

From: https://github.com/App-vNext/Polly/wiki/Timeout

int retryCount_ = 0;

Func<TimeSpan> myTimeoutProvider = () => 
    TimeSpan.FromMilliseconds(25*retryCount_);

// Configure variable timeout via a func provider.
Policy
    .Timeout(() => myTimeoutProvider)) // Func<TimeSpan> myTimeoutProvider
    .Retry(3, (exception, retryCount, context) =>
    {
        retryCount_ = retryCount;
        // do something 
    })

or set the timeout similar to the way shown in:

Policy
    .Handle<SomeExceptionType>()
    .WaitAndRetry(5, retryAttempt => 
        TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)) 
    );

UPDATE

You can also hook onto onTimeoutAsync callback and keep increasing your local variable that myTimeoutProvider relies on.

Upvotes: 1

Related Questions