Skip to main content

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.Polly
  • Microsoft.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"));