The short answer is yes they are treated separately.
In order to understand how the system works we have to look under the hood. Let's start our journey at the AddPolicyHandler
.
Disclaimer: I've slightly edited the code snippets for the sake of brevity.
public static IHttpClientBuilder AddPolicyHandler(this IHttpClientBuilder builder, IAsyncPolicy<HttpResponseMessage> policy)
{
if (builder == null) throw new ArgumentNullException(nameof(builder));
if (policy == null) throw new ArgumentNullException(nameof(policy));
builder.AddHttpMessageHandler(() => new PolicyHttpMessageHandler(policy));
return builder;
}
This method is defined inside the PollyHttpClientBuilderExtensions
class and it provides extension methods for the IHttpClientBuilder
.
As you can see it does nothing else just registers yet another HttpMessageHandler
into the chain.
Now, let's see how does this special handler look like
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request == null) throw new ArgumentNullException(nameof(request));
// Guarantee the existence of a context for every policy execution,
// but only create a new one if needed.
// This allows later handlers to flow state if desired.
var cleanUpContext = false;
var context = request.GetPolicyExecutionContext();
if (context == null)
{
context = new Context();
request.SetPolicyExecutionContext(context);
cleanUpContext = true;
}
HttpResponseMessage response;
try
{
var policy = _policy ?? SelectPolicy(request);
response = await policy.ExecuteAsync((c, ct) => SendCoreAsync(request, c, ct), context, cancellationToken).ConfigureAwait(false);
}
finally
{
if (cleanUpContext)
request.SetPolicyExecutionContext(null);
}
return response;
}
This method is defined inside the PolicyHttpMessageHandler
.
As you can see nothing extraordinary happens here
- We either retrieve or create a new Context
- We either retrieve the policy from registry or use the provided one
- We execute the policy which decorates the
SendCoreAsync
So, where does the magic happen? Let's jump to the documentation comment of this class
All policies provided by Polly are designed to be efficient when used in a long-lived way. Certain policies such as the
Bulkhead and Circuit-Breaker maintain state and should be scoped across calls you wish to share the Bulkhead or Circuit-Breaker state.
Take care to ensure the correct lifetimes when using policies and message handlers together in custom scenarios. The extension
methods provided by PollyHttpClientBuilderExtensions
are designed to assign a long lifetime to policies
and ensure that they can be used when the handler rotation feature is active.
To understand how does Retry differ from Circuit Breaker lets look at their Engine
s' Implementation
signature
RetryEngine
internal static class RetryEngine
{
internal static TResult Implementation<TResult>(
Func<Context, CancellationToken, TResult> action,
Context context,
CancellationToken cancellationToken,
ExceptionPredicates shouldRetryExceptionPredicates,
ResultPredicates<TResult> shouldRetryResultPredicates,
Action<DelegateResult<TResult>, TimeSpan, int, Context> onRetry,
int permittedRetryCount = Int32.MaxValue,
IEnumerable<TimeSpan> sleepDurationsEnumerable = null,
Func<int, DelegateResult<TResult>, Context, TimeSpan> sleepDurationProvider = null)
{
...
}
}
CircuitBreakerEngine
internal class CircuitBreakerEngine
{
internal static TResult Implementation<TResult>(
Func<Context, CancellationToken, TResult> action,
Context context,
CancellationToken cancellationToken,
ExceptionPredicates shouldHandleExceptionPredicates,
ResultPredicates<TResult> shouldHandleResultPredicates,
ICircuitController<TResult> breakerController)
{
...
}
}
What you have to spot here is the ICircuitController
. The CircuitStateController
base class stores the state information. One of its derived class is shared between the different policy executions
internal readonly ICircuitController<EmptyStruct> _breakerController;
...
CircuitBreakerEngine.Implementation(
action,
context,
cancellationToken,
ExceptionPredicates,
ResultPredicates,
_breakerController);
I hope this clarify things.