Normandy.Resilience.CircuitBreaker (normandy v0.2.0)
View SourceCircuit Breaker pattern implementation to prevent cascading failures.
The circuit breaker monitors failures and can transition between three states:
- Closed: Normal operation, requests pass through
- Open: Failures exceeded threshold, requests fail fast
- Half-Open: Testing if service recovered, limited requests allowed
States
Closed ──(failures > threshold)──> Open
↑ │
│ │
└──(success)── Half-Open ←─(timeout)┘Features
- Automatic state transitions based on failure rates
- Configurable failure threshold and timeout
- Half-open state for gradual recovery
- Thread-safe using GenServer
- Metrics and monitoring support
Example
# Start a circuit breaker
{:ok, cb} = CircuitBreaker.start_link(
name: :api_breaker,
failure_threshold: 5,
timeout: 60_000
)
# Execute protected call
case CircuitBreaker.call(cb, fn ->
MyAPI.risky_operation()
end) do
{:ok, result} -> handle_success(result)
{:error, :open} -> handle_circuit_open()
{:error, reason} -> handle_failure(reason)
end
# Check state
CircuitBreaker.state(cb) # :closed | :open | :half_open
Summary
Functions
Execute a function protected by the circuit breaker.
Returns a specification to start this module under a supervisor.
Get detailed metrics about the circuit breaker.
Manually reset the circuit breaker to closed state.
Start a circuit breaker GenServer.
Get the current state of the circuit breaker.
Manually open the circuit breaker (for testing or maintenance).
Types
@type circuit_breaker_option() :: {:name, atom()} | {:failure_threshold, pos_integer()} | {:success_threshold, pos_integer()} | {:timeout, pos_integer()} | {:half_open_max_calls, pos_integer()}
@type circuit_breaker_options() :: [circuit_breaker_option()]
@type state() :: :closed | :open | :half_open
Functions
@spec call(GenServer.server(), function()) :: {:ok, term()} | {:error, term()}
Execute a function protected by the circuit breaker.
Returns
{:ok, result}- Function executed successfully{:error, :open}- Circuit is open, request blocked{:error, reason}- Function failed with reason
Examples
CircuitBreaker.call(breaker, fn ->
{:ok, MyAPI.call()}
end)
# With explicit error handling
CircuitBreaker.call(breaker, fn ->
case MyAPI.call() do
{:ok, result} -> {:ok, result}
{:error, _} = error -> error
end
end)
Returns a specification to start this module under a supervisor.
See Supervisor.
@spec metrics(GenServer.server()) :: map()
Get detailed metrics about the circuit breaker.
Returns
Map with current state and statistics
Examples
CircuitBreaker.metrics(breaker)
#=> %{
state: :closed,
failure_count: 2,
success_count: 100,
opened_at: nil
}
@spec reset(GenServer.server()) :: :ok
Manually reset the circuit breaker to closed state.
Examples
CircuitBreaker.reset(breaker)
#=> :ok
@spec start_link(circuit_breaker_options()) :: GenServer.on_start()
Start a circuit breaker GenServer.
Options
:name- Registered name for the circuit breaker:failure_threshold- Number of failures before opening (default: 5):success_threshold- Number of successes to close from half-open (default: 2):timeout- Milliseconds before transitioning to half-open (default: 60_000):half_open_max_calls- Max concurrent calls in half-open state (default: 1)
Examples
{:ok, cb} = CircuitBreaker.start_link(name: :my_breaker)
{:ok, cb} = CircuitBreaker.start_link(
name: :api_breaker,
failure_threshold: 10,
timeout: 30_000
)
@spec state(GenServer.server()) :: state()
Get the current state of the circuit breaker.
Returns
:closed- Normal operation:open- Circuit is open, failing fast:half_open- Testing recovery
Examples
CircuitBreaker.state(breaker)
#=> :closed
@spec trip(GenServer.server()) :: :ok
Manually open the circuit breaker (for testing or maintenance).
Examples
CircuitBreaker.trip(breaker)
#=> :ok