1use std::future::Future;
4use std::time::Duration;
5
6use ferrin_provider_util::retry::retry_after_within;
7use ferrin_spec::error::ProviderError;
8use futures_util::future::Either;
9use futures_util::future::select;
10use rand::RngExt;
11use tokio_util::sync::CancellationToken;
12
13use crate::error::Error;
14use crate::error::RetryReason;
15
16#[derive(Debug, Clone, PartialEq)]
18pub struct RetryPolicy {
19 pub max_retries: u32,
21 pub initial_delay: Duration,
23 pub backoff_factor: f64,
25 pub max_retry_after: Duration,
27 pub jitter: Jitter,
29}
30
31impl Default for RetryPolicy {
32 fn default() -> Self {
33 Self {
34 max_retries: 2,
35 initial_delay: Duration::from_secs(2),
36 backoff_factor: 2.0,
37 max_retry_after: Duration::from_secs(60),
38 jitter: Jitter::None,
39 }
40 }
41}
42
43impl RetryPolicy {
44 #[must_use]
46 pub fn with_max_retries(max_retries: u32) -> Self {
47 Self {
48 max_retries,
49 ..Self::default()
50 }
51 }
52
53 #[must_use]
55 pub fn none() -> Self {
56 Self::with_max_retries(0)
57 }
58
59 #[must_use]
62 pub fn delay_for(&self, retry: u32, error: &ProviderError) -> Duration {
63 let header_delay = error
64 .as_api_call()
65 .and_then(|api| api.response_headers.as_ref())
66 .and_then(|headers| retry_after_within(headers, self.max_retry_after));
67 let base = header_delay.unwrap_or_else(|| {
68 let exponent = retry.saturating_sub(1);
69 let factor = self
70 .backoff_factor
71 .powi(i32::try_from(exponent).unwrap_or(i32::MAX));
72 self.initial_delay.mul_f64(factor.max(0.0))
73 });
74 match self.jitter {
75 Jitter::None => base,
76 Jitter::Full => {
77 let millis = u64::try_from(base.as_millis()).unwrap_or(u64::MAX);
78 if millis == 0 {
79 base
80 } else {
81 Duration::from_millis(rand::rng().random_range(0..=millis))
82 }
83 }
84 }
85 }
86}
87
88#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
90#[non_exhaustive]
91pub enum Jitter {
92 #[default]
94 None,
95 Full,
97}
98
99pub(crate) async fn retry<T, F, Fut>(
107 policy: &RetryPolicy,
108 cancellation: &CancellationToken,
109 operation: F,
110) -> Result<T, Error>
111where
112 F: FnMut(u32) -> Fut,
113 Fut: Future<Output = Result<T, Error>>,
114{
115 retry_with(policy, cancellation, ProviderError::is_retryable, operation).await
116}
117
118pub(crate) async fn retry_with<T, F, Fut>(
121 policy: &RetryPolicy,
122 cancellation: &CancellationToken,
123 is_retryable: impl Fn(&ProviderError) -> bool,
124 mut operation: F,
125) -> Result<T, Error>
126where
127 F: FnMut(u32) -> Fut,
128 Fut: Future<Output = Result<T, Error>>,
129{
130 let mut errors: Vec<ProviderError> = Vec::new();
131 loop {
132 if cancellation.is_cancelled() {
133 return Err(abort_error(errors));
134 }
135 let attempt = u32::try_from(errors.len()).unwrap_or(u32::MAX);
136 match operation(attempt).await {
137 Ok(value) => return Ok(value),
138 Err(Error::Cancelled) => return Err(abort_error(errors)),
139 Err(Error::Provider(error)) => {
140 let error = *error;
141 if policy.max_retries == 0 {
142 return Err(Error::from(error));
143 }
144 let retryable = is_retryable(&error);
145 let retry_number = attempt.saturating_add(1);
146 let delay = policy.delay_for(retry_number, &error);
147 errors.push(error);
148 let attempts = retry_number;
149 if attempts > policy.max_retries {
150 return Err(Error::Retry {
151 reason: RetryReason::MaxRetriesExceeded,
152 attempts,
153 errors,
154 });
155 }
156 if !retryable {
157 if attempts == 1 {
158 return Err(Error::from(
159 errors.pop().unwrap_or(ProviderError::Cancelled),
160 ));
161 }
162 return Err(Error::Retry {
163 reason: RetryReason::ErrorNotRetryable,
164 attempts,
165 errors,
166 });
167 }
168 let sleep = Box::pin(tokio::time::sleep(delay));
169 let cancelled = Box::pin(cancellation.cancelled());
170 if let Either::Right(_) = select(sleep, cancelled).await {
171 return Err(Error::Retry {
172 reason: RetryReason::Abort,
173 attempts,
174 errors,
175 });
176 }
177 }
178 Err(other) => return Err(other),
179 }
180 }
181}
182
183fn abort_error(errors: Vec<ProviderError>) -> Error {
184 if errors.is_empty() {
185 Error::Cancelled
186 } else {
187 Error::Retry {
188 reason: RetryReason::Abort,
189 attempts: u32::try_from(errors.len()).unwrap_or(u32::MAX),
190 errors,
191 }
192 }
193}