Level 1 One circuit breaker
When a backend starts failing, hammering it with more requests makes things worse. A circuit breaker sits in front of it and stops traffic for a while after repeated failures. Build CircuitBreaker(failure_threshold, cooldown). There is no real clock: every method takes now, an integer, and now never decreases between calls.
The breaker is in one of three states:
"closed"(normal): every request is allowed. It counts consecutive failures; a success resets the count to 0. When the count reachesfailure_threshold, the breaker becomes"open"at thatnow."open": every request is refused untilcooldowntime units have passed since it opened. Fromnow >= opened_at + cooldownon, it is"half_open"."half_open": exactly one trial request is allowed. While that trial is in flight (allowed but not yet recorded), every other request is refused. If the trial succeeds the breaker becomes"closed"with a failure count of 0; if it fails the breaker becomes"open"again, opened at thatnow.
Methods:
allow(now) -> bool: may a request go through right now? In"half_open"with no trial in flight, returnTrueand mark the trial as in flight.record(success: bool, now) -> None: report the outcome of a request that was allowed. In"closed"it updates the count; in"half_open"it is the trial's result (even ifallowwasn't called for it); in"open"(before the cooldown is over) it is a late result and is ignored.state(now) -> str:"closed","open"or"half_open". Only looking at the state never uses up the trial.
cb = CircuitBreaker(failure_threshold=2, cooldown=10)
cb.record(False, 1); cb.record(False, 2) # 2 consecutive failures: opens at 2
cb.allow(5) # False
cb.state(11) # "open"
cb.state(12) # "half_open"
cb.allow(12) # True (the trial)
cb.allow(13) # False (trial still in flight)
cb.record(False, 14) # trial failed: open again from 14
cb.allow(23) # False
cb.allow(24) # True
cb.record(True, 25) # closed
cb.state(25) # "closed"
Show hint
Store state, failures, opened_at and trial_in_flight. Write one _refresh(now) that turns "open" into "half_open" once the cooldown is over, and call it at the start of every method.