wayback_rs/util/
retries.rs1use core::pin::Pin;
3use futures::{
4 task::{Context, Poll},
5 Future,
6};
7use log::{log, Level};
8use std::fmt::Debug;
9use std::marker::PhantomData;
10use std::time::Duration;
11use tryhard::{
12 backoff_strategies::BackoffStrategy, OnRetry, RetryFuture, RetryFutureConfig, RetryPolicy,
13};
14
15pub fn retry_future<F, Fut, T, E>(f: F) -> RetryFuture<F, Fut, ErrorBackoff<E>, LogOnRetry>
17where
18 F: FnMut() -> Fut,
19 Fut: Future<Output = Result<T, E>>,
20 E: Retryable,
21{
22 tryhard::retry_fn(f).with_config(E::retry_config())
23}
24
25pub struct LogFuture {
26 level: Option<Level>,
27 message: Option<String>,
28}
29
30impl Future for LogFuture {
31 type Output = ();
32 fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
33 if let Some(level) = self.level {
34 log!(
35 level,
36 "{}",
37 self.message
38 .take()
39 .expect("LogFuture polled after completion")
40 );
41 }
42
43 Poll::Ready(())
44 }
45}
46
47pub struct LogOnRetry {
48 level: Option<Level>,
49}
50
51impl<E: Debug> OnRetry<E> for LogOnRetry {
52 type Future = LogFuture;
53
54 fn on_retry(
55 &mut self,
56 attempts: u32,
57 next_delay: Option<Duration>,
58 previous_error: &E,
59 ) -> Self::Future {
60 match next_delay {
61 Some(delay) => {
62 let message = if self.level.is_none() {
63 None
64 } else {
65 Some(format!(
66 "Retry {}; waiting {:?} after error: {:?}",
67 attempts, delay, previous_error
68 ))
69 };
70 LogFuture {
71 level: self.level,
72 message,
73 }
74 }
75 None => LogFuture {
76 level: None,
77 message: None,
78 },
79 }
80 }
81}
82
83pub struct ErrorBackoff<E>
84where
85 E: ?Sized,
86{
87 delay: Duration,
88 _error: PhantomData<E>,
89}
90
91impl<'a, E: Retryable> BackoffStrategy<'a, E> for ErrorBackoff<E> {
92 type Output = RetryPolicy;
93
94 fn delay(&mut self, _attempt: u32, error: &'a E) -> RetryPolicy {
95 error.custom_retry_policy().unwrap_or_else(|| {
96 let prev_delay = self.delay;
97 self.delay *= 2;
98 RetryPolicy::Delay(prev_delay)
99 })
100 }
101}
102
103pub trait Retryable {
106 fn max_retries() -> u32;
108
109 fn default_initial_delay() -> Duration;
111
112 fn log_level() -> Option<Level>;
115
116 fn custom_retry_policy(&self) -> Option<RetryPolicy>;
120
121 fn new_backoff() -> ErrorBackoff<Self> {
123 ErrorBackoff {
124 delay: Self::default_initial_delay(),
125 _error: PhantomData,
126 }
127 }
128
129 fn retry_config() -> RetryFutureConfig<ErrorBackoff<Self>, LogOnRetry> {
131 RetryFutureConfig::new(Self::max_retries())
132 .on_retry(LogOnRetry {
133 level: Self::log_level(),
134 })
135 .custom_backoff(Self::new_backoff())
136 }
137}