Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Polly Retry policy with Function is not waiting for result

I am trying to convert my existing function to Polly Retry policy

public static T Execute<T>(Func<T> getTask) where T : Task
{
    var retryCount = 3;
    while (retryCount-- > 0)
    {
        try
        {
            getTask().Wait();
            return getTask();
        } catch(Exception ex){
            // handle retry
        }
    }
}

Converted to this

public static T Execute<T>(Func<T> func) where T : Task
{
    var task = func();
    Policy.Handle<HttpRequestException>()
        .Or<TimeoutException>()
        .WaitAndRetryAsync(
            3,
            retryAttempt => TimeSpan.FromSeconds(Math.Pow(5, retryAttempt)),
            (exception, timeSpan, retryCount, context) =>
            {
                //do some logging
            })
        .ExecuteAsync(func).Wait();
    return task;
}

and test code is

var retryCount = 0;
var res = HttpRetryWrapper.Execute(() => Task.Factory.StartNew<string>(function: () =>
{
    if (++retryCount == 3)
    {
        return "fake";
    }
    throw new TimeoutException();
}));

when I assert the res value, I am not getting the right result. Debugging traces me into point where the Execution is not waiting for results properly.

The number of calls to test function is correct. However the logging is messed up and the final result is not having result fake

like image 756
RaceBase Avatar asked Jun 12 '18 12:06

RaceBase


1 Answers

For a helper method for async executions through a hard-coded Polly policy, where the executions asynchronously return a type TResult, via a Task<TResult>, you could adopt:

public static Task<TResult> ExecuteAsync<TResult>(Func<Task<TResult>> func) 
{
    return Policy.Handle<HttpRequestException>()
        .Or<TimeoutException>()
        .WaitAndRetryAsync(
            3,
            retryAttempt => TimeSpan.FromSeconds(Math.Pow(5, retryAttempt)),
            (exception, timeSpan, retryCount, context) =>
            {
                //do some logging
            })
        .ExecuteAsync<TResult>(func); // This is an async-await-eliding contraction of: .ExecuteAsync<TResult>(async () => await func());
    }

(Given the policy used is the same every time, you could consider also storing the policy in a static field and creating it only once.)

Note: This (intentionally) doesn't conform to the contract stated in your comment to your original question, as being unbreakable:

public static T Execute<T>(Func<T> func) where T : Task

The clause where T : Task initially looks appealing for an or async async-like method designed to work with either Task or Task<T>. Jon Skeet explains here and here why it doesn't work with async. Your proposed helper method signature is not in-itself async:

public static T Execute<T>(Func<T> func) where T : Task

However, introducing .ExecuteAsync(async () => await func()); in your example code forces a similar issue. The Polly .ExecuteAsync(...) overload, precisely in order to play nicely with async/await, exists in two main forms:

(1) Task ExecuteAsync(Func<Task> func)

(2) Task<TResult> ExecuteAsync<TResult>(Func<Task<TResult>> func)

The compiler has to select one or the other at compile time: it can't (in your example code) compile it to be either (1) or (2) at runtime. Since it only knows T : Task it selects (1), which returns Task. Hence the error you see in your comment to @JamesFaix's answer: Cannot implicitly convert type Task to T.

If you want helper patterns of this form, which callers can use for either Task or Task<TResult> -returning calls, you would have to declare both:

class HttpRetryWrapper
{
    private static policy = Policy.Handle<HttpRequestException>()
        .Or<TimeoutException>()
        .WaitAndRetryAsync(
            3,
            retryAttempt => TimeSpan.FromSeconds(Math.Pow(5, retryAttempt)),
            (exception, timeSpan, retryCount, context) =>
            {
                //do some logging
            });

    public static Task ExecuteAsync(Func<Task> func) 
    {
        return policy.ExecuteAsync(func);
    }

    public static Task<TResult> ExecuteAsync<TResult>(Func<Task<TResult>> func) 
    {
        return policy.ExecuteAsync<TResult>(func);
    }
}

Finally, if this is for wrapping calls placed through HttpClient, the recommended pattern is to place the Polly policies in a DelegatingHandler as described in @MuhammedRehanSaeed's answer here. ASP.NET Core 2.1 supports concise declaration for creating such DelegatingHandlers using IHttpClientFactory.

like image 117
mountain traveller Avatar answered Nov 15 '22 00:11

mountain traveller