Polly no .NET — Guia Completo de Resiliência | CSharp
- Polly no .NET — Guia Completo de Resiliência | CSharp
O que é o Polly
Polly é uma biblioteca de resiliência para .NET que permite implementar padrões como:
- Retry
- Circuit Breaker
- Timeout
- Fallback
- Bulkhead Isolation
- Hedging (na versão moderna via Polly.Core)
- Rate Limiting (integrado ao ecossistema moderno)
Ela é amplamente utilizada com HttpClient, especialmente via:
Microsoft.Extensions.Http.PollyMicrosoft.Extensions.Http.Resilience(.NET 8+ / Aspire)
Conceitos Fundamentais
Polly trabalha com o conceito de Policy.
Uma Policy define:
- O que deve ser tratado (Exception, Result, ambos)
- O que fazer quando ocorrer
- Quantas vezes tentar
- Estratégia de espera
- Comportamento sob falha
Exemplo básico:
var retryPolicy = Policy
.Handle<HttpRequestException>()
.Retry(3);
Como o Retry Funciona Internamente
Quando você executa:
await retryPolicy.ExecuteAsync(() => client.GetAsync(url));
Fluxo:
- Polly executa o delegate
- Se lançar exceção tratável → intercepta
- Aplica estratégia configurada
- Executa novamente o delegate
- Repete até atingir limite
Importante:
- Polly NÃO clona objetos automaticamente
- Ele apenas reexecuta o delegate
- Se o delegate reutiliza estado mutável → pode quebrar
Retry
Retry Simples
var policy = Policy
.Handle<HttpRequestException>()
.Retry(3);
Retry com Callback
var policy = Policy
.Handle<HttpRequestException>()
.Retry(3, (exception, retryCount) =>
{
Console.WriteLine($"Tentativa {retryCount}");
});
Retry Async
var policy = Policy
.Handle<HttpRequestException>()
.RetryAsync(3);
Wait and Retry
Permite atrasar entre tentativas.
var policy = Policy
.Handle<HttpRequestException>()
.WaitAndRetry(3, retryAttempt =>
TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
Isso gera backoff exponencial.
Wait and Retry com Jitter (recomendado)
Evita avalanche em sistemas distribuídos.
var jitterer = new Random();
var policy = Policy
.Handle<HttpRequestException>()
.WaitAndRetry(5, attempt =>
TimeSpan.FromMilliseconds(
Math.Pow(2, attempt) * 100 +
jitterer.Next(0, 100)));
Retry Baseado em Resultado
Não apenas exceção, mas também status code.
var policy = Policy
.HandleResult<HttpResponseMessage>(r =>
!r.IsSuccessStatusCode)
.RetryAsync(3);
Circuit Breaker
Evita continuar tentando quando o sistema está falhando consistentemente.
var breaker = Policy
.Handle<HttpRequestException>()
.CircuitBreaker(5, TimeSpan.FromSeconds(30));
Estados:
- Closed
- Open
- Half-Open
Fluxo:
- Após 5 falhas → abre circuito
- Durante 30 segundos → bloqueia chamadas
- Após período → testa novamente
Advanced Circuit Breaker
Baseado em taxa de falhas.
var breaker = Policy
.Handle<HttpRequestException>()
.AdvancedCircuitBreaker(
failureThreshold: 0.5,
samplingDuration: TimeSpan.FromSeconds(30),
minimumThroughput: 10,
durationOfBreak: TimeSpan.FromSeconds(15));
Timeout
Interrompe execução após tempo limite.
var timeout = Policy
.TimeoutAsync(TimeSpan.FromSeconds(3));
Pode ser:
- Pessimistic (cancela thread)
- Optimistic (usa CancellationToken)
Fallback
Define comportamento alternativo.
var fallback = Policy
.Handle<HttpRequestException>()
.FallbackAsync(
fallbackAction: () =>
Task.FromResult("Resposta alternativa"),
onFallbackAsync: ex =>
{
Console.WriteLine("Fallback acionado");
return Task.CompletedTask;
});
Bulkhead Isolation
Limita concorrência.
var bulkhead = Policy
.BulkheadAsync(
maxParallelization: 5,
maxQueuingActions: 10);
Evita exaustão de recursos.
Policy Wrap
Permite combinar políticas.
var policyWrap = Policy.WrapAsync(
retryPolicy,
circuitBreaker,
timeoutPolicy);
Ordem importa:
- Outer policy executa primeiro
- Inner policy executa por último
Integração com HttpClient
Via Microsoft.Extensions.Http.Polly:
services.AddHttpClient("api")
.AddPolicyHandler(retryPolicy);
Cada request passa pelo handler.
Polly no .NET 8+ (Resilience Pipeline)
No .NET moderno, Polly foi reestruturado em:
- Polly.Core
- ResiliencePipeline
Exemplo:
builder.AddRetry(new RetryStrategyOptions
{
MaxRetryAttempts = 3
});
Usado pelo Aspire.
Como o Retry Refaz a Request
Existem dois cenários:
Polly Clássico
- Reexecuta delegate
- Não clona HttpRequestMessage
- Se request reutilizar Stream → pode falhar
ResilienceHandler (.NET 8+ / Aspire)
- Clona HttpRequestMessage
- Clona HttpContent
- Se Stream não for replayable → lança exceção
Cuidados Importantes
Não reutilizar Stream
StreamContent content = new StreamContent(stream);
Se retry acontecer, stream pode estar consumido.
Solução:
- Usar
Func<Stream> - Ou ByteArrayContent
Não reutilizar HttpRequestMessage
Errado:
var request = new HttpRequestMessage(...);
await retry.ExecuteAsync(() => client.SendAsync(request));
Correto:
await retry.ExecuteAsync(() =>
{
var request = new HttpRequestMessage(...);
return client.SendAsync(request);
});
Evitar Retry Infinito
Sempre definir:
- Limite de tentativas
- Backoff
- Jitter
Não usar Retry para erros lógicos
Não deve tratar:
- 400 BadRequest
- 401 Unauthorized
- 403 Forbidden
Retry é para falhas transitórias.
Cuidado com Idempotência
Seguro:
- GET
- PUT (idempotente)
- DELETE (idempotente)
Perigoso:
- POST com efeitos colaterais
Evitar Retry em Operações Longas
Pode gerar duplicidade.
Monitorar métricas
Sempre logar:
- Tentativas
- Abertura de circuito
- Fallback acionado
Estratégias Recomendadas em Produção
- Retry com backoff exponencial + jitter
- Circuit breaker
- Timeout menor que timeout de rede
- Bulkhead em serviços críticos
- Logging estruturado
Exemplo Profissional Completo
var retry = Policy
.Handle<HttpRequestException>()
.WaitAndRetryAsync(5, attempt =>
TimeSpan.FromMilliseconds(
Math.Pow(2, attempt) * 200));
var breaker = Policy
.Handle<HttpRequestException>()
.CircuitBreakerAsync(3, TimeSpan.FromSeconds(30));
var timeout = Policy.TimeoutAsync<HttpResponseMessage>(5);
var policy = Policy.WrapAsync(retry, breaker, timeout);
await policy.ExecuteAsync(() =>
client.GetAsync("https://api.exemplo.com"));