| Safe Haskell | None |
|---|---|
| Language | GHC2021 |
Ecluse.Breaker
Contents
Description
The small circuit-breaker state machine that guards an unreliable operation.
A breaker fronts a call that can fail or hang — minting an outbound credential, or consulting an effectful rule source. While the call is healthy it stays out of the way; once failures pile up it trips open and fast-fails further calls for a cooldown, sparing both the caller's latency and the failing dependency. After the cooldown it admits a single half-open probe: if the probe succeeds the breaker resets, and if it fails the breaker re-opens for another cooldown.
The machine is pure and clock-injected: every transition takes the caller's now,
so it is deterministic under test with no real time passing. The two policy knobs —
the trip threshold and the cooldown — are not held here; each caller passes its
own to recordFailure, so one breaker shape serves consumers that tune them
differently. Concurrency and storage (an STM TVar, a record field) are the
caller's concern too: these functions only fold one state into the next.
Synopsis
- data Breaker
- initialBreaker :: Breaker
- admit :: UTCTime -> Breaker -> (Bool, Breaker)
- recordSuccess :: Breaker -> Breaker
- recordFailure :: Int -> NominalDiffTime -> UTCTime -> Breaker -> Breaker
- newtype BreakerReporter = BreakerReporter (Breaker -> IO ())
- noBreakerReporter :: BreakerReporter
- reportBreakerChange :: BreakerReporter -> Breaker -> Breaker -> IO ()
Documentation
The breaker's state, gating whether the guarded operation may be attempted.
A Closed breaker is healthy and counts consecutive failures towards the trip
threshold; an Open breaker fast-fails until its instant passes; a HalfOpen
breaker has admitted one recovery probe and is waiting on its outcome.
initialBreaker :: Breaker Source #
A fresh, healthy breaker with no failures recorded.
admit :: UTCTime -> Breaker -> (Bool, Breaker) Source #
Decide whether the guarded operation may be attempted at now, returning the
admission and the breaker state to keep.
A Closed or HalfOpen breaker always admits and is unchanged. An Open breaker
denies while its instant is still in the future; once now reaches it the breaker
moves to HalfOpen and admits a single recovery probe. The caller commits the
returned state (e.g. writes it back to its TVar) so the half-open transition is
recorded.
recordSuccess :: Breaker -> Breaker Source #
Fold a successful attempt into the breaker: reset it to healthy, clearing any accumulated failures or a half-open probe.
recordFailure :: Int -> NominalDiffTime -> UTCTime -> Breaker -> Breaker Source #
Fold a failed attempt into the breaker, given the caller's trip threshold and
cooldown and the current instant.
A Closed breaker counts the failure up, tripping Open for the cooldown once the
count reaches the threshold. Any other state (a failed half-open probe, or a failure
folded in while already open) (re-)opens for a fresh cooldown.
Observing transitions
newtype BreakerReporter Source #
An observer of breaker state changes: invoked with the breaker's new state after a transition commits, so a layer that cares (a state gauge) can record it.
Deliberately telemetry-agnostic — it is just a callback, so
the breaker and its callers (Ecluse.Rules.Effectful, the credential refresher) stay
free of any metric dependency; the composition root supplies the bridge to the
instruments. Breaker -> IO ()noBreakerReporter is the inert default: a breaker observed by it records
nothing, which is also how a breaker constructed before the telemetry substrate exists
behaves until the live observer is installed.
Constructors
| BreakerReporter (Breaker -> IO ()) |
noBreakerReporter :: BreakerReporter Source #
The inert reporter: discards the state, recording nothing.
reportBreakerChange :: BreakerReporter -> Breaker -> Breaker -> IO () Source #
Report a transition through the observer, but only when old and new differ in
their observable state — Closed carries a failure tally that is not itself
observable, so a failure that merely advances the count within Closed is not a state
change and fires nothing. A genuine change (a trip, a recovery probe, a reset) fires the
reporter with the new state.