~/problems / Simulation & OOP design / Object-oriented design and extensible simulations

OA: Dual circuit breakers

medium 2 levels ~45 min Databricks

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 reaches failure_threshold, the breaker becomes "open" at that now.
  • "open": every request is refused until cooldown time units have passed since it opened. From now >= opened_at + cooldown on, 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 that now.

Methods:

  • allow(now) -> bool: may a request go through right now? In "half_open" with no trial in flight, return True and 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 if allow wasn'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.

Level 2 unlocks when level 1 passes.

Topic: Object-oriented design and extensible simulations. Classes that survive new requirements: games, payments, subscriptions, refactors.

0:00
Ctrl ' run · Ctrl ↵ submit
esc