// The window limiter's decisions, kept free of the clock and the maps so each
// boundary can be checked on plain values.
///|
/// What one pass of the global wait does.
priv enum GlobalStep {
/// No global window: admit without counting.
Pass
/// Admit, leaving the window starting at `start` with `count` admissions.
Admit(start~ : Int64, count~ : Int32)
/// Sleep this many milliseconds, then look again.
Wait(Int32)
}
///|
/// Decides one pass of the global wait at `now`. An account-wide block holds
/// every non-exempt request, even when the window has room; a window opens at
/// the first admission and a fresh one opens once `now` reaches its end.
fn global_step(
now : Int64,
blocked_until~ : Int64?,
window~ : GlobalWindow?,
start~ : Int64?,
count~ : Int32,
) -> GlobalStep {
if blocked_until is Some(until) && now < until {
return Wait(delay_until(until, now))
}
guard window is Some(window) else { return Pass }
let (start, count) = match start {
Some(start) if now < saturating_add_ms(start, window.window_ms) =>
(start, count)
_ => (now, 0)
}
if count < window.limit {
Admit(start~, count=count + 1)
} else {
Wait(delay_until(saturating_add_ms(start, window.window_ms), now))
}
}
///|
/// What one pass of a bucket's wait does.
priv enum BucketStep {
/// No recorded state: admit.
Admit
/// The recorded window has reset: forget it and admit.
Expired
/// Admit, recording the state with one fewer request left.
Consume(RateLimitState)
/// Sleep this many milliseconds, then look again.
Wait(Int32)
}
///|
/// Decides one pass of a bucket's wait at `now` from its recorded state.
fn bucket_step(state : RateLimitState?, now : Int64) -> BucketStep {
guard state is Some(state) else { return Admit }
if now >= state.reset_at_unix_ms {
return Expired
}
if state.remaining > 0 {
return Consume({ ..state, remaining: state.remaining - 1, })
}
Wait(delay_until(state.reset_at_unix_ms, now))
}
///|
/// How long to sleep from `now` until `until`, saturating at the longest sleep
/// a clock takes instead of wrapping when the span overflows.
fn delay_until(until : Int64, now : Int64) -> Int32 {
if until <= now {
return 0
}
let delta = if now < 0L && until > 9223372036854775807L + now {
9223372036854775807L
} else {
until - now
}
if delta > 2147483647L {
2147483647
} else {
delta.to_int()
}
}
///|
/// What a response does to its bucket and to the account-wide block.
priv struct ReleaseEffect {
/// The state to record for the bucket, if any.
record : RateLimitState?
/// The account-wide block after the response.
blocked_until : Int64?
}
///|
/// Decides what a response with `status` does at `now`. `parsed` is the
/// parser's state, `delay_ms` the Retry-After delay or the configured default,
/// and `global` whether the response is an account-wide 429. A 429 with a delay
/// empties the bucket until then, or, when global, extends the account-wide
/// block (never shortening it) and leaves the parsed state as it is.
fn release_effect(
status : Int32,
parsed~ : RateLimitState?,
delay_ms~ : Int32?,
global~ : Bool,
blocked_until~ : Int64?,
now~ : Int64,
) -> ReleaseEffect {
guard status == 429 && delay_ms is Some(ms) else {
return { record: parsed, blocked_until, }
}
let until = saturating_add_ms(now, ms.max(0))
if global {
let blocked_until = match blocked_until {
Some(previous) => previous.max(until)
None => until
}
{ record: parsed, blocked_until: Some(blocked_until), }
} else {
{ record: Some({ remaining: 0, reset_at_unix_ms: until, }), blocked_until, }
}
}