PushX.CircuitBreaker (PushX v0.14.0)

Copy Markdown View Source

Circuit breaker for push notification providers.

Tracks consecutive failures per provider and temporarily blocks requests when a provider is consistently failing, preventing resource waste on dead connections.

States

  • :closed — Normal operation, requests flow through
  • :open — Provider is failing, requests are rejected immediately
  • :half_open — Cooldown expired, exactly one probe request is allowed through (others are rejected until the probe reports back; if the probe never reports, another probe is admitted after a full cooldown)

Configuration

config :pushx,
  circuit_breaker_enabled: true,
  circuit_breaker_threshold: 5,       # consecutive failures to open
  circuit_breaker_cooldown_ms: 30_000  # ms before half_open

Usage

The circuit breaker is checked automatically in APNS.send_once/3 and FCM.send_once/3 when enabled. You can also check manually:

case PushX.CircuitBreaker.allow?(:apns) do
  :ok -> # Proceed
  {:error, :circuit_open} -> # Provider is down
end

Summary

Functions

Checks if a request is allowed for the given provider.

Returns a specification to start this module under a supervisor.

Records a failed request. Opens the circuit if the failure threshold is reached.

Records a successful request, resetting the circuit to :closed.

Resets the circuit breaker for a provider. Useful for testing or manual recovery.

Starts the circuit breaker process.

Returns the current circuit breaker state for a provider.

Types

key()

@type key() :: atom()

provider()

@type provider() :: :apns | :fcm

state()

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

Functions

allow?(provider)

@spec allow?(key()) :: :ok | {:error, :circuit_open}

Checks if a request is allowed for the given provider.

Returns :ok if the circuit is closed or half-open (probe), {:error, :circuit_open} if the circuit is open.

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

record_failure(provider)

@spec record_failure(key()) :: :ok

Records a failed request. Opens the circuit if the failure threshold is reached.

Serialized through the GenServer.

record_success(provider)

@spec record_success(key()) :: :ok

Records a successful request, resetting the circuit to :closed.

Writes are serialized through the GenServer so concurrent successes and failures cannot lose updates via ETS read-modify-write — but in steady state (:closed with zero failures) a success changes nothing, so it skips the GenServer round-trip entirely. The breaker process therefore sees no traffic at all on the healthy hot path, instead of serializing every send result at high throughput. The skip can race a concurrent failure (the reset it would have applied is missed), which at worst opens the breaker one failure early — the next non-steady success resets the count through the GenServer as before.

reset(provider)

@spec reset(key()) :: :ok

Resets the circuit breaker for a provider. Useful for testing or manual recovery.

start_link(opts \\ [])

Starts the circuit breaker process.

state(provider)

@spec state(key()) :: state()

Returns the current circuit breaker state for a provider.