Polly doesn’t just handle failures; it actively prevents cascading failures by strategically allowing services to temporarily fail fast.

Let’s say you have a HttpClient making calls to a downstream service. Without Polly, if that service becomes unresponsive, your HttpClient calls will hang, consuming threads and potentially exhausting your application’s resources, which in turn makes your own service unresponsive.

Here’s a simple .NET Core HttpClient making a call to https://api.example.com/data:

var httpClient = new HttpClient();
var response = await httpClient.GetAsync("https://api.example.com/data");
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(content);

Now, let’s inject Polly to add resilience. We’ll use the Polly.Extensions.Http package, which integrates Polly directly with HttpClientFactory. First, add the NuGet package:

dotnet add package Polly.Extensions.Http

Then, configure your HttpClientFactory in Startup.cs (or Program.cs for .NET 6+):

// In Startup.cs or Program.cs
services.AddHttpClient("MyApiClient")
    .AddPolicyHandler(Policy.Handle<HttpRequestException>()
        .CircuitBreakerAsync(
            exceptionsAllowedBeforeBreaking: 2,
            durationOfBreak: TimeSpan.FromSeconds(30)
        ));

This configuration sets up a circuit breaker for any HttpClient named "MyApiClient".

  • exceptionsAllowedBeforeBreaking: 2: The circuit breaker will allow up to 2 consecutive HttpRequestException failures before it "breaks" (opens).
  • durationOfBreak: TimeSpan.FromSeconds(30): Once broken, the circuit will remain open for 30 seconds. During this time, any attempts to use this HttpClient will immediately throw an exception without even attempting the network request.

Now, when you inject and use IHttpClientFactory:

public class MyService
{
    private readonly HttpClient _httpClient;

    public MyService(IHttpClientFactory clientFactory)
    {
        _httpClient = clientFactory.CreateClient("MyApiClient");
    }

    public async Task<string> GetDataAsync()
    {
        var response = await _httpClient.GetAsync("https://api.example.com/data");
        response.EnsureSuccessStatusCode(); // This will throw HttpRequestException on non-success
        return await response.Content.ReadAsStringAsync();
    }
}

If api.example.com starts returning 500 errors or becomes unreachable, HttpRequestException will be thrown. After two consecutive failures, the circuit breaker will trip. The next call to GetDataAsync within those 30 seconds will bypass the network call entirely and throw an exception immediately. After 30 seconds, Polly will transition the circuit to a "half-open" state, allowing a single trial request. If that request succeeds, the circuit closes. If it fails, it breaks again for another 30 seconds.

The core problem Polly’s circuit breaker solves is preventing a healthy service from being overwhelmed by the cascading failures of an unhealthy dependency. When a dependency is down, repeatedly hammering it with requests is counterproductive. It wastes resources on your side and delays the dependency’s recovery. The circuit breaker acts as a safety valve, giving the dependency a chance to recover and preventing your service from burning itself out trying to reach it.

One of the most subtle but powerful aspects of Polly’s circuit breaker is its behavior in the "half-open" state. It’s not just a passive timer; it’s an active test. After the durationOfBreak elapses, Polly doesn’t just re-open the circuit. It allows one request to pass through. If that single request is successful, Polly assumes the dependency has recovered and closes the circuit, allowing normal traffic to resume. If that single request fails, Polly immediately re-opens the circuit and restarts the durationOfBreak timer. This "try-it-once" approach is crucial for gradual recovery and prevents a sudden flood of traffic from overwhelming a dependency that might only be partially recovered.

The next logical step is to combine circuit breakers with retry policies, so that failures before the circuit breaks are handled gracefully.

Want structured learning?

Take the full Circuit-breaker course →