Skip to main content

shared/
retry.rs

1use std::{
2    sync::LazyLock,
3    time::{Duration, Instant},
4};
5
6const STARTUP_BUDGET: Duration = Duration::from_secs(10);
7const INITIAL_BACKOFF: Duration = Duration::from_millis(250);
8const MAX_BACKOFF: Duration = Duration::from_secs(2);
9
10static STARTUP_DEADLINE: LazyLock<Instant> = LazyLock::new(|| Instant::now() + STARTUP_BUDGET);
11
12pub async fn startup_connect<
13    T,
14    E: std::fmt::Display,
15    F: FnMut() -> Fut,
16    Fut: Future<Output = Result<T, E>>,
17>(
18    label: &str,
19    mut attempt: F,
20) -> T {
21    let deadline = *STARTUP_DEADLINE;
22    let mut backoff = INITIAL_BACKOFF;
23
24    loop {
25        let err = match attempt().await {
26            Ok(value) => return value,
27            Err(err) => err,
28        };
29
30        let remaining = deadline.saturating_duration_since(Instant::now());
31        if remaining.is_zero() {
32            return crate::utils::handle_startup_error(anyhow::anyhow!(
33                "connecting to the {label} failed: {err}"
34            ));
35        }
36
37        let wait = backoff.min(remaining);
38        tracing::warn!(
39            "connecting to the {label} failed ({err}), retrying in {:.2}s",
40            wait.as_secs_f64()
41        );
42
43        tokio::time::sleep(wait).await;
44        backoff = (backoff * 2).min(MAX_BACKOFF);
45    }
46}