Normandy.Resilience.CircuitBreaker (normandy v0.2.0)

View Source

Circuit 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

circuit_breaker_option()

@type circuit_breaker_option() ::
  {:name, atom()}
  | {:failure_threshold, pos_integer()}
  | {:success_threshold, pos_integer()}
  | {:timeout, pos_integer()}
  | {:half_open_max_calls, pos_integer()}

circuit_breaker_options()

@type circuit_breaker_options() :: [circuit_breaker_option()]

state()

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

Functions

call(server, fun)

@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)

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

metrics(server)

@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
}

reset(server)

@spec reset(GenServer.server()) :: :ok

Manually reset the circuit breaker to closed state.

Examples

CircuitBreaker.reset(breaker)
#=> :ok

start_link(opts \\ [])

@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
)

state(server)

@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

trip(server)

@spec trip(GenServer.server()) :: :ok

Manually open the circuit breaker (for testing or maintenance).

Examples

CircuitBreaker.trip(breaker)
#=> :ok