CircuitBreaker — Fault Isolation & Outage Shielding
2.80 kB minified + gzipped
When an external dependency — a payment gateway, third-party webhook receiver, or database replica — suffers a sustained outage, client applications retrying failed calls trigger a retry storm. Hundreds of concurrent requests repeatedly attempt TCP handshakes, execute database transactions, and wait for connection timeouts. This exhausts server connection pools, starves the Node.js event loop, and causes cascading failures across otherwise healthy upstream services.
This manual coordination introduces three dangerous runtime failure modes:
- The Thundering Probe Herd: When the cooldown timer expires, multiple concurrent requests simultaneously attempt probe requests against the recovering service, immediately knocking it back offline before it can stabilize.
- False Outage Triggers: Client-side domain errors (such as
400 Bad Requestor404 Not Found) increment failure counters, incorrectly opening the circuit breaker for healthy external APIs. - Timer Handle Leaks: Background
setIntervalorsetTimeouttimers keep Node.js serverless invocations and CLI scripts hanging, preventing clean process exit.
CircuitBreaker replaces this boilerplate with a demand-driven state machine. It tracks task
failures, fast-fails subsequent calls in 0ms with CircuitBreaker.OpenError once failures exceed a
threshold, and coordinates isolated single-probe trials during recovery without running persistent
background timers.
flowchart TD
Closed["Closed (Normal traffic passes through)"] -->|"Failures reach failureThreshold"| Open["Open (Fast-fails calls in 0ms)"]
Open -->|"resetTimeout elapses (on demand)"| HalfOpen["HalfOpen (Single probe test)"]
HalfOpen -->|"successThreshold probes succeed"| Closed
HalfOpen -->|"Probe fails with tripping error"| Open
Operating States & Mental Model
Section titled “Operating States & Mental Model”A CircuitBreaker operates as a discriminated union over three distinct states:
Closed: The downstream dependency is healthy. Calls execute normally. Any success resets consecutive failure counters to0. If failures reachfailureThreshold, the breaker transitions toOpen.Open: The downstream dependency is failing. All protected tasks immediately short-circuit in 0ms withResult.make.err(CircuitBreaker.OpenError<E>). Zero network I/O or downstream allocations occur.HalfOpen: TheresetTimeoutcooldown has elapsed. The breaker allows a single probe request through to test downstream health (probeInFlight: true). Concurrent requests arriving while the probe is active continue to fast-fail. IfsuccessThresholdconsecutive probes succeed, the circuit returns toClosed. If any probe fails, the circuit immediately trips back toOpen.
Creating a CircuitBreaker
Section titled “Creating a CircuitBreaker”Initialize a circuit breaker with CircuitBreaker.make, providing the failure threshold and
cooldown timeout:
Configuration Options
Section titled “Configuration Options”failureThreshold: The number of failures required to trip the circuit toOpen.resetTimeout: The cooldown duration the breaker remains inOpenbefore permitting trial probes.successThreshold: The number of consecutive successful probes required inHalfOpento declare the service recovered and reset the circuit toClosed. Defaults to1.shouldTrip: Optional filter predicate. Non-tripping errors (such as validation failures or404 Not Found) return theirErroutcome normally without incrementing failure counters or tripping the circuit.failureWindow: Optional sliding duration window for tracking failures. When specified, only failures occurring within the window are counted towardsfailureThreshold.
Protecting Fallible Tasks
Section titled “Protecting Fallible Tasks”The breaker.protect combinator wraps any Task.Result<EIn, A> computation. Because it accepts the
task as its sole argument, it integrates directly into pipe:
The protected task preserves the exact narrowed error type EIn of the input task while appending
CircuitBreaker.OpenError<E> to the error channel.
Sliding Failure Windows
Section titled “Sliding Failure Windows”By default, CircuitBreaker counts consecutive failures: any successful execution resets the
counter to 0.
To detect partial degradation patterns — where a service fails 50% of requests in an alternating
pattern — specify failureWindow:
With failureWindow configured, failures are tracked within a sliding timestamp window. Intervening
successes do not erase recorded failures. If 10 failures occur within any rolling 60-second window,
the circuit trips to Open.
Observing State & Health Checks
Section titled “Observing State & Health Checks”Query the current lifecycle state synchronously with breaker.state():
Telemetry Subscriptions
Section titled “Telemetry Subscriptions”Attach listener callbacks to observe state transitions using breaker.subscribe:
Subscriber exceptions are isolated with try/catch blocks, guaranteeing that an observer error
never disrupts ongoing task execution.
Manual Resets
Section titled “Manual Resets”To force an open or half-open circuit breaker back to Closed with zero recorded failures (for
instance, after an operational administrator confirms recovery via an admin dashboard):
End-to-End Pipeline Workflow
Section titled “End-to-End Pipeline Workflow”The following example demonstrates protecting an order fulfillment pipeline against downstream inventory service outages while isolating customer notifications:
Problems it solves
Section titled “Problems it solves”- Preventing cascading failures from retry storms: During sustained downstream outages, retrying
every incoming call saturates connection pools and starves CPU resources.
CircuitBreakerfast-fails requests in 0ms whenOpen, freeing system resources for unaffected workflows. - Single-probe mutex locks during service recovery: When an outage ends, allowing hundreds of
concurrent callers to test a recovering service triggers immediate relapse.
CircuitBreakerallows exactly one trial probe through duringHalfOpen, rejecting concurrent callers until recovery is proven. - Demand-driven time accounting without timer leaks: Traditional implementations run background
setTimeouthandles to transition from Open to HalfOpen, causing memory leaks in serverless functions and preventing Node.js processes from exiting.CircuitBreakerevaluates expiration lazily on incoming calls and state queries without background intervals. - Selective error filtering: Domain errors (such as validation rejections or missing records)
should never be conflated with infrastructure downtime. The
shouldTrippredicate filters errors so only genuine service outages increment failure counters. - Preserving task error channels in pipelines: Wrapping fallible tasks often erases specific
error unions.
CircuitBreaker.protectpreserves the exact input error typeEInwhile cleanly appendingCircuitBreaker.OpenError<E>to the return type.