Wednesday, 12 June 2019

Create a simple Retry Policy by using Polly in any fault.

Polly a .NET fault-handling wrapper that allows developers to express policies in thread safe manner.
Policies could be made over:

  • Retry
  • Circuit Breaker
  • Timeout
  • Bulkhead Isolation
  • Fallback.

More: <https://github.com/App-vNext/Polly

Simple Demo:

using Polly;
using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
 
namespace PollyRetry
{
 
    class Program
    {
        static void Main(string[] args)
        {
            HttpRequestDemo();
            Console.WriteLine("Done!");
            Console.ReadLine();
        }
 
        private static void HttpRequestDemo()
        {
 
 
            HttpStatusCode[] httpStatusCodesWorthRetrying = {
                HttpStatusCode.RequestTimeout, // 408
                HttpStatusCode.InternalServerError, // 500
                HttpStatusCode.BadGateway, // 502
                HttpStatusCode.ServiceUnavailable, // 503
                HttpStatusCode.GatewayTimeout, // 504
                HttpStatusCode.NotFound // 404
            };
 
            var httpRetryPolicy = Policy.HandleResult<HttpResponseMessage>(r => httpStatusCodesWorthRetrying.Contains(r.StatusCode))
            .OrResult(r => r.StatusCode == HttpStatusCode.BadGateway)
            .WaitAndRetry(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), (extime) =>
            {
                Console.WriteLine($"Unable to complete operation after {time.TotalSeconds:n1}s ({ex.Result.StatusCode})");
            });
 
            using (var client = new HttpClient())
            {
                client.BaseAddress = new Uri("http://localhost:51976/");
                client.DefaultRequestHeaders.Accept.Clear();
                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                var response = httpRetryPolicy.ExecuteAndCapture(() =>
                {
                    return client.GetAsync("api/order").Result;
                }).FinalHandledResult;
 
                if (response != null && response.IsSuccessStatusCode)
                {
                    Console.WriteLine("Got valid response from API");
                }
            }
 
 
        }
    }
}


We can also create multiple policy to handle multiple faults, like server is down and we are unable to establish connection with the service. In that case at initial call above code will produce exception. So, I made some changes in code, created two policies and wrapped first one with 2nd once.

using Polly;
using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
 
namespace PollyRetry
{
 
    class Program
    {
        static async System.Threading.Tasks.Task Main(string[] args)
        {
            await HttpRequestDemoAsync();
            Console.WriteLine("Done!");
            Console.ReadLine();
        }
 
        private static async System.Threading.Tasks.Task HttpRequestDemoAsync()
        {
 
            int retryCount = 5;
 
            HttpStatusCode[] httpStatusCodesWorthRetrying = {
                HttpStatusCode.RequestTimeout, // 408
                HttpStatusCode.InternalServerError, // 500
                HttpStatusCode.BadGateway, // 502
                HttpStatusCode.ServiceUnavailable, // 503
                HttpStatusCode.GatewayTimeout, // 504
                HttpStatusCode.NotFound // 404
            };
 
 
            var httpRetryPolicy = Policy.HandleResult<HttpResponseMessage>(r => httpStatusCodesWorthRetrying.Contains(r.StatusCode))
            .OrResult(r => r.StatusCode == HttpStatusCode.BadGateway)
            .WaitAndRetryAsync(retryCountretryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), (exceptioncalculatedWaitDuration) =>
            {
                Console.WriteLine($"Unable to complete operation after {calculatedWaitDuration.TotalSeconds:n1}s ({exception.Result.StatusCode})");
            });
 
            var exceptionPolicy = Policy
            .Handle<Exception>()
            .WaitAndRetryAsync(retryCountretryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), (exceptioncalculatedWaitDuration) =>
            {
                Console.WriteLine($"Error while performing operation after {calculatedWaitDuration.TotalSeconds:n1}{exception.Message}");
            })
            .WrapAsync(httpRetryPolicy);
 
 
            using (var client = new HttpClient())
            {
                client.BaseAddress = new Uri("http://localhost:51976/");
                client.DefaultRequestHeaders.Accept.Clear();
                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                var response = (await exceptionPolicy.ExecuteAndCaptureAsync(() =>
                {
                    return client.GetAsync("api/order");
                })).FinalHandledResult;
 
                if (response != null && response.IsSuccessStatusCode)
                {
                    Console.WriteLine("Got valid response from API");
                }
            }
        }
    }
}

Now you can see variation in fault message

In case of DotNetCore we can use Microsoft.Extensions.Http.Polly.