Cheatsheet: Circuit Breaker em Zig

Circuit Breaker em Zig

O padrão Circuit Breaker protege um sistema contra falhas em cascata, funcionando como um disjuntor elétrico. Quando um serviço externo apresenta falhas repetidas, o circuit breaker “abre”, impedindo novas tentativas e falhando rapidamente. Após um período de espera, permite tentativas limitadas para verificar se o serviço se recuperou.

Quando Usar

  • Chamadas a serviços externos (APIs, bancos de dados)
  • Operações de rede que podem falhar
  • Proteção contra sobrecarga de serviços em recuperação
  • Microservices e sistemas distribuídos

Implementação

const std = @import("std");

fn CircuitBreaker(comptime T: type, comptime E: type) type {
    return struct {
        const Self = @This();

        const Estado = enum {
            fechado,    // operação normal
            aberto,     // falhas demais, bloqueando chamadas
            semi_aberto, // testando se serviço se recuperou
        };

        estado: Estado = .fechado,
        falhas_consecutivas: u32 = 0,
        limite_falhas: u32,
        tempo_timeout_ns: u64,
        ultimo_falha_ns: i128 = 0,
        sucessos_semi_aberto: u32 = 0,
        sucessos_necessarios: u32 = 2,

        pub fn init(limite_falhas: u32, timeout_segundos: u32) Self {
            return .{
                .limite_falhas = limite_falhas,
                .tempo_timeout_ns = @as(u64, timeout_segundos) * std.time.ns_per_s,
            };
        }

        pub fn executar(self: *Self, operacao: *const fn () E!T) E!T {
            switch (self.estado) {
                .aberto => {
                    const agora = std.time.nanoTimestamp();
                    if (agora - self.ultimo_falha_ns > self.tempo_timeout_ns) {
                        // Timeout expirou — tentar semi-aberto
                        self.estado = .semi_aberto;
                        self.sucessos_semi_aberto = 0;
                    } else {
                        return error.CircuitoBloqueado;
                    }
                },
                .fechado, .semi_aberto => {},
            }

            const resultado = operacao() catch |err| {
                self.registrarFalha();
                return err;
            };

            self.registrarSucesso();
            return resultado;
        }

        fn registrarFalha(self: *Self) void {
            self.falhas_consecutivas += 1;
            self.ultimo_falha_ns = std.time.nanoTimestamp();

            if (self.estado == .semi_aberto or
                self.falhas_consecutivas >= self.limite_falhas)
            {
                self.estado = .aberto;
                std.debug.print("[CIRCUIT BREAKER] Circuito ABERTO após {d} falhas\n", .{
                    self.falhas_consecutivas,
                });
            }
        }

        fn registrarSucesso(self: *Self) void {
            switch (self.estado) {
                .semi_aberto => {
                    self.sucessos_semi_aberto += 1;
                    if (self.sucessos_semi_aberto >= self.sucessos_necessarios) {
                        self.estado = .fechado;
                        self.falhas_consecutivas = 0;
                        std.debug.print("[CIRCUIT BREAKER] Circuito FECHADO — serviço recuperado\n", .{});
                    }
                },
                .fechado => {
                    self.falhas_consecutivas = 0;
                },
                .aberto => {},
            }
        }

        pub fn estadoAtual(self: *const Self) []const u8 {
            return switch (self.estado) {
                .fechado => "FECHADO (operando normalmente)",
                .aberto => "ABERTO (bloqueando chamadas)",
                .semi_aberto => "SEMI-ABERTO (testando recuperação)",
            };
        }
    };
}

Uso Prático

const CB = CircuitBreaker([]const u8, anyerror);

var tentativas: u32 = 0;

fn chamarServico() ![]const u8 {
    tentativas += 1;
    if (tentativas < 8) return error.ServicoIndisponivel;
    return "resposta ok";
}

pub fn main() void {
    var cb = CB.init(3, 5); // 3 falhas para abrir, 5s timeout

    for (0..10) |i| {
        if (cb.executar(chamarServico)) |resp| {
            std.debug.print("Tentativa {d}: {s}\n", .{ i, resp });
        } else |err| {
            std.debug.print("Tentativa {d}: erro {}\n", .{ i, err });
        }
        std.debug.print("  Estado: {s}\n", .{cb.estadoAtual()});
    }
}

Quando Evitar

  • Operações locais que não falham por indisponibilidade de serviço
  • Quando falhas são esperadas e tratadas individualmente
  • Sistemas simples sem dependências externas

Veja Também

Continue aprendendo Zig

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