Louise Ahokas
Louise Ahokas

Reputation: 55

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));

Upvotes: 3

Views: 2590

Answers (1)

mountain traveller
mountain traveller

Reputation: 8156

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.

Upvotes: 4

Related Questions