\Drupal\strata\Health CircuitBreaker

Stops a repair that keeps failing from being attempted forever.

Keyed on the finding code, because that is the identifier that survives a class rename and is the same key the ledger and the ladder use. A repair that has failed $threshold times in a row is almost certainly failing for a reason retrying will not fix, and each retry costs the same network round trip, the same lock, the same log line as the one before it.

Three states, and only three:

  • closed: fewer than $threshold consecutive failures. Calls are allowed.
  • open: the threshold was reached less than $cooldown seconds ago. Calls are refused.
  • half-open: the cooldown has passed. One trial call is allowed; a failure re-opens the circuit for a fresh cooldown, and a success closes it.

Only a recorded success clears the count. Time alone moves the circuit to half-open and no further, so a permanently broken code makes exactly one attempt per cooldown instead of flapping closed and hammering whatever it is failing against.

The clock is injected. A test that had to sleep for a five-minute cooldown would not be a test anyone runs, so time is a parameter here rather than a call to time() buried in a comparison.

State is in memory and dies with the process; durable state is the ledger's job.

Summary

Methods
Properties
Constants
__construct
threshold
cooldown
allow
state
recordFailure
recordSuccess
reset
No public properties found
CLOSED
OPEN
HALF_OPEN
No protected methods found
No protected properties found
No protected constants found
now
threshold
cooldown
clock
tracked
No private constants found

Constant

CLOSED

CLOSED = 'closed'

The circuit is closed: calls are allowed.

OPEN

OPEN = 'open'

The circuit is open: calls are refused until the cooldown elapses.

HALF_OPEN

HALF_OPEN = 'half-open'

The cooldown has elapsed: one trial call is allowed.

Properties

$threshold

$threshold : int

Consecutive failures that open the circuit.

Type

int —

$cooldown

$cooldown : int

Seconds the circuit stays open before a trial call is let through.

Type

int —

$clock

$clock : \Closure

Returns the current unix timestamp.

Type

Closure

$tracked

$tracked : array

Per-code failure count and the timestamp the circuit last opened at.

Type

array<string|int, mixed> —

Methods

__construct()

__construct(int  $threshold = 3, int  $cooldown = 300, callable|null  $clock = null) : mixed

Constructs the breaker.

Parameters

int $threshold

Consecutive failures that open the circuit.

int $cooldown

Seconds the circuit stays open, measured from the failure that opened it.

callable|null $clock

Returns a unix timestamp as an int. NULL uses time().

Throws

\InvalidArgumentException

When the threshold is below 1, or the cooldown is negative. A threshold of 0 would open every circuit before anything had failed.

Returns

mixed —

threshold()

threshold() : int

Failures a code may take before its circuit opens.

Returns

int —

The threshold.

cooldown()

cooldown() : int

Seconds an open circuit stays open.

Returns

int —

The cooldown.

allow()

allow(string  $code) : bool

Whether a repair for this code may be attempted now.

Parameters

string $code

The finding code the repair is for.

Returns

bool —

TRUE while closed or half-open, FALSE while open.

state()

state(string  $code) : string

The state of one code's circuit right now.

Derived from the count and the clock on every call rather than stored, so a circuit cannot be left stale by nobody having asked about it during the cooldown.

Parameters

string $code

The finding code.

Returns

string —

CLOSED, OPEN or HALF_OPEN.

recordFailure()

recordFailure(string  $code) : void

Records a failed attempt.

Every failure at or past the threshold re-stamps the open time. That is what makes a failed trial call restart the cooldown instead of leaving the circuit half-open and retrying on every sweep from then on.

Parameters

string $code

The finding code the repair was for.

Returns

void —

recordSuccess()

recordSuccess(string  $code) : void

Records a successful attempt, closing the circuit.

A success is the only thing that clears the count. Letting the cooldown alone clear it would turn a permanently broken code into one that retries $threshold times per cooldown.

Parameters

string $code

The finding code the repair was for.

Returns

void —

reset()

reset(string  $code) : void

Forgets a code entirely, as if it had never failed.

For an operator who has fixed the underlying cause and does not want to wait out a cooldown to find out. Not for the repair path; that reports what happened and lets the count decide.

Parameters

string $code

The finding code to forget.

Returns

void —

now()

now() : int

Reads the injected clock.

Throws

\UnexpectedValueException

When the injected clock returns anything but an int. Caught here rather than left to a comparison, where a string clock would silently make every cooldown look elapsed.

Returns

int —

A unix timestamp.