Skip to main content

async_retry/
async_retry.rs

1//! `retry_async` is runtime-agnostic: it only speaks `core::future::Future`.
2//! This example proves it by driving the retry loop with a ~20-line hand-rolled
3//! executor and a sleep future that's ready immediately — no Tokio, async-std,
4//! or any runtime dependency. In real code you'd pass `tokio::time::sleep`,
5//! `embassy_time::Timer`, or a browser/WASM timer instead.
6//!
7//! Run with:
8//!
9//! ```text
10//! cargo run --example async_retry
11//! ```
12
13use std::future::Future;
14use std::ops::ControlFlow;
15use std::pin::Pin;
16use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
17use std::time::Duration;
18
19use sisyphus::{retry_async, ExponentialBackoff, PolicyExt, RetryError};
20
21/// A stand-in for a real async timer; ready on first poll.
22async fn fake_sleep(delay: Duration) {
23    println!("  (would await timer for {delay:?})");
24}
25
26async fn run() -> Result<u32, RetryError<&'static str>> {
27    let policy = ExponentialBackoff::new(Duration::from_millis(10), 2.0).max_attempts(5);
28    let mut attempt = 0u32;
29
30    retry_async(
31        policy,
32        || {
33            attempt += 1;
34            async move {
35                println!("async attempt {attempt}");
36                if attempt < 4 {
37                    ControlFlow::Continue("service unavailable")
38                } else {
39                    ControlFlow::Break(attempt)
40                }
41            }
42        },
43        // Swap this closure for your runtime's real sleep in production.
44        fake_sleep,
45    )
46    .await
47}
48
49fn main() {
50    match block_on(run()) {
51        Ok(attempts) => println!("succeeded on attempt {attempts}"),
52        Err(RetryError::Exhausted(last)) => println!("exhausted; last state: {last}"),
53    }
54}
55
56/// A minimal `block_on` so this example needs zero async dependencies.
57fn block_on<F: Future>(mut fut: F) -> F::Output {
58    fn noop(_: *const ()) {}
59    fn clone(_: *const ()) -> RawWaker {
60        RawWaker::new(std::ptr::null(), &VTABLE)
61    }
62    static VTABLE: RawWakerVTable = RawWakerVTable::new(clone, noop, noop, noop);
63
64    // Safety: the waker is a no-op and never dereferences its data pointer.
65    let waker = unsafe { Waker::from_raw(RawWaker::new(std::ptr::null(), &VTABLE)) };
66    let mut cx = Context::from_waker(&waker);
67    // Safety: `fut` lives on the stack for the duration of this function and is
68    // never moved after being pinned.
69    let mut fut = unsafe { Pin::new_unchecked(&mut fut) };
70    loop {
71        if let Poll::Ready(v) = fut.as_mut().poll(&mut cx) {
72            return v;
73        }
74    }
75}