Polly Resilience Patterns
Implement resilience with retry, circuit breaker, and bulkhead patterns.
By EMEPublished: February 20, 2025
pollyresiliencecircuit breakerretryfault tolerance
A Simple Analogy
Polly is like insurance for your API calls. When something fails, Polly automatically retries, stops calling broken services, and isolates failures to prevent cascading problems.
Why Polly?
- Resilience: Auto-recover from transient failures
- Circuit breaker: Stop calling failing services
- Retry logic: With exponential backoff
- Bulkhead: Isolate resource consumption
- Fallback: Provide alternative responses
Retry Policy
var retryPolicy = Policy
.Handle<HttpRequestException>()
.Or<TimeoutException>()
.WaitAndRetryAsync(
retryCount: 3,
sleepDurationProvider: attempt =>
TimeSpan.FromSeconds(Math.Pow(2, attempt)), // 1s, 2s, 4s
onRetry: (outcome, timespan, retryCount, context) =>
{
logger.LogWarning(
$"Retry {retryCount} after {timespan.TotalSeconds}s. Reason: {outcome.Exception?.Message}");
});
var response = await retryPolicy.ExecuteAsync(async () =>
{
return await httpClient.GetAsync("https://api.example.com/data");
});
Circuit Breaker
var circuitBreakerPolicy = Policy
.Handle<HttpRequestException>()
.OrResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
.CircuitBreakerAsync(
handledEventsAllowedBeforeBreaking: 5,
durationOfBreak: TimeSpan.FromSeconds(30),
onBreak: (outcome, timespan) =>
{
logger.LogError($"Circuit breaker opened for {timespan.TotalSeconds}s");
},
onReset: () =>
{
logger.LogInformation("Circuit breaker reset");
});
try
{
var response = await circuitBreakerPolicy.ExecuteAsync(async () =>
{
return await httpClient.GetAsync("https://failing-api.com/data");
});
}
catch (BrokenCircuitException)
{
logger.LogError("Circuit breaker is open. Service is down.");
}
Bulkhead Isolation
var bulkheadPolicy = Policy.BulkheadAsync(
maxParallelization: 10,
maxQueuingActions: 20,
onBulkheadRejectedExecutionAsync: context =>
{
logger.LogWarning("Bulkhead queue is full. Request rejected.");
return Task.CompletedTask;
});
try
{
await bulkheadPolicy.ExecuteAsync(async () =>
{
await Task.Delay(1000); // Simulates work
});
}
catch (BulkheadRejectedException)
{
// Handle rejection
}
Combined Policies (Wrap)
// Retry -> Circuit Breaker -> Bulkhead
var policy = Policy.WrapAsync(
retryPolicy,
circuitBreakerPolicy,
bulkheadPolicy);
var response = await policy.ExecuteAsync(async () =>
{
return await httpClient.GetAsync("https://api.example.com/data");
});
Fallback Policy
var fallbackPolicy = Policy<HttpResponseMessage>
.Handle<HttpRequestException>()
.OrResult(r => !r.IsSuccessStatusCode)
.FallbackAsync(
fallbackAction: async () =>
{
logger.LogWarning("Using fallback response from cache");
var cached = await cache.GetAsync("api-response");
return new HttpResponseMessage(System.Net.HttpStatusCode.OK)
{
Content = new StringContent(cached)
};
},
onFallbackAsync: (outcome, context) =>
{
logger.LogError($"Fallback triggered: {outcome.Exception?.Message}");
return Task.CompletedTask;
});
var response = await fallbackPolicy.ExecuteAsync(async () =>
{
return await httpClient.GetAsync("https://api.example.com/data");
});
HttpClientFactory Integration
builder.Services.AddHttpClient<IPaymentClient, PaymentClient>()
.AddTransientHttpErrorPolicy()
.WaitAndRetryAsync(3, retryAttempt =>
TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
builder.Services.AddHttpClient<IOrderClient, OrderClient>()
.AddTransientHttpErrorPolicy()
.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30));
builder.Services.AddHttpClient<IUserClient, UserClient>()
.AddPolicyHandler(GetRetryPolicy())
.AddPolicyHandler(GetCircuitBreakerPolicy());
Advanced: Async Bulkhead
var asyncBulkheadPolicy = Policy.BulkheadAsync(
maxParallelization: 10,
maxQueuingActions: 20);
var database = new AsyncBulkheadPolicy();
var externalApi = new AsyncBulkheadPolicy();
// Keep database and API calls isolated
var dbPolicy = asyncBulkheadPolicy;
var apiPolicy = asyncBulkheadPolicy;
// Even if API is overloaded, database calls proceed
Best Practices
- Use defaults: Start with retry and circuit breaker
- Monitor policies: Track rejections and failures
- Set realistic timeouts: Avoid cascading waits
- Test failure scenarios: Verify fallback works
- Document policies: Explain why each exists
Related Concepts
- Hystrix (Java equivalent)
- Service mesh patterns
- Distributed tracing
- Health checks
Summary
Polly provides resilience patterns for distributed systems. Combine retry, circuit breaker, and bulkhead policies to build systems that gracefully handle failures and failures.