Building resilient .NET applications with Polly - Part 4

In this post, we will learn how to address the issue of an unresponsive server by incorporating a straightforward timeout mechanism using Polly.

What was the issue in the previous scenario ?

The problem arose from a service that never responded. Since the calling code did not enforce timeouts, the thread became suspended. Game over.

The timeout is a simple mechanism allowing you to stop waiting for an answer once you think it won't come. (...) It's essential that any resource pool that blocks threads must have a timeout to ensure that calling threads eventually unblock, whether resources become available or not.
Nygard Release It!: Design and Deploy Production-Ready Software

Installing Polly

  • Add the Polly Nuget package to the EOCS.Polly.CallingService project.

Important

For this series, we are utilizing Polly version 8.

Configuring Polly

Various methods exist to address the challenges posed by network issues or failures in integration points. In Polly, these methods are termed resilience strategies (formerly policies). These strategies can also be combined; for instance, we can implement a timeout policy and fallback to a default value in case of failure. This introduces the concept of a resilience pipeline, which is the amalgamation of multiple strategies for managing a request.

Information

Additional details can be found in the documentation (here).

  • Edit the code in the GetAccountById method.
 1[FunctionName(nameof(GetAccountById))]
 2public async Task<IActionResult> GetAccountById([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)] HttpRequest req, ILogger log)
 3{
 4    var pipeline = new ResiliencePipelineBuilder().AddTimeout(TimeSpan.FromSeconds(5)).Build();
 5                
 6    // Execute the pipeline asynchronously
 7    var response = await pipeline.ExecuteAsync(async token => 
 8    {
 9        var client = new HttpClient();
10        return await client.GetAsync("http://localhost:7271/api/GetNeverResponding", token).ConfigureAwait(false);
11    });
12
13    return response.IsSuccessStatusCode ? new OkResult() : new BadRequestResult();
14}

Upon executing the request via Fiddler, we now receive a response.

Information 1

Now, we indeed receive a response, even though it may be an error 500. It's important to note that Polly doesn't magically resolve the issue with the flawed service but prevents this problem from propagating throughout the entire system: this phenomenon is what is referred to as resilience. In this case, the response might not be the expected one, but crucially, calling threads are not blocked. It's essential to acknowledge that the responsibility to promptly resolve the ongoing issue still rests with us.

Information 2

In a real-world scenario, it is likely that we would prefer to utilize dependency injection instead of creating the pipeline for each request.

Handling failure

In the previous code, we settle for returning an error 500, but such an error is rather generic and doesn't offer much insight into the underlying problem. It would be more beneficial to provide additional information to developers, especially when we ascertain that a timeout has occurred.

  • Edit the code in the GetAccountById method.
 1[FunctionName(nameof(GetAccountById))]
 2public async Task<IActionResult> GetAccountById([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)] HttpRequest req, ILogger log)
 3{
 4    var pipeline = new ResiliencePipelineBuilder().AddTimeout(TimeSpan.FromSeconds(5)).Build();
 5
 6    try
 7    {
 8        // Execute the pipeline asynchronously
 9        var response = await pipeline.ExecuteAsync(async token =>
10        {
11            var client = new HttpClient();
12            return await client.GetAsync("http://localhost:7271/api/GetNeverResponding", token);
13        });
14
15        return new OkResult();
16    }
17    catch (TimeoutRejectedException)
18    {
19        return new BadRequestObjectResult("A timeout has occurred while processing the request.");
20    }
21}

Upon executing the request via Fiddler, we now receive a error 400.

So, we explored the installation and configuration of Polly in a very basic scenario. However, this case was somewhat trivial (in the strict sense, using Polly for a simple timeout is unnecessary). Moving forward, we will dedicate our focus to examining more sophisticated scenarios.

Building resilient applications with Polly - Part 5