HTTPower.Middleware.CircuitBreaker (HTTPower v0.16.0)
View SourceCircuit breaker implementation for HTTPower.
Implements the circuit breaker pattern to protect against cascading failures when calling failing services. The circuit breaker has three states:
- Closed (normal): Requests pass through, failures are tracked
- Open (failing): Requests fail immediately without calling the service
- Half-Open (testing): Limited requests allowed to test recovery
How It Works
Closed State: Requests pass through normally. The circuit breaker tracks failures in a sliding window. If failures exceed the threshold, it transitions to Open.
Open State: All requests fail immediately with
:service_unavailable. After a timeout period, the circuit transitions to Half-Open.Half-Open State: A limited number of test requests are allowed through. If they succeed, the circuit transitions back to Closed. If they fail, the circuit transitions back to Open.
Configuration
config :httpower, :circuit_breaker,
enabled: true, # Enable/disable (default: false)
failure_threshold: 5, # Open after N failures
failure_threshold_percentage: 50, # Or open after N% failure rate
window_size: 10, # Track last N requests
timeout: 60_000, # Stay open for 60s (milliseconds)
half_open_requests: 1 # Allow N test requestsUsage
# Global circuit breaker
config :httpower, :circuit_breaker,
enabled: true,
failure_threshold: 5,
timeout: 60_000
# Per-client circuit breaker
client = HTTPower.new(
base_url: "https://api.example.com",
circuit_breaker: [
failure_threshold: 3,
timeout: 30_000
]
)
# Per-request circuit breaker key
HTTPower.get(url, circuit_breaker_key: "payment_api")Example
# After 5 failures, circuit opens
for _ <- 1..5 do
{:error, _} = HTTPower.get("https://failing-api.com/endpoint")
end
# Subsequent requests fail immediately
{:error, %{reason: :service_unavailable}} =
HTTPower.get("https://failing-api.com/endpoint")
# After 60 seconds, circuit enters half-open
# Next successful request closes the circuit
:timer.sleep(60_000)
{:ok, _} = HTTPower.get("https://failing-api.com/endpoint")
Summary
Functions
Checks if a request should be allowed through the circuit breaker.
Returns a specification to start this module under a supervisor.
Manually closes a circuit.
Gets the current state of a circuit.
Feature callback for the HTTPower pipeline.
Manually opens a circuit.
Records a failed request for the circuit.
Records a successful request for the circuit.
Resets a circuit to its initial closed state.
Starts the circuit breaker GenServer.
Types
@type circuit_breaker_config() :: [ enabled: boolean(), failure_threshold: pos_integer(), failure_threshold_percentage: pos_integer(), window_size: pos_integer(), timeout: pos_integer(), half_open_requests: pos_integer() ]
@type circuit_key() :: String.t()
@type circuit_state() :: %{ state: state(), requests: [request_result()], opened_at: integer() | nil, half_open_attempts: integer() }
@type request_result() :: {:success | :failure, integer()}
@type state() :: :closed | :open | :half_open
Functions
@spec call( circuit_key(), (-> {:ok, term()} | {:error, term()}), circuit_breaker_config() ) :: {:ok, term()} | {:error, term()}
Checks if a request should be allowed through the circuit breaker.
Returns:
{:ok, :allowed}if request can proceed{:error, :service_unavailable}if circuit is open{:ok, :disabled}if circuit breaker is disabled
Examples
iex> HTTPower.CircuitBreaker.call("api.example.com", fn ->
...> HTTPower.get("https://api.example.com/users")
...> end)
{:ok, response}
Returns a specification to start this module under a supervisor.
See Supervisor.
@spec close_circuit(circuit_key()) :: :ok
Manually closes a circuit.
Useful for testing or manual intervention.
@spec get_state(circuit_key()) :: state() | nil
Gets the current state of a circuit.
Returns :closed, :open, :half_open, or nil if circuit doesn't exist.
Feature callback for the HTTPower pipeline.
Checks circuit breaker state and stores info for post-request recording.
Returns:
:okif circuit is closed (continue with request){:ok, request}with circuit breaker info stored in private{:error, reason}if circuit is open (fail immediately)
Examples
iex> request = %HTTPower.Request{url: "https://api.example.com", ...}
iex> HTTPower.CircuitBreaker.handle_request(request, [failure_threshold: 5])
{:ok, modified_request}
@spec open_circuit(circuit_key()) :: :ok
Manually opens a circuit.
Useful for testing or manual intervention.
@spec record_failure(circuit_key(), circuit_breaker_config()) :: :ok
Records a failed request for the circuit.
@spec record_success(circuit_key(), circuit_breaker_config()) :: :ok
Records a successful request for the circuit.
@spec reset_circuit(circuit_key()) :: :ok
Resets a circuit to its initial closed state.
Starts the circuit breaker GenServer.