When you define a Circuit Breaker policy then you can define what sort of exception(s) should be considered by the CB implementation. In other words you can list those exceptions that should be treated as failed execution and should be counted into the successive failure count.
You can define the list of exceptions with the combination of Handle<T>
and Or<T>
method calls.
Let's scrutinize this concept via a simple example:
var retry = Policy
.Handle<ArgumentException>()
.Or<NotSupportedException>()
.WaitAndRetry(5, _ => TimeSpan.FromSeconds(1),
onRetry: (exception, delay, context) => Console.WriteLine($"{"Retry",-10}{delay,-10:ss\\.fff}: {exception.GetType().Name}"));
var circuitBreaker = Policy
.Handle<ArgumentException>()
.CircuitBreaker(2, TimeSpan.FromSeconds(1),
onBreak: (ex, @break) => Console.WriteLine($"{"Break",-10}{@break,-10:ss\\.fff}: {ex.GetType().Name}"),
onReset: () => Console.WriteLine($"{"Reset",-10}"),
onHalfOpen: () => Console.WriteLine($"{"HalfOpen",-10}"));
- The circuit breaker policy considers all
ArgumentException
s (including ArgumentNullException
and ArgumentOutOfRangeException
) as handled exception.
- This means that if the called delegate throws one of these three exceptions then it will increase the successive failure count and if the threshold is reached then it will break.
- The retry policy is triggered in case of
ArgumentException
and in case of NotSupportedException
as well.
- If either of these is thrown then it will sleep for a second and then it tries to re-execute the same delegate.
So, from the Circuit Breaker perspective if a NotSupportedException
is thrown than it will not be considered >> hence the name unhandled.
This is how our sample method is implemented which will either throw an ArgumentException
or a NotSupportedException
:
private static int count = 0;
private const int threshold = 3;
static void SampleCall()
{
count++;
if (count >= threshold) throw new NotSupportedException();
throw new ArgumentException("Nothing");
}
The usage of the policies:
var strategy = Policy.Wrap(retry, circuitBreaker);
try
{
strategy.Execute(SampleCall);
Console.WriteLine("Succeeded");
}
catch (NotSupportedException)
{
Console.WriteLine("Failed");
}
Output when threshold
is set to 3
Retry 01.000 : ArgumentException
Break 01.000 : ArgumentException
Retry 01.000 : ArgumentException
HalfOpen
Retry 01.000 : NotSupportedException
Retry 01.000 : NotSupportedException
Retry 01.000 : NotSupportedException
Failed
After the CB has been transferred itself into the HalfOpen
state then the SampleCall
throws only NotSupportedException
s. This is not handled by the CB that's why it remains in the HalfOpen
state.
Output when threshold
is set to 2
Retry 01.000 : ArgumentException
Retry 01.000 : NotSupportedException
Retry 01.000 : NotSupportedException
Retry 01.000 : NotSupportedException
Retry 01.000 : NotSupportedException
Failed
The CB did not break because there was no two successive ArgumentException
. But the retry did trigger because it also handles NotSupportedException
.
Output when threshold
is set to 4
Retry 01.000 : ArgumentException
Break 01.000 : ArgumentException
Retry 01.000 : ArgumentException
HalfOpen
Break 01.000 : ArgumentException
Retry 01.000 : ArgumentException
HalfOpen
Retry 01.000 : NotSupportedException
Retry 01.000 : NotSupportedException
Failed
Because the SampleCall
did throw ArgumentException
when the CB was in the HalfOpen
state that's why CB considered that as handled exception and transferred itself from HalfOpen
to Open
.