Arrea.CircuitBreaker (Arrea v1.0.0)

Copy Markdown View Source

Circuit Breaker para protección del sistema.

Implementa el patrón Circuit Breaker con tres estados:

  • :closed — Operación normal; las llamadas se ejecutan directamente.
  • :open — Las llamadas se bloquean inmediatamente tras exceder el umbral de fallos consecutivos.
  • :half_open — Estado de prueba tras el timeout de recuperación. Se permite una llamada para verificar si el sistema se ha recuperado.

Decisión atómica

La decisión de permitir o bloquear la ejecución se toma de forma atómica dentro del GenServer (get_state_and_check), eliminando la race condition entre leer el estado y ejecutar la función.

Cierre desde half_open

En estado :half_open se requieren max(1, threshold / 2) éxitos consecutivos para cerrar el circuito, mitigando el riesgo de cierre prematuro ante llamadas concurrentes.

Un fallo en :half_open resetea el contador de éxitos acumulados al volver a :open, garantizando que el próximo intento de recuperación parta desde cero.

Tiempo de timeout

Se usa System.monotonic_time/1 para calcular el intervalo desde el último fallo, inmune a ajustes de reloj del sistema (NTP, cambios manuales, etc.).

Registro

Cada breaker se registra via Registry con nombre único bajo Arrea.CircuitBreaker.Registry.

Summary

Functions

Ejecuta una función protegida por el circuit breaker.

Returns a specification to start this module under a supervisor.

Notifica un fallo de ejecución al circuit breaker.

Obtiene el estado actual del circuit breaker (:closed, :open, :half_open).

Inicia un circuit breaker con nombre único (requerido en opts).

Notifica una ejecución exitosa al circuit breaker.

Types

state()

@type state() :: :closed | :open | :half_open

Functions

call(name, fun, opts \\ [])

@spec call(atom(), (-> term()), keyword()) :: {:ok, term()} | {:error, term()}

Ejecuta una función protegida por el circuit breaker.

  • Si el circuito está cerrado o en half_open: ejecuta la función.
  • Si el circuito está abierto y el timeout no ha expirado: retorna {:error, :circuit_open} sin ejecutar nada.
  • Si el breaker no está registrado: ejecuta la función directamente (comportamiento equivalente a circuito cerrado).

Ejemplos

iex> CircuitBreaker.call(:my_breaker, fn -> :ok end)
{:ok, :ok}

iex> CircuitBreaker.call(:my_breaker, fn -> raise "boom" end)
{:error, :execution_failed}

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

failure(name)

@spec failure(atom()) :: :ok

Notifica un fallo de ejecución al circuit breaker.

get_state(name)

@spec get_state(atom()) :: state()

Obtiene el estado actual del circuit breaker (:closed, :open, :half_open).

Retorna :closed si el breaker no está registrado.

start_link(opts)

@spec start_link(keyword()) :: GenServer.on_start()

Inicia un circuit breaker con nombre único (requerido en opts).

Opciones

  • :name — Nombre único del breaker (requerido)
  • :id — Alias de :name aceptado por conveniencia
  • :threshold — Número de fallos consecutivos para abrir el circuito (default: 5)
  • :timeout — Tiempo en ms antes de pasar a :half_open (default: 60_000)

success(name)

@spec success(atom()) :: :ok

Notifica una ejecución exitosa al circuit breaker.