1use std::future::Future;
4use std::time::Duration;
5
6#[derive(Debug, Clone, Copy)]
8pub struct RetryPolicy {
9 pub max_attempts: u32,
10 pub base_delay: Duration,
11}
12
13impl RetryPolicy {
14 pub fn with_max_attempts(max_attempts: u32) -> Self {
15 Self {
16 max_attempts: max_attempts.max(1),
17 base_delay: Duration::from_millis(100),
18 }
19 }
20}
21
22#[derive(Debug)]
24pub struct RetryError<E> {
25 source: Option<E>,
26 timed_out: bool,
27}
28
29impl<E> RetryError<E> {
30 pub fn into_source(self) -> Option<E> {
31 self.source
32 }
33
34 pub fn timed_out(&self) -> bool {
35 self.timed_out
36 }
37}
38
39pub async fn with_timeout<T, E, F>(fut: F, timeout: Duration) -> Result<T, RetryError<E>>
41where
42 F: Future<Output = Result<T, E>>,
43{
44 match tokio::time::timeout(timeout, fut).await {
45 Ok(Ok(v)) => Ok(v),
46 Ok(Err(e)) => Err(RetryError {
47 source: Some(e),
48 timed_out: false,
49 }),
50 Err(_) => Err(RetryError {
51 source: None,
52 timed_out: true,
53 }),
54 }
55}
56
57pub async fn retry<T, E, Fut, Make, Pred>(
59 mut make_fut: Make,
60 policy: &RetryPolicy,
61 is_retryable: Pred,
62) -> Result<T, RetryError<E>>
63where
64 Make: FnMut() -> Fut,
65 Fut: Future<Output = Result<T, E>>,
66 Pred: Fn(&E) -> bool,
67{
68 let mut attempt = 0u32;
69 loop {
70 attempt += 1;
71 match make_fut().await {
72 Ok(v) => return Ok(v),
73 Err(e) => {
74 if attempt >= policy.max_attempts || !is_retryable(&e) {
75 return Err(RetryError {
76 source: Some(e),
77 timed_out: false,
78 });
79 }
80 let delay = policy.base_delay.saturating_mul(1 << (attempt - 1).min(4));
81 tokio::time::sleep(delay).await;
82 }
83 }
84 }
85}