Cheatsheet: Retry Pattern em Zig

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

Continue aprendendo Zig

Explore mais tutoriais e artigos em português para dominar a linguagem Zig.