Thursday, February 13, 2020

Poor Man's Polly - Retry Logic

When consuming a REST API sometimes there's a glitch and your call to the REST endpoint fails. For example, the REST endpoint might be undergoing a quick restart, or your oAuth token might have just expired, or you might get timeout's during high peak times, and sometimes the endpoint is just ... well, a little buggy.

How can you make your code more resilient so as to provide a better experience for your customers? One way is to retry when things fail. This can end up in cumbersome code if you are not careful. Let's take a look at how to write simple retry logic that's understandable.

However, before we do there is very sophisticated NuGet package out there called Polly. If, after reading this, you decide you need more functionality than this little sample provides then definitely take a look at Polly. This little sample barely scratches the surface of what Polly provides. Still, this is an interesting exercise if you have never written anything like this before.

A Simple Example

The code below is using Flurl to access the free "World Clock API" to get the current UTC time. Notce the way the Retry.Execute() call takes a function as it's parameter. The Retry.Execute() function will, upon failure, retry this call in 1, 3 and 10 seconds respectively and throw an exception if no retries succeed.

static async Task<string> GetUtcFromWorlClock()
{
    return await Retry.Execute<string>(async () =>
    {
        return await "http://worldclockapi.com/api/json/utc/now".GetStringAsync();
    });
}

A More Advanced Example

This example supplies an error handler function and overrides the default (1, 3 and 10) second retry intervals.
static async Task<string> GetUtcFromWorlClockAdvanced()
{
    return await Retry.Execute<string>(
        async () =>
        {
            return await "http://worldclockapi.com/api/json/utc/now".GetStringAsync();
        },
        async (FlurlHttpException ex) =>
        {
            // Called if all retries fail
            Console.WriteLine(ex.Message);
            throw ex;
        },
        // Retry in 1, 5, 10, and 30 seconds
        new int[] { 1000, 5000, 10000, 30000 }
    );
}

The Retry Class

The Retry class is itself surprisingly very simple. If you need a starting point feel free to use this as a reference. However, don't rewrite what is available in Polly. If Polly is overkill, and sometimes it is, and all you need is simple retry logic like this, you might give it a try.
using Flurl.Http;
using System.Threading.Tasks;

namespace PoorMansPolly
{
  public delegate Task<T> MyFunction<T>();
  public delegate Task MyError(FlurlHttpException exception);

  public static class Retry
  {
      private static readonly int[] retries = new[] { 1000, 3000, 10000 };

      public static async Task<T> Execute<T>(MyFunction<T> myFunc, MyError myError = null, int[] delays = null)
      {
          int[] retryDelays = delays ?? retries;
          int tryCount = 0;
          int maxTryCount = retryDelays.Length;

          T response = default(T);

          while (true)
          {
              try
              {
                  response = await myFunc();
                  break;
              }
              catch (FlurlHttpException ex)
              {
                  if (tryCount == maxTryCount)
                  {
                      if (myError != null)
                      {
                          await myError(ex);
                          break;
                      }
                      else
                      {
                          throw;
                      }
                  }

                  await Task.Delay(retryDelays[tryCount++]);
              }
          }

          return response;
      }
  }
}

So that's it, Poor Man's Polly ....

1 comment:

  1. Dear Mike,

    Thank you for all your great works. I have loved you for a long time, since 2009
    I wish you and your family best wishes

    I love you and your codes

    Take care,

    Best regards,

    Le Khanh Thanh

    ReplyDelete