///|
/// Discord's retry rule for one logical call, as `resend_delay_ms` decides.
/// `closed` is the client's own flag, so a closed client sends nothing more.
priv struct DiscordRetry {
max_retries : Int32
closed : Ref[Bool]
}
///|
impl @runtime.RetryDecider for DiscordRetry with fn next_delay_ms(
self,
error,
_request,
attempt,
random,
) {
resend_delay_ms(
error,
attempt~,
max_retries=self.max_retries,
closed=self.closed.val,
random~,
)
}
///|
/// How long to wait before sending a call again after the failed attempt
/// numbered `attempt` (from `0`), or `None` to raise the failure. Only a
/// call Discord cannot have acted on is resent, at most `max_retries` times
/// in all and never once the client is `closed`:
///
/// - a 429, after the delay the response asked for plus one millisecond;
/// - a transient transport failure that proves no byte was sent (a failed
/// connection, a proxy that could not reach Discord, or a timeout while
/// waiting for a connection slot or connecting), after the runtime's
/// default backoff. Nothing reached Discord, so this holds for every
/// method, `POST` included.
///
/// Anything else is final: a failure after connecting or a timed-out
/// exchange may already have reached Discord, and a status error is
/// Discord's answer.
fn resend_delay_ms(
error : @runtime.SdkError,
attempt~ : Int32,
max_retries~ : Int32,
closed~ : Bool,
random~ : Double,
) -> Int32? {
if closed || attempt >= max_retries {
return None
}
match error {
RateLimited(headers~, body~, ..) => {
let (retry_after_ms, _) = rate_limit_details(headers, body)
Some(retry_after_ms.to_int() + 1)
}
Transport(failure) if failure.is_unsent() && error.is_retryable() =>
Some(@runtime.Backoff::default().delay_ms(attempt, random))
_ => None
}
}