ThinkingCog

Articles written by Parakh Singhal

Polly – Immediate Retry Pattern

Introduction

I previously wrote an article detailing the retry resilience pattern and its implementation in Polly framework. The article is a bit long and provides an overview of what needs to be done to implement the resilience in an ASP.Net web API setting. This article is more targeted in nature and picks up from the previous article and focuses on implementation of the immediate retry pattern.

Immediate Retry Pattern

Among the retry patterns available in Polly, the immediate retry pattern is perhaps the simplest to understand. The pattern calls the failed service or dependency immediately without any delay between the original and the subsequent retry calls. Since this pattern does not generally provide time to the failed service or dependency to recuperate and serve back, it is generally used where the service or dependency seldom fails. It is also applied where the response times are crucial to maintain a good user experience. Some examples include a heartbeat service, disk write service in an operating system etc.

Retry Pattern Swimlane Diagram

Figure 1 Retry Pattern Swimlane Diagram

Example

We will simulate a machine-to-machine communication where a Web API project will act as a server and another Web API server will act as a consumer of the service end point exposed by the server project. The consumer project will implement Polly resilience framework. Since I have covered the basics of Polly framework and covered topics like strategies, pipelines and their implementation in a code example in detail in my previous article, I will just gloss over the main details here.

ProjectStructure

Figure 2 Project structure

A server project serves an action method and randomly allows only 30% of the incoming requests to pass.

[ApiController]
[Route("api/[controller]")]
public class ServiceController : Controller
{
    [HttpGet]
    [Route("")]
    public IActionResult ServiceEndpoint()
    {
        Random random = new Random();
        int dice = random.Next(1, 100);
 
        if (dice < 30)
        {
            return Ok("Call succeeded. Dice rolled in your favour.");
        }
        else if (dice > 30 && dice < 50) 
        {
            return StatusCode(StatusCodes.Status502BadGateway, "Bad gateway.");
        }
        else if(dice > 50 && dice < 70)
        {
            return StatusCode(StatusCodes.Status500InternalServerError, "Internal server error.");
        }
        else 
        {
            return StatusCode(StatusCodes.Status408RequestTimeout, "Request Timeout.");
        }
    }
}

 

A consumer project consumes server project’s service end point and deploys the Polly resilience project. We create artefacts that will help us implement the immediate retry pattern, which includes, the strategy corresponding to immediate retry pattern, http codes which would spurn the retry logic into action and the predicate that would be invoked in case we receive an unfavourable http status back from the server. The strategy, corresponding options and initialization of the strategy into a pipeline will be done in a special file called “PollyStrategies.cs” placed in a special folder “ResilienceStrategies” folder in the consumer project.

public class PollyStrategies
{
    public ResiliencePipelineRegistry<string> StrategyPipelineRegistry { get; private set; }        
 
    public ResiliencePipeline<HttpResponseMessage>? ImmediateRetryStrategy { get; private set; }
    private RetryStrategyOptions<HttpResponseMessage>? immediateRetryStrategyOptions;     
 
    HttpStatusCode[] httpStatusCodesWorthRetrying = new HttpStatusCode[] {
                                                       HttpStatusCode.RequestTimeout,// 408
                                                       HttpStatusCode.InternalServerError, // 500
                                                       HttpStatusCode.BadGateway, // 502
                                                       HttpStatusCode.ServiceUnavailable, // 503
                                                       HttpStatusCode.GatewayTimeout // 504
                                                    };        
 
    private void InitializeOptions()
    {
        immediateRetryStrategyOptions = new RetryStrategyOptions<HttpResponseMessage>()
        {
            MaxRetryAttempts = 10,
            BackoffType = DelayBackoffType.Constant,
            Delay = TimeSpan.Zero,
            ShouldHandle = new PredicateBuilder<HttpResponseMessage>()
                               .HandleResult(response => httpStatusCodesWorthRetrying.Contains(response.StatusCode))
                               .Handle<HttpRequestException>()
                               .Handle<TimeoutRejectedException>(),
            OnRetry = async args => { await Console.Out.WriteLineAsync("ImmediateRetry - Retrying call..."); }
 
        };        
    }
 
    private void InitializePipelines()
    {
        ImmediateRetryStrategy = new ResiliencePipelineBuilder<HttpResponseMessage>().AddRetry<HttpResponseMessage>(immediateRetryStrategyOptions).Build();
            
    }
 
    private void RegisterPipelines()
    {
        StrategyPipelineRegistry = new ResiliencePipelineRegistry<string>();
 
        StrategyPipelineRegistry.TryAddBuilder<HttpResponseMessage>("ImmediateRetry", (builder, context) =>
        {
            builder.AddPipeline(ImmediateRetryStrategy);
 
        });
    }
 
    public PollyStrategies()
    {
        InitializeOptions();
        InitializePipelines();
        RegisterPipelines();
    }
}

Next, we perform the dependency injection of the PollyStrategies class as a singleton instance. This will make the Polly retry pipeline available across the consumer controllers. Next, we consume the retry pipeline, in our action method as shown in the example below.

Code in Program class in Program.cs file.

public class Program
{
    public static void Main(string[] args)
    {
        var builder = WebApplication.CreateBuilder(args);
 
        // Add services to the container.
 
        builder.Services.AddHttpClient();
        builder.Services.AddSingleton<PollyStrategies>(new PollyStrategies());
        builder.Services.AddControllers();
 
 
        var app = builder.Build();
 
        // Configure the HTTP request pipeline.
 
        app.UseAuthorization();
 
        app.MapControllers();
 
        app.Run();
    }
}

Code in the action method in Consumer controller class.

[ApiController]
 [Route("api/[controller]")]
 public class ConsumerController : Controller
 {
     private readonly IHttpClientFactory httpClientFactory;
     private readonly PollyStrategies pollyStrategies;
 
     public ConsumerController(IHttpClientFactory _httpclientFactory, PollyStrategies _pollyStrategies)
     {
         httpClientFactory = _httpclientFactory;
         pollyStrategies = _pollyStrategies;
     }
 
     public IActionResult ConsumerEndPoint()
     {
         string url = "http://localhost:5106/api/service";
 
         HttpClient client = httpClientFactory.CreateClient();
 
         HttpResponseMessage response = pollyStrategies
                                        .StrategyPipelineRegistry.GetPipeline<HttpResponseMessage>("ImmediateRetry")
                                        .Execute(() => client.GetAsync(url).Result);            
 
         if (response.StatusCode == HttpStatusCode.OK)
         {
             return Ok("Server responded");
         }
         else
         {
             return StatusCode((int)response.StatusCode, "Problem happened with the request")  ;
         }
     }
 }

 

Once we run the project, and execute a request against the consumer project, there’s a fair probability that we will encounter an unfavourable http status code from the server, and our Polly immediate retry pipeline and corresponding strategy will come into action, and will immediate fire back a retry call to the server’s end point, till it gets favorably resolved, subject to the max retries programmed in the strategy.

Immediate Retry Pattern in action

Figure 3 Immediate Retry Pattern in Action

Immediate Retry Successful in Fiddler

Figure 4 Desired end result as experienced by a user

Hope this concise introduction to immediate retry pattern was helpful.

References

1. Polly documentation: https://www.pollydocs.org/strategies/retry.html

2. Azure Architecture: https://learn.microsoft.com/en-us/azure/architecture/patterns/retry

3. ThinkMicroServices: http://thinkmicroservices.com/blog/2019/retry-pattern.html

Code

Please follow the link to obtain the code.

ASP.Net Core MVC on Raspberry Pi

Key Takeaway:

.Net Core allows for a cross platform operation of applications on supported hardware and software. This extends to ASP.Net Core. In this post I am going to show how to run ASP.Net Core in self-contained deployment mode on Raspberry Pi 3.

Read On

In my last post I showed how to run a .Net Core console application in Raspberry Pi. In this post I am going to show how to run an ASP.Net Core Web application on Raspbian Stretch operating system using Raspberry Pi 3 hardware. Before you do that make sure that you have assigned a static IP address to Pi. You can learn how to do that in one of my previous post.

First create a new ASP.Net Core Web application project in Visual Studio which does not rely on any kind of authentication.

ASP.Net Core Web App

ASP.NET Core Web Application

02 No authentication

Web application with no authentication

Since the aim of this post is learn how to run an ASP.Net Core application on Pi, let’s keep things simple. We will not do any modification to any of the pages in the application. Build and run the application locally to make sure that it works.

03 ASP.Net Core app running

Web application running out of the box

The application is running locally using IIS Express and listening at the address mentioned in launchSettings.json file under Properties in the project hierarchy. When it comes to hosting the application in Pi, we need to makes sure that the application listens at the desired IP address and port. This is accomplished using the “UseUrls” method in Program.cs file. The “UseUrls” method specifies the URL scheme that the web host will use to listen to the incoming requests. Since we will be using the Kestrel web server via terminal in Pi, it is important that we change the port in the Program.cs file, as shown in the image. Make sure that the port that you assign is not in use by some other app in Pi.

04 Program.cs file

Change the port to something that is available in Pi

Now publish the entire application for linux-arm combination using the following command:

dotnet publish -r linux-arm

 

Now copy the entire publish directory to Pi. This will give us not only our application, but also the server infrastructure to serve the application. Make sure that you have the appropriate permission to run not only the application, but also the Kestrel server under your account. You can use the following command to recursively allow your account have the execute permission on all the assemblies inside the publish folder.

chmod –R 755 publish

 

Once that is done, execute the application:

05 Kestrel running

Kestrel running

Now hop into your browser in your computer and use the IP address of your Pi in conjunction of the port on which the Kestrel server is listening.

06 Application running locally

ASP.NET Core Web application being served by Pi

Happy exploration.