In this article, we’ll explore how to use Polly in C# and how it can help handle faults and retries with ease. Polly, a popular NuGet package, provides a powerful set of tools and patterns that can greatly simplify fault handling and retry mechanisms in our code.
By leveraging Polly in our code, we can handle various types of faults, such as network errors or database connection failures. We can then implement intelligent retry strategies to ensure that our application doesn’t blow up when one of these inevitably has an issue (Remember: Murphy’s Law). Polly offers a flexible and configurable way of dealing with these scenarios, whether we need to handle transient faults or simply retry a failed operation.
I’ll show you 3 interesting code examples that showcase different use cases of Polly in C# — so let’s get right to it!
It’s important to handle faults effectively when building robust and reliable applications. Fault handling refers to handling errors, exceptions, and failures that can occur during the execution of a program — and we’re going to use Polly in C# to help us out here.
To demonstrate how Polly can handle a connectivity issue, let’s consider a scenario where our application needs to make a network request to an external API. We want to ensure that the request is retried automatically in case of any connection-related faults, such as temporary network outages or intermittent failures.
Let’s see how to use Polly in C# to handle a connectivity issue in a network request:
using Polly;
using Polly.Retry;
var handler = new NetworkRequestHandler();
var response = await handler.MakeNetworkRequestAsync("https://www.devleader.ca/sitemap.xml");
Console.WriteLine($"Response status code: {response.StatusCode}");
public class NetworkRequestHandler
{
private readonly HttpClient _httpClient;
private readonly AsyncRetryPolicy<HttpResponseMessage> _retryPolicy;
public NetworkRequestHandler()
{
_httpClient = new HttpClient();
// Define a retry policy: retry on HttpRequestException, 3 retries with exponential backoff
_retryPolicy = Policy
.HandleResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
.Or<HttpRequestException>()
.WaitAndRetryAsync(3, retryAttempt =>
TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
onRetry: (outcome, timespan, retryAttempt, context) =>
{
Console.WriteLine($"Request failed with {outcome.Exception?.Message}. Waiting {timespan} before next retry. Retry attempt {retryAttempt}.");
});
}
public async Task<HttpResponseMessage> MakeNetworkRequestAsync(string requestUri)
{
return await _retryPolicy.ExecuteAsync(async () =>
{
Console.WriteLine($"Making request to {requestUri}");
var response = await _httpClient.GetAsync(requestUri);
if (!response.IsSuccessStatusCode)
{
Console.WriteLine($"Request failed with status code: {response.StatusCode}");
throw new HttpRequestException($"Request to {requestUri} failed with status code {response.StatusCode}");
}
return response;
});
}
}
In the code example above, we define a NetworkRequestHandler
class that encapsulates the logic for making a network request using the HttpClient
class from the .NET framework. We initialize a AsyncRetryPolicy
from Polly with a fault condition of type HttpRequestException
. This means that the policy will be triggered whenever an HttpRequestException
is thrown.
The WaitAndRetry
method is used to specify the number of retry attempts and the time delay between each retry. In this example, we retry the request 3 times, with exponentially increasing delays using the formula TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))
.
In the MakeNetworkRequest
method, we use the Polly policy to execute the network request by calling the Execute
method on the policy. If a HttpRequestException
occurs during the execution, Polly will automatically retry the request according to the defined policy.
In software engineering, it is common to encounter scenarios where a particular operation may fail temporarily due to network issues, resource limitations, or other transient faults. In such cases, retry mechanisms are implemented to automatically retry the failed operation for a certain number of times until it succeeds or reaches a maximum retry count. This helps improve the reliability and robustness of the software application.
When it comes to implementing retry mechanisms in C#, the NuGet package Polly comes to the rescue. Polly is a powerful library that provides an easy and elegant way to handle faults and retries in your code. It simplifies the process of implementing retries by encapsulating the complex retry logic into a set of reusable policies.
Let’s consider a scenario where we need to perform a database operation, such as inserting a record into a table, but there might be temporary connectivity issues or resource constraints that could cause the operation to fail. With Polly, we can easily handle such failures by applying a retry policy.
First, we need to install the Polly NuGet package by adding the following line to the dependencies section of our project file (keeping in mind that the version you want to grab is likely newer!):
<ItemGroup>
<PackageReference Include="Polly" Version="8.3.0" />
</ItemGroup>
Once the package is installed, we can begin using Polly to implement the retry mechanism. Here’s an example code snippet that demonstrates how to use Polly to automatically retry a failing database operation:
using Polly;
using System.Data.SqlClient;
async Task<bool> InsertRecordAsync(
Record record,
CancellationToken cancellationToken)
{
int maxRetryCount = 3;
var connectionString = "// TODO: configure this elsewhere...";
var retryPolicy = Policy
.Handle<SqlException>() // Specify the type of exception to handle
.WaitAndRetryAsync(maxRetryCount, retryAttempt =>
TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))); // Exponential backoff strategy
return await retryPolicy.ExecuteAsync(async ct =>
{
using var connection = new SqlConnection(connectionString);
await connection.OpenAsync();
using var command = connection.CreateCommand();
command.CommandText = "INSERT INTO Records (Id, Name) VALUES (@Id, @Name)";
command.Parameters.AddWithValue("@Id", record.Id);
command.Parameters.AddWithValue("@Name", record.Name);
try
{
// Perform the database operation
var result = await command.ExecuteNonQueryAsync(ct);
return result == 1;
}
catch (SqlException)
{
// Rethrow the exception to let Polly handle the retry logic
throw;
}
}, cancellationToken);
}
public sealed record Record(
int Id,
string Name);
In the above example, we define a retry policy using the Policy.Handle<SqlException>()
method to specify the type of exception we want to handle, in this case, SQL exceptions. We then use the WaitAndRetryAsync
method to configure the policy to retry the operation up to maxRetryCount
times with an exponential backoff strategy, where each retry attempt waits for an increasing amount of time.
Inside the ExecuteAsync
method, we wrap the database operation in a try-catch block. If the operation fails due to a SQL exception, we throw the exception to let Polly handle the retry logic. Polly will automatically retry the operation according to the defined policy, providing a seamless experience for handling temporary faults.
Sometimes we need more advanced retry mechanisms and circuit breaker patterns to get us to those next levels of resilience in our applications. Fortunately, the Polly library in C# has us covered for these as well!
When working with external services or resources, it’s common to encounter transient faults such as network issues or service unavailability. Polly allows us to define custom retry policies that dictate how many times to retry and with what delay between retries. This ensures that our application can gracefully handle temporary failures and improve the overall reliability of the system.
Polly provides flexible options to customize retry policies based on specific requirements. We can define the number of retries, the backoff strategy to determine the delay between retries, and even define a predicate to selectively retry only on certain exceptions. By tailoring these policies, we can handle different scenarios effectively.
Here’s an example that shows how to use Polly for implementing a retry policy with an exponential backoff strategy:
var retryPolicy = Policy
.Handle<HttpRequestException>()
.OrResult<HttpResponseMessage>(response => response.StatusCode == HttpStatusCode.InternalServerError)
.WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
In this example, we define a retry policy that will retry the operation up to 3 times if an HttpRequestException
or if the HttpResponseMessage
has a status code of InternalServerError
. The backoff strategy is exponential, with the delay between retries increasing exponentially based on the retry attempt number.
Circuit breaker patterns play an important role in preventing cascading failures and protecting systems from a flurry of repeated requests to a failing service. Polly provides built-in support for implementing circuit breaker patterns, which help in automatically managing the state of the circuit breaker and controlling the flow of requests.
To implement a circuit breaker pattern with Polly, we need to define the threshold values for failures and successes, along with the time window in which to monitor these metrics. Once the circuit breaker trips, further requests are short-circuited, preventing unnecessary calls to the failing service.
Here’s an example of how to use Polly for implementing a circuit breaker pattern:
var circuitBreakerPolicy = Policy
.Handle<HttpRequestException>()
.OrResult<HttpResponseMessage>(response => response.StatusCode == HttpStatusCode.InternalServerError)
.CircuitBreakerAsync(3, TimeSpan.FromSeconds(30));
In this example, the circuit breaker policy is configured to trip open if there are 3 consecutive failures within a 30-second window. Once the circuit breaker is open, subsequent requests will be automatically rejected, preventing further calls until a specified recovery period has passed.
You’ve done it! You have 3 working code examples to help you understand how to use Polly in C#. By Polly, you can handle faults and retries with ease, improving the reliability and resilience of your applications. In this article, we explored three different use cases of Polly, covering fault handling, basic retries, and advanced retry patterns like circuit breakers.
By incorporating Polly into your projects, you can enhance the stability and responsiveness of your software. By using policies to specify retry counts, durations, and other configurable parameters, you can control the behavior and tolerance of your application in the face of failures. If you found this useful and you’re looking for more learning opportunities, consider subscribing to my free weekly software engineering newsletter and check out my free videos on YouTube!
Also published here.