// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0
///|
/// Google SRE's client-side throttling breaker (← go-zero's `googleBreaker`).
///
/// The algorithm is moonward's `Breaker`; this binds it to a `Clock` and a
/// source of draws, the shape go-zero's breaker has, so a call site asks with
/// no arguments. go-zero's constants are moonward's preset `sre`.
pub struct Breaker {
core : @moonward.Breaker
clock : @clock.Clock
roll : () -> Double
}
///|
/// A breaker over a `buckets` × `bucket_ms` window (go-zero's ten seconds in
/// forty slices by default) reading time from `clock`. `rand` supplies the shed
/// roll as a draw from `[0, 1)`; without it the breaker draws from a
/// system-seeded generator.
pub fn Breaker::new(
clock : @clock.Clock,
rand? : () -> Double,
buckets~ : Int32 = 40,
bucket_ms~ : Int64 = 250L,
) -> Breaker {
let roll = match rand {
Some(f) => f
None => {
let src = @random.Rand()
() => src.double()
}
}
let sre = {
..@moonward.sre,
size: buckets,
window: @moondate.Span::new(millis=bucket_ms * buckets.to_int64()),
}
{ core: @moonward.Breaker::new(sre~, now=nanos(clock)), clock, roll, }
}
///|
fn nanos(clock : @clock.Clock) -> Int64 {
clock.now() * 1_000_000L
}
///|
/// The fraction of new calls the breaker is shedding now, zero while healthy.
pub fn Breaker::drop_ratio(self : Breaker) -> Double {
self.core.ratio(nanos(self.clock))
}
///|
/// Ask to make one call. `Some` promise means it may proceed and the caller
/// settles the promise when it finishes; `None` means the throttle shed it.
pub fn Breaker::allow(self : Breaker) -> Promise? {
self.core
.allow(nanos(self.clock), (self.roll)())
.map(inner => { inner, clock: self.clock, })
}
///|
/// The receipt for an admitted call (← go-zero's `breaker.Promise`).
pub struct Promise {
inner : @moonward.Promise
clock : @clock.Clock
}
///|
pub fn Promise::accept(self : Promise) -> Unit {
self.inner.pass(nanos(self.clock))
}
///|
pub fn Promise::reject(self : Promise) -> Unit {
self.inner.fail(nanos(self.clock))
}
///|
/// Raised by `Breaker::run` for a call the throttle shed (← go-zero's
/// `ErrServiceUnavailable`).
pub suberror Unavailable
///|
/// Run `req` under the breaker (← go-zero's `Breaker.Do`): a shed call raises
/// `Unavailable` and `req` never runs; a raise from `req` counts as a failure.
pub fn[T] Breaker::run(self : Breaker, req : () -> T raise) -> T raise {
guard self.allow() is Some(p) else { raise Unavailable }
errdefer p.reject()
let out = req()
p.accept()
out
}