Circuit Breaker Pattern
The circuit breaker pattern prevents an application from repeatedly trying to execute an operation likely to fail, allowing the system to recover.
Why Circuit Breakers?
Without circuit breaker, a failing service causes:
1Request 1 → Service (fails after 30s timeout)2Request 2 → Service (fails after 30s timeout)3Request 3 → Service (fails after 30s timeout)4...5All threads blocked waiting for a dead service!
With circuit breaker:
1Request 1 → Service (fails)2Request 2 → Service (fails)3Request 3 → Service (fails)4Circuit OPENS!5Request 4 → Fails immediately (circuit open)6Request 5 → Fails immediately (circuit open)7...8[After break duration]9Request N → Service (succeeds)10Circuit CLOSES
Circuit States
1 ┌──────────────────────────────────────┐2 │ │3 ▼ │4┌─────────┐ failures exceed ┌─────────┐ │5│ CLOSED │─────threshold─────▶│ OPEN │ │6└─────────┘ └────┬────┘ │7 ▲ │ │8 │ break │9 │ duration │10 │ expires │11 │ │ │12 │ success ┌────▼────┐ │13 └────────────────────────│HALF-OPEN│───┘14 failure └─────────┘
- Closed: Normal operation, requests flow through
- Open: Requests fail immediately without trying
- Half-Open: Test requests to see if service recovered
Basic Circuit Breaker
csharp1var circuitBreaker = Policy2 .Handle<HttpRequestException>()3 .CircuitBreakerAsync(4 exceptionsAllowedBeforeBreaking: 3, // Open after 3 failures5 durationOfBreak: TimeSpan.FromSeconds(30) // Stay open for 30s6 );
Circuit Breaker with Callback
csharp1var circuitBreaker = Policy2 .Handle<HttpRequestException>()3 .CircuitBreakerAsync(4 exceptionsAllowedBeforeBreaking: 3,5 durationOfBreak: TimeSpan.FromSeconds(30),6 onBreak: (exception, duration) =>7 {8 Console.WriteLine($"Circuit OPEN for {duration.TotalSeconds}s: {exception.Message}");9 },10 onReset: () =>11 {12 Console.WriteLine("Circuit CLOSED - service recovered");13 },14 onHalfOpen: () =>15 {16 Console.WriteLine("Circuit HALF-OPEN - testing...");17 });
Advanced Circuit Breaker
Based on failure rate over a time window:
csharp1var advancedBreaker = Policy2 .Handle<HttpRequestException>()3 .AdvancedCircuitBreakerAsync(4 failureThreshold: 0.5, // 50% failure rate5 samplingDuration: TimeSpan.FromSeconds(10), // Over 10 seconds6 minimumThroughput: 8, // Need at least 8 requests to evaluate7 durationOfBreak: TimeSpan.FromSeconds(30)8 );
Checking Circuit State
csharp1// Check current state2CircuitState state = circuitBreaker.CircuitState;34switch (state)5{6 case CircuitState.Closed:7 Console.WriteLine("Operating normally");8 break;9 case CircuitState.Open:10 Console.WriteLine("Circuit is open - failing fast");11 break;12 case CircuitState.HalfOpen:13 Console.WriteLine("Testing if service recovered");14 break;15 case CircuitState.Isolated:16 Console.WriteLine("Manually isolated");17 break;18}
Manual Circuit Control
csharp1// Manually break the circuit2circuitBreaker.Isolate();3Console.WriteLine("Manually opened circuit for maintenance");45// Manually reset6circuitBreaker.Reset();7Console.WriteLine("Manually closed circuit");
Handling Open Circuit
csharp1try2{3 await circuitBreaker.ExecuteAsync(() => httpClient.GetAsync(url));4}5catch (BrokenCircuitException ex)6{7 // Circuit is open - service is known to be unavailable8 Console.WriteLine("Service unavailable, try again later");910 // Return cached data or default11 return GetCachedResponse();12}13catch (HttpRequestException ex)14{15 // Service responded but with error16 Console.WriteLine($"Request failed: {ex.Message}");17 throw;18}
Combining Retry and Circuit Breaker
Important: Wrap retry INSIDE circuit breaker:
csharp1// Retry policy2var retry = Policy3 .Handle<HttpRequestException>()4 .WaitAndRetryAsync(3, _ => TimeSpan.FromSeconds(1));56// Circuit breaker (outer)7var breaker = Policy8 .Handle<HttpRequestException>()9 .CircuitBreakerAsync(5, TimeSpan.FromSeconds(30));1011// Combine: breaker wraps retry12var policy = Policy.WrapAsync(breaker, retry);1314// Usage15await policy.ExecuteAsync(() => httpClient.GetAsync(url));
Execution flow:
1breaker.Execute(2 retry.Execute( // Retries 3 times3 httpClient.GetAsync(url)4 )5)6// If all retries fail, that counts as ONE failure for circuit breaker
Per-Service Circuit Breakers
Use separate breakers for each external service:
csharp1class ServiceClientFactory2{3 private readonly Dictionary<string, IAsyncPolicy> _breakers = new();45 public IAsyncPolicy GetCircuitBreaker(string serviceName)6 {7 if (!_breakers.TryGetValue(serviceName, out var breaker))8 {9 breaker = Policy10 .Handle<HttpRequestException>()11 .CircuitBreakerAsync(3, TimeSpan.FromSeconds(30),12 onBreak: (ex, ts) => Console.WriteLine($"{serviceName} circuit OPEN"),13 onReset: () => Console.WriteLine($"{serviceName} circuit CLOSED"));1415 _breakers[serviceName] = breaker;16 }1718 return breaker;19 }20}2122// Usage23var paymentBreaker = factory.GetCircuitBreaker("PaymentService");24var inventoryBreaker = factory.GetCircuitBreaker("InventoryService");2526// Payment service down doesn't affect inventory calls
Complete Example
csharp1class ResilientApiClient2{3 private readonly HttpClient _client = new();4 private readonly IAsyncPolicy<HttpResponseMessage> _policy;56 public ResilientApiClient()7 {8 // Retry with exponential backoff9 var retry = Policy<HttpResponseMessage>10 .Handle<HttpRequestException>()11 .OrResult(r => r.StatusCode >= HttpStatusCode.InternalServerError)12 .WaitAndRetryAsync(3, attempt =>13 TimeSpan.FromSeconds(Math.Pow(2, attempt)));1415 // Circuit breaker16 var breaker = Policy<HttpResponseMessage>17 .Handle<HttpRequestException>()18 .OrResult(r => r.StatusCode >= HttpStatusCode.InternalServerError)19 .CircuitBreakerAsync(5, TimeSpan.FromSeconds(30),20 onBreak: (result, duration) =>21 Console.WriteLine($"Circuit OPEN: {result.Exception?.Message}"),22 onReset: () =>23 Console.WriteLine("Circuit CLOSED"));2425 // Combine: circuit breaker wraps retry26 _policy = Policy.WrapAsync(breaker, retry);27 }2829 public async Task<string> GetAsync(string url)30 {31 try32 {33 var response = await _policy.ExecuteAsync(() => _client.GetAsync(url));34 return await response.Content.ReadAsStringAsync();35 }36 catch (BrokenCircuitException)37 {38 return "Service temporarily unavailable";39 }40 }41}
Key Takeaways
- Circuit breaker prevents cascading failures
- Three states: Closed (normal), Open (failing fast), Half-Open (testing)
- Configure failures threshold and break duration
- Use AdvancedCircuitBreaker for rate-based breaking
- Wrap retry inside circuit breaker
- Use separate breakers for different services
- Handle BrokenCircuitException for graceful degradation