Home/Docs/circuit-breaker

Circuit Breaker Protection

The Circuit Breaker pattern prevents cascading failures by temporarily blocking requests to a downstream service that's experiencing outages or severe degradation, instead of letting every caller keep hammering a service that's already down.

Circuit Breaker State Machine

Click states to inspect circuit isolation behavior.

STATE: CLOSED

Normal operation. Requests pass through to upstream target API seamlessly.

Not wired in automatically

CircuitBreaker is a standalone, thread-safe utility — it isn't attached to BaseAPIClient by default. You apply it yourself as a decorator around whichever calls you want protected.


How It Works

CircuitBreaker tracks consecutive failures and moves through three states:

Half-Open doesn't limit itself to one trial call

The classic circuit breaker pattern allows exactly one trial request through in the half-open state. This implementation doesn't enforce that — it checks the state once per call, and any call made while the breaker is Half-Open will run the wrapped function. If several calls happen concurrently right after the cooldown elapses, all of them go through, not just one.

The OPEN → HALF_OPEN transition happens lazily: nothing runs on a timer. Instead, every time .state is read (which happens at the start of every wrapped call), it checks whether recovery_timeout has elapsed since the last failure and flips to HALF_OPEN if so.


Using It as a Decorator

CircuitBreaker instances are callables, so you use them directly as a decorator on a synchronous function:

CODE
from hakiapi.core.circuit_breaker import CircuitBreaker, CircuitOpenError

breaker = CircuitBreaker(failure_threshold=5, recovery_timeout=30.0)

@breaker
def fetch_data():
    return client.get("some/endpoint")

try:
    fetch_data()
except CircuitOpenError as e:
    print(f"Blocked, retry in {e.retry_after:.1f}s")
Share one instance

Create a single CircuitBreaker and reuse it across every call site that hits the same downstream service. A breaker created fresh per-call never accumulates failures, so it can never open.

Synchronous callables only

The decorator wraps func(*args, **kwargs) directly and returns its result. Don't decorate an async def function with it — the wrapper isn't awaited, so it won't work with coroutines.


Which Exceptions Count as Failures

By default, only HakiAPIError (and its subclasses) count toward the failure threshold — that's what expected_exceptions defaults to. Any other exception type propagates immediately without being caught, counted, or affecting the circuit's state at all.

CODE
from hakiapi.core.exceptions import ServerError, RequestTimeoutError

# Only count server errors and timeouts — a 404 (ClientError) won't trip the breaker
breaker = CircuitBreaker(expected_exceptions=(ServerError, RequestTimeoutError))
Narrowing expected_exceptions

This is useful when you don't want client-side mistakes (bad request bodies, 404s, etc.) to open a circuit that should really only react to the downstream service actually being unhealthy.


Constructor Parameters

ParameterTypeDefaultDescription
failure_thresholdint5Consecutive failures required to move from Closed to Open. Clamped to a minimum of 1.
recovery_timeoutfloat30.0Seconds to wait after the last failure before allowing a Half-Open trial. Clamped to a minimum of 0.1.
expected_exceptionstuple[type[Exception], ...](HakiAPIError,)Exception types that count as failures. Anything else propagates without affecting circuit state.

CircuitOpenError

Raised in place of calling the wrapped function whenever the circuit is OPEN. A subclass of HakiAPIError.

  • message — defaults to "Circuit breaker is OPEN. Request blocked.", but the decorator overrides it with a message naming the blocked function.
  • retry_after — seconds remaining in the cooldown window, computed from recovery_timeout minus time elapsed since the last failure. Never negative.
CODE
except CircuitOpenError as e:
    print(f"{e}, retry in {e.retry_after:.1f}s")

What's Next?

  • See exceptions for the rest of the HakiAPIError hierarchy expected_exceptions draws from.
  • See retries for how CircuitBreaker compares to (and can be combined with) HakiAPI's retry/backoff logic.