Retry an async operation up to `config.max_attempts` times. The closure `f` is called on each attempt. If it returns `Err(e)` and `e.is_retryable()` returns `Some(_)`, the helper sleeps for the computed delay and tries again. Otherwise the error is returned immediately.
(config: &RetryConfig, mut f: F)
| 143 | /// `e.is_retryable()` returns `Some(_)`, the helper sleeps for the computed |
| 144 | /// delay and tries again. Otherwise the error is returned immediately. |
| 145 | pub async fn with_retry<F, Fut, T, E>(config: &RetryConfig, mut f: F) -> Result<T, E> |
| 146 | where |
| 147 | F: FnMut() -> Fut, |
| 148 | Fut: Future<Output = Result<T, E>>, |
| 149 | E: IsRetryable + std::fmt::Debug, |
| 150 | { |
| 151 | let mut attempt: u32 = 0; |
| 152 | |
| 153 | loop { |
| 154 | attempt += 1; |
| 155 | match f().await { |
| 156 | Ok(val) => return Ok(val), |
| 157 | Err(e) => { |
| 158 | if attempt >= config.max_attempts { |
| 159 | return Err(e); |
| 160 | } |
| 161 | match e.is_retryable() { |
| 162 | Some(msg) => { |
| 163 | let delay_ms = delay(attempt, None); |
| 164 | warn!( |
| 165 | attempt, |
| 166 | max = config.max_attempts, |
| 167 | delay_ms, |
| 168 | reason = %msg, |
| 169 | "retrying after transient error" |
| 170 | ); |
| 171 | tokio::time::sleep(std::time::Duration::from_millis(delay_ms)).await; |
| 172 | } |
| 173 | None => return Err(e), |
| 174 | } |
| 175 | } |
| 176 | } |
| 177 | } |
| 178 | } |
| 179 | |
| 180 | /// Same as [`with_retry`] but calls `hook(attempt, &error, delay_ms)` before |
| 181 | /// each retry sleep, giving the caller a chance to log or update UI. |
nothing calls this directly
no test coverage detected