alt text

Update 29th Aug 2021

I’m now using Dapper and Polly - please see this article for most up to date thinking.

Update 14th Oct 2020

I’ve shied away from using Polly in favour of a simpler implementation. MPActors Source is a good start.

My 2020 article on Dapper contains current thoughts and implementations.

Introduction

I’m writing articles on developing a website broken link checker in C#. This is part of that series.

There is a intro article on how I used Dapper

In my broken link checker I’m using SQL Azure, which very infrequently will not work, so I needed some kind of retry mechanism in my SQL calls. I reached out to the excellent Polly library.

I’m also doing a lot of Http calls in my broken link checker, so have explored how polly could help too.

SQL

Polly

https://hyr.mn/dapper-and-polly/ has a good tutorial where he uses an extension method.

public static async Task<IEnumerable<Actor>> GetTop10ActorsWithRetry(string connectionString)
    => await WithConnection(connectionString, async x =>
    {
        // Using an extension method to call the polly retry code
        var result = await x.QueryAsyncWithRetry<Actor>(
            @"SELECT TOP 10 *
            FROM Actors");
        return result;
    });

We can make the code more terse using our wrapper function. This means we don’t need the extension methods.

public static async Task<T> WithConnection<T>(
    string connectionString,
    Func<IDbConnection, Task<T>> func)
{
    await using var conn = new SqlConnection(connectionString);
    //return await func(conn);
    return await DapperExtensions.RetryPolicy.ExecuteAsync(() => func(conn));
}

Http

Polly-Samples

  • RetryNTimes
  • WaitAndRetryNTimes
  • WaitAndRetryNTimesWithExponentialBackOff

I like this simple backoff strategy then eventual failure.

var httpClient = new HttpClient();
var url = "https://localhost:44307/api/values";

// Define our policy:
var policy = Policy.Handle<Exception>().WaitAndRetryAsync(
    6, // Could do forever ie miss this out, but good to do this ie max of 6400ms then it will fail
    attempt => TimeSpan.FromSeconds(0.1 * Math.Pow(2,
                                             attempt)), // Back off! 200,400,800,1600ms etc.. 
    (exception, calculatedWaitDuration) => 
{
    Console.WriteLine($"{exception.Message} : Auto delay for {calculatedWaitDuration.TotalMilliseconds}ms");
});

while (true)
{
    try
    {
        await policy.ExecuteAsync(async () =>
        {
            var httpResponseMessage = await httpClient.GetAsync(url);

            // throws if .IsSuccessStatusCode property is false
            httpResponseMessage.EnsureSuccessStatusCode();

            var sc = httpResponseMessage.StatusCode;
            Console.WriteLine($"Status code is {(int)sc}{sc}");

            var content = await httpResponseMessage.Content.ReadAsStringAsync();
            Console.WriteLine($"Content is {content}");
        });
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Request eventually failed with {ex.Message}");
    }

    await Task.Delay(500);
}

Conclusion

This could be much simplified without having to use Polly. So that is what I’m considering ie a simple exponential back off (using code similar to above).