maybe_backoff/
maybe_backoff.rs

1use crate::{backoff::Backoff as _, ExponentialBackoff, ExponentialBackoffBuilder};
2use std::time::Duration;
3
4/// `MaybeBackoff` provides a simplified way to manage an optional exponential backoff while giving control over when to wait.
5///
6/// # Example
7/// ```rust,no_run,ignore
8/// let mut backoff = MaybeBackoff::default();
9///
10/// // Loop that runs fallible operation that should be retried with backoff.
11/// loop {
12///     backoff.sleep().await; // Does nothing when not armed (disarmed by default).
13///     backoff.arm();
14///
15///     while let Some(event) = event_source.next().await {
16///         match event {
17///             Ok(Event::Open) => debug!("Connected!"),
18///             Ok(Event::Message(event)) => match parse(event) {
19///                 Ok(data) => {
20///                     backoff.disarm();
21///                     forward_data(data).await;
22///                     break;
23///                 }
24///                 Err(error) => {
25///                     error!("Parsing failed: {error:?}");
26///                     event_source.close();
27///                     continue;
28///                 }
29///             },
30///             Err(error) => {
31///                 error!("Event source failed: {error:?}");
32///                 event_source.close();
33///                 continue;
34///             }
35///         }
36///     }
37/// }
38/// ```
39#[derive(Default)]
40pub struct MaybeBackoff {
41    backoff: Option<ExponentialBackoff>,
42}
43
44impl MaybeBackoff {
45    pub fn arm(&mut self) {
46        if self.backoff.is_none() {
47            self.backoff = Some(
48                ExponentialBackoffBuilder::new()
49                    .with_initial_interval(Duration::from_millis(50))
50                    .with_max_interval(Duration::from_secs(3))
51                    .with_multiplier(1.5)
52                    .with_randomization_factor(0.2)
53                    .build(),
54            )
55        }
56    }
57
58    pub fn disarm(&mut self) {
59        self.backoff = None;
60    }
61
62    pub async fn sleep(&mut self) {
63        if let Some(duration) = self.backoff.as_mut().and_then(|b| b.next_backoff()) {
64            #[cfg(all(not(target_arch = "wasm32"), not(feature = "tokio")))]
65            std::thread::sleep(duration);
66            #[cfg(all(not(target_arch = "wasm32"), feature = "tokio"))]
67            tokio_1::time::sleep(duration).await;
68            #[cfg(target_arch = "wasm32")]
69            gloo::timers::future::TimeoutFuture::new(duration.as_millis().try_into().unwrap())
70                .await;
71        }
72    }
73}