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
//! Small, runtime-agnostic retry helpers for fallible operations.
//!
//! `reliakit-retry` turns a [`Backoff`] schedule and an attempt limit into a
//! [`RetryPolicy`], then drives a fallible operation against it — synchronously
//! or asynchronously. It is deliberately minimal: it decides *whether* to retry
//! and *how long* the gap should be, but it never sleeps, spawns, or assumes an
//! async runtime. You inject the waiting.
//!
//! It has no third-party dependencies, forbids unsafe code, and is
//! `no_std`-friendly (it needs no allocation and no clock).
//!
//! # Why it does not sleep
//!
//! Blocking the current thread (`std::thread::sleep`) or awaiting a runtime
//! timer is hidden runtime behavior, and it ties a small helper to one
//! execution model. Instead:
//!
//! - [`retry`] runs attempts back-to-back and never waits.
//! - [`retry_with_sleep`] hands each backoff [`Duration`](core::time::Duration)
//! to a `sleep` closure *you* provide (e.g. one that calls your timer).
//! - [`retry_async`] awaits a `sleep` future *you* provide, so it works under
//! any executor without depending on Tokio, async-std, or `futures`.
//!
//! # Attempt counting
//!
//! [`RetryPolicy::max_attempts`] is the *total* number of attempts, including
//! the first:
//!
//! - `max_attempts = 1` → try once, never retry (the backoff is never used).
//! - `max_attempts = 3` → the first try plus up to two retries.
//! - `max_attempts = 0` is rejected by [`RetryPolicy::new`] (returns `None`).
//!
//! The attempt count is the single authority for how many times the operation
//! runs. The [`Backoff`] is consulted only for the delay *before each retry*
//! (retry `0` is the first retry, zero-based); if it yields no delay, the gap is
//! [`Duration::ZERO`](core::time::Duration::ZERO). The two limits therefore
//! never conflict.
//!
//! # Retry predicate
//!
//! Every helper takes a `should_retry: FnMut(&E) -> bool` classifier. Returning
//! `false` stops immediately — use it to retry only transient errors and fail
//! fast on permanent ones. It is consulted only when another attempt is actually
//! possible (so it is never called when `max_attempts` is already reached).
//!
//! # Example — sync, no sleeping
//!
//! ```
//! use core::time::Duration;
//! use reliakit_retry::{retry, Backoff, RetryError, RetryPolicy};
//!
//! let policy = RetryPolicy::new(3, Backoff::constant(Duration::from_millis(10))).unwrap();
//!
//! let mut calls = 0;
//! let result: Result<u32, RetryError<&str>> = retry(
//! &policy,
//! || {
//! calls += 1;
//! if calls < 2 { Err("temporary") } else { Ok(42) }
//! },
//! |_error| true, // retry every error
//! );
//!
//! assert_eq!(result.unwrap(), 42);
//! assert_eq!(calls, 2);
//! ```
//!
//! # Example — sync, with an injected sleeper
//!
//! ```
//! use core::time::Duration;
//! use reliakit_retry::{retry_with_sleep, Backoff, RetryError, RetryPolicy};
//!
//! let policy = RetryPolicy::new(4, Backoff::exponential(Duration::from_millis(1), 2)).unwrap();
//!
//! // Record the delays instead of really sleeping (a real caller would wait).
//! let mut waited: Vec<Duration> = Vec::new();
//! let mut attempts = 0;
//! let result: Result<(), RetryError<&str>> = retry_with_sleep(
//! &policy,
//! || { attempts += 1; Err("always fails") },
//! |_error| true,
//! |delay| waited.push(delay),
//! );
//!
//! assert!(matches!(result, Err(RetryError::Exhausted { attempts: 4, .. })));
//! // Three gaps before retries 2, 3, 4: 1ms, 2ms, 4ms.
//! assert_eq!(waited, [Duration::from_millis(1), Duration::from_millis(2), Duration::from_millis(4)]);
//! ```
//!
//! For the async helper, see [`retry_async`] and the `async_retry` example,
//! which drives it without any runtime.
//!
//! # Feature flags
//!
//! - `std` (default) adds `impl std::error::Error for RetryError`. With
//! `--no-default-features` the crate is pure `core`: no allocation, no clock,
//! no runtime.
//!
//! # What this is not
//!
//! This is a small retry helper, not a framework, middleware stack, async
//! runtime, or a Tower replacement. It does not log, spawn, time, or schedule on
//! your behalf.
pub use RetryError;
pub use RetryPolicy;
pub use ;
/// Re-exported from `reliakit-backoff` so the backoff schedule is reachable
/// without a separate dependency line.
pub use Backoff;