///|
/// retry_index is zero-based, elapsed_ms must use a monotonic clock.
/// replay_safe opts non-idempotent methods in only when the caller knows that
/// the server implements deduplication or equivalent replay-safe semantics.
pub fn decide(
settings : Settings,
http_method~ : String,
status~ : Int,
retry_index~ : Int,
elapsed_ms~ : Int64,
body_replayable~ : Bool,
replay_safe? : Bool = false,
server_delay_ms? : Int64 = 0L,
sample_ppm? : Int = 1000000,
) -> Decision {
if !valid_settings(settings) {
return Stop(InvalidSettings)
}
if retry_index < 0 ||
elapsed_ms < 0L ||
server_delay_ms < 0L ||
sample_ppm < 0 ||
sample_ppm > 1000000 ||
status < 100 ||
status > 599 {
return Stop(InvalidInput)
}
if !retryable_status(status) {
return Stop(NotRetryable)
}
if !body_replayable || (!idempotent_method(http_method) && !replay_safe) {
return Stop(NotReplayable)
}
if retry_index >= settings.max_retries {
return Stop(AttemptsExhausted)
}
if elapsed_ms >= settings.total_budget_ms {
return Stop(BudgetExhausted)
}
if server_delay_ms > settings.max_delay_ms {
return Stop(ServerDelayExceedsCap)
}
let ceiling = backoff_ceiling(settings, retry_index).unwrap()
let client_delay = jitter_delay(ceiling, sample_ppm).unwrap()
let delay = if server_delay_ms > client_delay {
server_delay_ms
} else {
client_delay
}
let remaining = settings.total_budget_ms - elapsed_ms
// Never clamp a server minimum to fit the remaining budget.
if settings.request_reserve_ms >= remaining ||
delay >= remaining - settings.request_reserve_ms {
return Stop(BudgetExhausted)
}
Wait(delay)
}