Retry Pattern em Zig
O padrão Retry automatiza a repetição de operações que falharam, com estratégias inteligentes de espera (backoff) para evitar sobrecarga. Em Zig, implementamos esse padrão com generics, comptime e controle explícito de timing.
Quando Usar
- Chamadas de rede que podem falhar temporariamente
- Acesso a recursos compartilhados com contenção
- Operações de I/O em dispositivos que podem estar ocupados
- Interação com APIs rate-limited
Implementação com Backoff Exponencial
const std = @import("std");
fn RetryConfig(comptime T: type, comptime E: type) type {
return struct {
const Self = @This();
max_tentativas: u32 = 3,
delay_inicial_ms: u64 = 100,
delay_maximo_ms: u64 = 30_000,
multiplicador: f64 = 2.0,
com_jitter: bool = true,
pub fn executar(self: Self, operacao: *const fn () E!T) E!T {
var tentativa: u32 = 0;
var delay_ms = self.delay_inicial_ms;
while (tentativa < self.max_tentativas) : (tentativa += 1) {
if (operacao()) |resultado| {
if (tentativa > 0) {
std.debug.print("[RETRY] Sucesso na tentativa {d}\n", .{tentativa + 1});
}
return resultado;
} else |err| {
if (tentativa + 1 >= self.max_tentativas) {
std.debug.print("[RETRY] Falha final após {d} tentativas\n", .{tentativa + 1});
return err;
}
std.debug.print("[RETRY] Tentativa {d} falhou, esperando {d}ms\n", .{
tentativa + 1, delay_ms,
});
// Esperar com backoff
var delay_real = delay_ms;
if (self.com_jitter) {
// Adicionar jitter aleatório (0-50% do delay)
var prng = std.Random.DefaultPrng.init(@intCast(std.time.nanoTimestamp()));
const jitter = prng.random().intRangeAtMost(u64, 0, delay_ms / 2);
delay_real += jitter;
}
std.time.sleep(delay_real * std.time.ns_per_ms);
// Aumentar delay para próxima tentativa
delay_ms = @min(
@as(u64, @intFromFloat(@as(f64, @floatFromInt(delay_ms)) * self.multiplicador)),
self.delay_maximo_ms,
);
}
}
unreachable;
}
};
}
// Uso
var contador_falhas: u32 = 0;
fn operacaoInstavel() ![]const u8 {
contador_falhas += 1;
if (contador_falhas < 3) return error.TemporariamenteIndisponivel;
return "dados recebidos";
}
pub fn main() !void {
const config = RetryConfig([]const u8, anyerror){
.max_tentativas = 5,
.delay_inicial_ms = 100,
.multiplicador = 2.0,
};
const resultado = try config.executar(operacaoInstavel);
std.debug.print("Resultado: {s}\n", .{resultado});
}
Retry com Filtro de Erros
const std = @import("std");
fn retrySeRetentavel(
comptime T: type,
operacao: anytype,
args: anytype,
max_tentativas: u32,
) !T {
var tentativa: u32 = 0;
while (tentativa < max_tentativas) : (tentativa += 1) {
if (@call(.auto, operacao, args)) |resultado| {
return resultado;
} else |err| {
// Só retentar erros temporários
switch (err) {
error.Timeout,
error.ConexaoRecusada,
error.ServicoIndisponivel,
=> {
if (tentativa + 1 < max_tentativas) {
const delay = std.math.shl(u64, 100, @intCast(tentativa));
std.time.sleep(delay * std.time.ns_per_ms);
continue;
}
},
// Erros permanentes — não retentar
else => return err,
}
return err;
}
}
unreachable;
}
Quando Evitar
- Erros que não são temporários (autenticação, permissão, dados inválidos)
- Operações não idempotentes (pagamentos, envio de email)
- Quando o serviço está claramente offline (use Circuit Breaker)
- Loops infinitos de retry sem backoff
Veja Também
- Circuit Breaker — Parar de retentar quando há muitas falhas
- Error Handling — Tratamento de erros em Zig
- Strategy — Diferentes estratégias de backoff
- Concorrência — Retry com timeout
- FAQ Produção — Resiliência em sistemas reais