Search code examples
c#pollycircuit-breakerpolicywrappolicy-registry

C# and Polly - How to Unwrap IAsyncPolicy to get to the ICircuitBreakerPolicy


I am setting up Polly policies with some variant of:

Policy
    .Handle<Exception>()
    .AdvancedCircuitBreakerAsync(failureThreshold, samplingDuration, minimumThroughput, durationOfBreak         )
    .WrapAsync(Policy.TimeoutAsync(timeout, TimeoutStrategy.Pessimistic));

I am adding them to an IReadOnlyPolicyRegistry<string> and services dependent on Polly use the registry to get the policy.

Now I'm trying to add all circuit breaker statuses to the health check. I am trying to do this by taking the PolicyRegistry and iterating through the policies. However, the types in the registry are IAsyncPolicy, IAsyncPolicy<HttpResponseMessage>, etc.

Using the debugger, I can see that the Outer property is an AsyncCircuitBreakerPolicy, but that Outer property is not public so I can't use it and policy as AsyncCircuitBreakerPolicy returns null.

Does anyone know how to 'unwrap' the IAsyncPolicy to get at the AsyncCircuitBreakerPolicy?

Is there an out of the box solution for grabbing all circuit breakers registered with Polly?

Do I need to keep my own internal list of circuit breakers and add to it when creating the Polly policies?

Note: I want to add a health check entry for each circuit breaker even if it is closed - just so I know things were registered correctly.


Solution

  • In Polly almost every policy implements a bunch of interfaces:

    • AsyncPolicyWrap implements IAsyncPolicy, IsPolicy and IPolicyWrap
      • We can use the last one to access the Outer / Inner
    • AsyncCircuitBreakerPolicy implements IAsyncPolicy, IsPolicy and ICircuitBreakerPolicy
      • We can use the last one to access the CircuitState

    The IReadOnlyPolicyRegistry<string> is an IEnumerable<KeyValuePair<string, IsPolicy>> so, you can iterate through it.

    With a foreach the cb's state retrieval logic looks like this:

    foreach (var (key, policy) in registry)
    {
        if (policy is IPolicyWrap wrap)
        {
            if (wrap.Outer is ICircuitBreakerPolicy cb)
            {
                var state = cb.CircuitState;
            }
        }
    }
    

    The IPolicyWrap defines a function called GetPolicy<T> with which we can get rid of the inner if block

    foreach (var (key, policy) in registry)
    {
        if (policy is IPolicyWrap wrap)
        {
            var cb = wrap.GetPolicy<ICircuitBreakerPolicy>();
            var state = cb?.CircuitState;
        }
    }
    

    We can get rid of the other if block with some Linq

    foreach (var policy in registry.Select(kp => kp.Value).Where(p => p is IPolicyWrap).Cast<IPolicyWrap>())
    {
        var cb = policy.GetPolicy<ICircuitBreakerPolicy>();
        var state = cb?.CircuitState;  
    }
    

    The GetPolicy<T> or GetPolicies<T> have that advantage that it does not matter whether T is the Outer or the Inner or the Inner's Outer ... it will find T or Ts anywhere in the policy chain.