Skip to main content

babelforce_manager_sdk/
retry.rs

1use std::time::Duration;
2
3/// Automatic-retry tuning. Transient failures — network errors and 429/502/503/504 — are retried
4/// with exponential backoff. Non-idempotent requests are only retried on 429.
5#[derive(Clone, Debug)]
6pub struct RetryPolicy {
7    /// Retry attempts after the initial request.
8    pub max_retries: u32,
9    /// Base backoff; grows exponentially per attempt.
10    pub base_delay: Duration,
11    /// Upper bound on any single backoff.
12    pub max_delay: Duration,
13}
14
15impl Default for RetryPolicy {
16    fn default() -> Self {
17        Self {
18            max_retries: 2,
19            base_delay: Duration::from_millis(250),
20            max_delay: Duration::from_secs(10),
21        }
22    }
23}
24
25impl RetryPolicy {
26    /// A policy that performs no retries.
27    pub fn disabled() -> Self {
28        Self {
29            max_retries: 0,
30            ..Self::default()
31        }
32    }
33}
34
35/// Implemented (per generated module, in `error.rs`) for each module's `Error<T>` so the retry loop
36/// can decide whether a failure is worth retrying.
37pub(crate) trait Transient {
38    fn transient(&self, idempotent: bool) -> bool;
39}
40
41/// Invoke `f` with retries per `policy`. `f` is re-invoked (rebuilding the request) on transient
42/// failure. `idempotent` gates whether network/5xx errors are retried (429 always is).
43pub(crate) async fn with_retry<F, Fut, R, E>(
44    policy: &RetryPolicy,
45    idempotent: bool,
46    mut f: F,
47) -> Result<R, E>
48where
49    F: FnMut() -> Fut,
50    Fut: std::future::Future<Output = Result<R, E>>,
51    E: Transient,
52{
53    let mut attempt: u32 = 0;
54    loop {
55        match f().await {
56            Ok(r) => return Ok(r),
57            Err(e) => {
58                if attempt >= policy.max_retries || !e.transient(idempotent) {
59                    return Err(e);
60                }
61                let factor = 1u32.checked_shl(attempt.min(16)).unwrap_or(u32::MAX);
62                let delay = policy
63                    .base_delay
64                    .saturating_mul(factor)
65                    .min(policy.max_delay);
66                tokio::time::sleep(delay).await;
67                attempt += 1;
68            }
69        }
70    }
71}