Circuit Breaker
A Circuit Breaker prevents repeated failures when calling an unreliable downstream. It watches call outcomes and, after too many consecutive failures, opens to block further calls for a cool-down period so the dependency can recover.
Why
- Prevent cascading failures across services.
- Stop a failing dependency from exhausting every thread or connection in your pool.
- Expose the health of a dependency at the breaker boundary, so each caller does not have to track it.
State machine
Three normal states (CLOSED, OPEN, HALF_OPEN) plus two manual overrides (FORCED_OPEN, FORCED_CLOSED).
stateDiagram-v2
direction LR
[*] --> CLOSED
CLOSED --> OPEN: failure threshold reached
OPEN --> HALF_OPEN: after reset_timeout
HALF_OPEN --> CLOSED: probes succeed
HALF_OPEN --> OPEN: probe fails
| State | Description |
|---|---|
| CLOSED | Normal operation. Calls are allowed. |
| OPEN | Calls are blocked to let the dependency recover. |
| HALF_OPEN | A limited number of probe calls test whether the dependency is back. |
| FORCED_OPEN | Manual override that blocks every call. |
| FORCED_CLOSED | Manual override that allows every call. |
Usage
from grelmicro.resilience import CircuitBreaker
circuit_breaker = CircuitBreaker(
"system_name", ignore_exceptions=FileNotFoundError
)
async def async_context_manager():
async with circuit_breaker:
print("Calling external service...")
@circuit_breaker
async def async_call():
print("Calling external service...")
def sync_context_manager():
with circuit_breaker.from_thread:
print("Calling external service from AnyIO worker thread...")
@circuit_breaker
def sync_call():
print("Calling external service from AnyIO worker thread...")
Thread safety
The Circuit Breaker is not thread-safe. Decorated sync functions or from_thread methods ensure state changes run safely within the async event loop. Threaded usage is supported only in AnyIO worker threads and may be slower than pure async usage.
See the API reference for every option.
Configuration
CircuitBreaker follows the three-paths configuration contract.
Programmatic
from grelmicro.resilience import CircuitBreaker
cb = CircuitBreaker(
"payments",
error_threshold=5,
success_threshold=2,
reset_timeout=30,
)
Declarative
from grelmicro.resilience import CircuitBreaker, CircuitBreakerConfig
config = CircuitBreakerConfig(
error_threshold=10,
reset_timeout=60.0,
ignore_exceptions=(ValueError,),
)
cb = CircuitBreaker.from_config("payments", config)
Environmental
Prefix: GREL_CIRCUIT_BREAKER_{NAME_UPPER}_
| Env var | Config field | Type | Default |
|---|---|---|---|
GREL_CIRCUIT_BREAKER_{NAME_UPPER}_ERROR_THRESHOLD |
error_threshold |
int (> 0) |
5 |
GREL_CIRCUIT_BREAKER_{NAME_UPPER}_SUCCESS_THRESHOLD |
success_threshold |
int (> 0) |
2 |
GREL_CIRCUIT_BREAKER_{NAME_UPPER}_RESET_TIMEOUT |
reset_timeout |
float (> 0) |
30.0 |
GREL_CIRCUIT_BREAKER_{NAME_UPPER}_HALF_OPEN_CAPACITY |
half_open_capacity |
int (> 0) |
1 |
GREL_CIRCUIT_BREAKER_{NAME_UPPER}_LOG_LEVEL |
log_level |
str |
"WARNING" |
GREL_CIRCUIT_BREAKER_{NAME_UPPER}_IGNORE_EXCEPTIONS |
ignore_exceptions |
CSV or JSON list of FQN strings (e.g. builtins.ValueError,my_app.errors.PaymentError or '["builtins.ValueError"]') |
[] |
Concrete example for CircuitBreaker("payments"):
GREL_CIRCUIT_BREAKER_PAYMENTS_ERROR_THRESHOLD=10
GREL_CIRCUIT_BREAKER_PAYMENTS_RESET_TIMEOUT=60
from grelmicro.resilience import CircuitBreaker
# GREL_CIRCUIT_BREAKER_PAYMENTS_ERROR_THRESHOLD=10
# GREL_CIRCUIT_BREAKER_PAYMENTS_RESET_TIMEOUT=60
cb = CircuitBreaker("payments")