///|
/// The delay before retry number `attempt + 1`, in milliseconds.
///
/// The exponential delay is `base_ms * 2^attempt` up to `max_ms`; a negative
/// attempt counts as 0, and a nonpositive `base_ms` or `max_ms` gives 0.
/// `random` is clamped into [0, 1), NaN to 0. The jitter then applies:
/// `NoJitter` keeps the delay, `Full` takes `trunc(delay * random)`, and
/// `Proportional(lo~, hi~)` multiplies by `lo + trunc(random * (hi - lo))`
/// per mille, in integers. The jittered delay is capped at `max_ms` again, so
/// a `hi` above 1000 spreads delays below the cap but never past it.
///
/// ```mbt check
/// test {
/// let backoff : @retry.Backoff = {
/// base_ms: 500,
/// max_ms: 30000,
/// jitter: Proportional(lo=800, hi=1200),
/// }
/// assert_eq(backoff.delay_ms(2, 0.5), 2000)
/// assert_eq(backoff.delay_ms(0, 0.0), 400)
/// }
/// ```
pub fn Backoff::delay_ms(
self : Backoff,
attempt : Int32,
random : Double,
) -> Int32 {
if self.base_ms <= 0 || self.max_ms <= 0 {
return 0
}
let max = self.max_ms.to_int64()
// base_ms < 2^31, so a shift below 31 stays under 2^62.
let raw = if attempt >= 31 {
max
} else {
(self.base_ms.to_int64() << attempt.max(0)).min(max)
}
let random = if !(random >= 0.0) {
0.0
} else {
random.min(0.9999999999999999)
}
let delay = match self.jitter {
NoJitter => raw
Full => (raw.to_double() * random).to_int64()
Proportional(lo~, hi~) => {
// Both factors stay below 2^31, so the product fits in Int64.
let span = (hi.to_int64() - lo.to_int64()).to_double()
let per_mille = lo.to_int64() + (random * span).to_int64()
raw * per_mille.max(0L) / 1000L
}
}
delay.max(0L).min(max).to_int()
}