1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
//! # sisyphus
//!
//! A generic, **execution-agnostic** and **time-agnostic** backoff & retry
//! utility, built as a pure state machine.
//!
//! Sisyphus was condemned by the gods to roll a boulder up a hill for eternity,
//! only to watch it roll back down every time — the original retry loop. This
//! crate is the part of that punishment worth keeping: it computes *when* to
//! push the boulder again, and nothing else.
//!
//! ## Why another retry crate?
//!
//! Most retry crates (e.g. `backoff`) bake in `std::time::Instant` and
//! `std::thread::sleep`, coupling *policy* (how long to wait) with *execution*
//! (how to wait). That falls apart in:
//!
//! * **WebAssembly engines** executing Wasm directly, with no host threads;
//! * **Deterministic blockchain / consensus** state machines, where wall-clock
//! reads and panics are forbidden;
//! * **`no_std` embedded** targets with no allocator and no `std::time`.
//!
//! `sisyphus` solves this by being a pure state machine:
//!
//! * `#![no_std]`, zero allocation, no `dyn` (unless you opt in).
//! * Time is just [`core::time::Duration`]; instants come from your [`Clock`].
//! * Randomness/jitter is injected via [`Jitter`] (deterministic by default).
//! * Execution is driven by *your* `sleep` — sync closure or async future.
//!
//! ## 30-second tour
//!
//! ```
//! use core::ops::ControlFlow;
//! use core::time::Duration;
//! use sisyphus::{retry_sync, ExponentialBackoff, PolicyExt, RetryError};
//!
//! // 1. Build a policy by composing pure state machines.
//! let policy = ExponentialBackoff::new(Duration::from_millis(50), 2.0)
//! .with_max_delay(Duration::from_secs(5))
//! .max_attempts(4);
//!
//! // 2. Drive it with your own operation + sleep. Here `sleep` just advances
//! // a virtual clock, so the example runs instantly and deterministically.
//! let mut tries = 0u32;
//! let mut elapsed = Duration::ZERO;
//! let result: Result<&str, RetryError<&str>> = retry_sync(
//! policy,
//! || {
//! tries += 1;
//! if tries >= 3 { ControlFlow::Break("connected") }
//! else { ControlFlow::Continue("connection refused") }
//! },
//! |delay| elapsed += delay, // plug in your real sleep in production
//! );
//!
//! assert_eq!(result, Ok("connected"));
//! ```
//!
//! ## Feature flags
//!
//! | feature | default | adds |
//! |---------|---------|------|
//! | `alloc` | no | [`BoxedPolicy`], `impl BackoffPolicy for Box<dyn …>` |
//! | `std` | no | [`SystemClock`] backed by `std::time::Instant` |
//!
//! The default build pulls in neither, keeping it pure-`core` and
//! allocation-free.
extern crate std;
extern crate alloc;
pub use Clock;
pub use SystemClock;
pub use ;
pub use ;
pub use ;
/// Optional dynamic-dispatch support, gated behind the `alloc` feature.
///
/// Static dispatch (generics) is the default and the recommended path — it is
/// what keeps the crate allocation-free. But sometimes you genuinely need to
/// store policies of differing concrete types in one place (e.g. a config-driven
/// table of policies). For those cases only, enable `alloc` and use a
/// [`BoxedPolicy`].
pub use BoxedPolicy;