Search code examples
c#pollycircuit-breaker

Polly circuitbreaker is not being called


I'm trying to implement a circuitbreaker for the first time, but it just won't work. The fallback policy works but I can't seem to reach the circuitbreaker. I've tried this in different versions, including a retry policy for the circuitbreaker, but it doesn't seem to matter. I'm sure it's something basic I missed.

Here is a simplified version of the code for test purposes:

var timeoutPolicy = Policy
            .TimeoutAsync(
                _settings.TimeoutWhenCallingApi,
                TimeoutStrategy.Pessimistic
            );

        var circuitBreaker = Policy
            .Handle<TimeoutRejectedException>()
            .CircuitBreakerAsync(
                _settings.ConsecutiveExceptionsAllowedBeforeBreaking,
                _settings.DurationOfBreak
            )
            .WrapAsync(timeoutPolicy);

        policy = Policy
            .Handle<Exception>()
            .FallbackAsync(
                async cancellationToken => { Console.WriteLine("fallback triggered"); })
            .WrapAsync(circuitBreaker);

        await policy.ExecuteAsync(() => Task.Delay(-1));

Solution

  • The following code sample constructs the timeout, circuit-breaker and fallback policies essentially identical to the original code posted in your question.

    using Polly; 
    using System;
    using System.Threading.Tasks;
    
    public class Program
    {
        public static async void Main() {
    
        var timeoutPolicy = Policy
            .TimeoutAsync(
                TimeSpan.FromMilliseconds(10), // _settings.TimeoutWhenCallingApi,
                Polly.Timeout.TimeoutStrategy.Pessimistic
            );
    
        var circuitBreaker = Policy
            .Handle<Polly.Timeout.TimeoutRejectedException>()
            .CircuitBreakerAsync(
                1, // _settings.ConsecutiveExceptionsAllowedBeforeBreaking,
                TimeSpan.FromSeconds(30) // _settings.DurationOfBreak
            );
        var circuitBreakerWrappingTimeout = circuitBreaker
            .WrapAsync(timeoutPolicy);
    
        var policy = Policy
            .Handle<Exception>()
            .FallbackAsync(
                async cancellationToken => { Console.WriteLine("fallback triggered"); })
            .WrapAsync(circuitBreakerWrappingTimeout);
    
        Console.WriteLine("Circuit state before execution: " + circuitBreaker.CircuitState);
        
        await policy.ExecuteAsync(() => Task.Delay(-1));
        
        Console.WriteLine("Circuit state after execution: " + circuitBreaker.CircuitState);
        }
    }
    

    The code can be run with this dotnetfiddle sample: https://dotnetfiddle.net/m9O3cg

    (dotnetfiddle sample changed to a non-async main only because dotnetfiddle did not always await the async Main() method to complete, so the output was not always complete with async Main())

    The output is:

    Circuit state before execution: Closed
    fallback triggered
    Circuit state after execution: Open
    

    This demonstrates that the circuit-breaker policy is being reached/taking part in your execution.

    • The executed delegate is timed out by the timeout policy, which throws TimeoutRejectedException
    • The circuit-breaker policy catches that and counts the number of consecutive TimeoutRejectedExceptions it has experienced: 1. That is enough to break, so the circuit-breaker transitions to open state.
    • The circuit-breaker rethrows the TimeoutRejectedException (it is only a measuring-and-breaking device)
    • The fallback policy catches the TimeoutRejectedException and outputs the fallback.
    • The final line outputting circuit state demonstrates the circuit-breaker was affected by (so did take part in) the call.

    Another execution through policy within the 30-second durationOfBreak would fail with BrokenCircuitException, due to the circuit being open.