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}