MediumReliabilityPython 3
Circuit Breaker State
Model closed, open, and half-open breaker transitions with deterministic cooldown behavior.
35m3 sample tests9 hidden tests
Implement a small circuit breaker for protecting a dependency that's failing.
Requirements
- Define
CircuitBreaker(failure_threshold, cooldown_seconds, now). allow()returns whether a request may be attempted.record_success()records a successful attempt.record_failure()records a failed attempt.state()returns"closed","open", or"half_open".- In
closed, requests are allowed and consecutive failures are counted. - A success while
closedresets the consecutive-failure count to zero. - Reaching
failure_thresholdopens the breaker. - In
open, requests are blocked until cooldown has elapsed. - The open → half_open transition happens only inside
allow()when cooldown has elapsed.state()must not transition or report half_open solely because cooldown elapsed; without a successful probeallow(),state()stays"open". - After cooldown, exactly one trial request is allowed while
half_open. Furtherallow()calls returnFalseuntil that trial is recorded as success or failure. - A half-open success closes the breaker; a half-open failure opens it again.
Example
python
clock = {"t": 0}
breaker = CircuitBreaker(2, 10, now=lambda: clock["t"])
breaker.record_failure()
breaker.record_failure()
assert breaker.state() == "open"
assert not breaker.allow()
clock["t"] = 10
assert breaker.state() == "open" # cooldown alone doesn't half-open
assert breaker.allow() is True
assert breaker.state() == "half_open"
assert breaker.allow() is False # only one half-open trial
breaker.record_success()
assert breaker.state() == "closed"
breaker.record_failure()
breaker.record_success()
breaker.record_failure()
assert breaker.state() == "closed" # success reset the consecutive-failure countConstraints
- Count consecutive failures only while closed.
- Ignore
record_successandrecord_failurewhileopenbecause no probe was allowed. An open-state record leavesopened_atunchanged. - Use deterministic injected time.
- Avoid hidden background timers.
failure_thresholdis a positive integer;cooldown_secondsis finite and nonnegative.now()returns finite, nondecreasing time in seconds. Equality at the cooldown boundary permits the trial.- Calls are serialized. While closed, records represent completed attempts and can be submitted directly. While half-open, the next record belongs to the single admitted trial; no old attempt completes during that trial. Handling overlapping attempts would require request identities or generations in addition to this API.
The clock measures elapsed time, so a real caller can inject time.monotonic. A failed trial restarts cooldown from its failure time, not the original opening time. For example, opening at 0 with cooldown 5, then failing a probe at 7, blocks the next probe until 12.
Editor