Polly is an Open Source library for .Net that allows implementing certain resilience patterns and exception management/recovery in .Net applications.
When we face processes that are susceptible to throw exceptions there are several patterns or strategies that we can follow to manage the behavior in case of an error. However, implementing these patterns requires time and code.
Polly simplifies the process, allowing us to implement patterns like Retry, Timeout, or Circuit-breaker in a robust and very simple way through its Fluent syntax.
The patterns that we can implement with Polly are:
- Retry: Attempts to execute the process N times or indefinitely.
- Circuit-breaker: When several failures occur, it disables the process for a period of time.
- Timeout: Attempts to execute a process for a certain amount of time.
- Fallback: Executes a process in case of an error (similar to try-catch).
- Bulkhead: Limits the number of processes that can run in parallel.
- Cache: Avoids running a process if it has already been executed before.
- PolicyWrap: Combines several of the previous rules.
Each of these patterns has a multitude of parameters and options to adapt them to our needs. For more information about them, consult the abundant documentation available on the project’s website https://github.com/App-vNext/Polly . Polly is a library mainly designed for web processes. So you will see that many available examples focus on topics related to Http requests. However, Polly can be useful in a wide variety of situations and projects even if they are not at all related to communication.
To illustrate this, let’s take a look at some small examples of the use and operation of the library. To do this, simply create a console application and add Polly through the Nuget package manager.
Retry pattern
The Retry pattern simply attempts to repeat the action in case an exception occurs.
using Polly;
sing Polly.Timeout;
using System;
using System.Diagnostics;
namespace TestPolly
{
class Program
{
static void Main(string[] args)
{
testRetry();
}
static void testRetry()
{
try
{
Policy
.Handle<Exception>()
.Retry(3)
.Execute(RetrySampleMethod);
}
catch (Exception)
{
Console.WriteLine("Retry exceed");
}
Console.WriteLine("Finish");
Console.ReadLine();
}
static int counter = 0;
[DebuggerNonUserCode]
static void RetrySampleMethod()
{
Console.WriteLine(counter);
counter++;
throw new Exception();
}
}
}
As we can see, we have the ‘testRetry()’ function, which executes the ‘RetrySampleMethod()‘. In this function, which would simulate our real code, we have added an exception. In general, we would use a specific type of exception, but for this example, we are simply going to use the base class.
Now, in testRetry, we have used Polly to execute the code 4 times (1 original + 3 retries). When accumulating the 4 errors, it will throw an exception, which we have captured in a Try Catch block.
Fallback pattern
To avoid this Try Catch block and make it more in the “Polly way,” we can replace it with a combination with Fallback. Therefore, the previous code is equivalent to,
using Polly;
using Polly.Timeout;
using System;
using System.Diagnostics;
namespace TestPolly
{
class Program
{
static void Main(string[] args)
{
testRetry();
}
static void testRetry()
{
Policy
.Handle<Exception>()
.Fallback(_ => Console.WriteLine("Retry exceed"))
.Wrap(
Policy
.Handle<Exception>()
.Retry(3)
)
.Execute(RetrySampleMethod);
Console.WriteLine("Finish");
Console.ReadLine();
}
static int counter = 0;
[DebuggerNonUserCode]
static void RetrySampleMethod()
{
Console.WriteLine(counter);
counter++;
throw new Exception();
}
}
}
Timeout pattern
The Timeout pattern consists of stopping an action if the execution time is greater than a certain interval.
using Polly;
using Polly.Timeout;
using System;
using System.Diagnostics;
namespace TestLibraries
{
class Program
{
static void Main(string[] args)
{
testTimeout();
}
static void testTimeout()
{
Policy.Timeout(1, TimeoutStrategy.Pessimistic, onTimeout: (context, timespan, task) =>
{
Console.WriteLine("Timeout");
})
.ExecuteAndCapture(TimeoutSampleMethod);
Console.WriteLine("Finish");
Console.ReadLine();
}
[DebuggerNonUserCode]
static void TimeoutSampleMethod()
{
System.Threading.Thread.Sleep(2000);
}
}
}
In this case, our code is simulated by the ‘TimeoutSampleMethod()’ function, which, as we can see, only has a blocking wait of 2 seconds. In the ‘testTimeout()’ function, we use Polly to launch the previous function with a Timeout of 1 second. The pessimistic strategy is used for functions that do not implement CancelationToken.
Circuit-Breaker pattern
The Circuit-Breaker pattern stops requests to a system if it detects a series of errors close in time, and disables them for an interval.
using Polly;
using Polly.Timeout;
using System;
using System.Diagnostics;
namespace TestLibraries
{
class Program
{
static void Main(string[] args)
{
testCircuitBreak();
}
private static void testCircuitBreak()
{
var pol = Policy
.Handle<Exception>()
.CircuitBreaker(
exceptionsAllowedBeforeBreaking: 10,
onBreak: (exception, timespan) => { Console.WriteLine("Break"); },
onReset: () => { Console.WriteLine("Reset"); },
durationOfBreak: TimeSpan.FromSeconds(5)
);
while(true)
{
pol.ExecuteAndCapture(CircuitBreakSampleMethod);
}
}
[DebuggerNonUserCode]
static void CircuitBreakSampleMethod()
{
System.Threading.Thread.Sleep(100);
Console.WriteLine("Error");
throw new Exception();
}
}
}
Finally, you will see that in all the examples we have marked the function that throws the Exception with the [DebuggerNonUserCode]
attribute. This is necessary for debugging in Visual Studio, because otherwise it captures the exception we throw, even if it is handled by Polly. In case of being code from a library external to our code, this attribute would not be necessary.
It is very easy to implement patterns with the Polly library. A very useful tool that should be part of your favorite libraries collection.