use anyhow::Result;
use std::future::Future;
use std::pin::Pin;
use std::time::Duration;
use tokio::sync::watch;
use tokio::time::sleep;
pub async fn retry_with_exponential_backoff<F, T, E>(
mut operation: F,
max_retries: u32,
base_timeout: Duration,
cancellation_token: Option<&watch::Receiver<bool>>,
) -> Result<T, E>
where
F: FnMut() -> Pin<Box<dyn Future<Output = Result<T, E>> + Send>>,
E: std::fmt::Display,
{
let mut last_error = None;
for attempt in 0..=max_retries {
if let Some(token) = cancellation_token {
if *token.borrow() {
return Err(last_error.unwrap_or_else(|| {
panic!("Request cancelled before any attempt")
}));
}
}
match operation().await {
Ok(result) => return Ok(result),
Err(e) => {
eprintln!("🔄 API request attempt {} failed: {}", attempt + 1, e);
last_error = Some(e);
if attempt < max_retries {
let delay = base_timeout * 2_u32.pow(attempt);
let delay = std::cmp::min(delay, Duration::from_secs(300));
eprintln!(
"🔄 Waiting {:?} before retry attempt {}",
delay,
attempt + 2
);
sleep(delay).await;
}
}
}
}
Err(last_error.unwrap())
}
pub async fn retry_http_request<T, E>(
max_retries: u32,
base_timeout: Duration,
cancellation_token: Option<&watch::Receiver<bool>>,
request_builder: impl Fn() -> Pin<Box<dyn Future<Output = Result<T, E>> + Send>>,
) -> Result<T, E>
where
E: std::fmt::Display,
{
retry_with_exponential_backoff(
|| request_builder(),
max_retries,
base_timeout,
cancellation_token,
)
.await
}