chrono_machines 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/Cargo.lock +679 -0
- data/Cargo.toml +7 -0
- data/lib/chrono_machines/version.rb +1 -1
- data/lib/chrono_machines.rb +1 -0
- metadata +4 -20
- data/ext/chrono_machines_native/target/debug/build/clang-sys-414c893ed9493fe4/out/common.rs +0 -355
- data/ext/chrono_machines_native/target/debug/build/clang-sys-414c893ed9493fe4/out/dynamic.rs +0 -276
- data/ext/chrono_machines_native/target/debug/build/clang-sys-414c893ed9493fe4/out/macros.rs +0 -49
- data/ext/chrono_machines_native/target/debug/build/clang-sys-b1d0ba329a011cdd/out/common.rs +0 -355
- data/ext/chrono_machines_native/target/debug/build/clang-sys-b1d0ba329a011cdd/out/dynamic.rs +0 -276
- data/ext/chrono_machines_native/target/debug/build/clang-sys-b1d0ba329a011cdd/out/macros.rs +0 -49
- data/ext/chrono_machines_native/target/debug/build/rb-sys-7d416123f8551038/out/bindings-0.9.124-mri-arm64-darwin23-4.0.3.rs +0 -9057
- data/ext/chrono_machines_native/target/debug/build/rb-sys-848783778984ec5d/out/bindings-0.9.124-mri-arm64-darwin23-3.4.8.rs +0 -8933
- data/ext/chrono_machines_native/target/debug/build/rb-sys-ce17442a66050c01/out/bindings-0.9.128-mri-arm64-darwin23-3.4.8.rs +0 -8933
- data/ext/chrono_machines_native/target/package/chrono-machines-0.3.2/Cargo.toml +0 -76
- data/ext/chrono_machines_native/target/package/chrono-machines-0.3.2/src/backoff.rs +0 -497
- data/ext/chrono_machines_native/target/package/chrono-machines-0.3.2/src/dsl.rs +0 -108
- data/ext/chrono_machines_native/target/package/chrono-machines-0.3.2/src/lib.rs +0 -352
- data/ext/chrono_machines_native/target/package/chrono-machines-0.3.2/src/policy.rs +0 -189
- data/ext/chrono_machines_native/target/package/chrono-machines-0.3.2/src/retry.rs +0 -1489
- data/ext/chrono_machines_native/target/package/chrono-machines-0.3.2/src/sleep.rs +0 -97
- data/ext/chrono_machines_native/target/package/chrono_machines_native-0.2.3/Cargo.toml +0 -56
- data/ext/chrono_machines_native/target/package/chrono_machines_native-0.2.3/src/lib.rs +0 -192
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
|
2
|
-
#
|
|
3
|
-
# When uploading crates to the registry Cargo will automatically
|
|
4
|
-
# "normalize" Cargo.toml files for maximal compatibility
|
|
5
|
-
# with all versions of Cargo and also rewrite `path` dependencies
|
|
6
|
-
# to registry (e.g., crates.io) dependencies.
|
|
7
|
-
#
|
|
8
|
-
# If you are reading this file be aware that the original Cargo.toml
|
|
9
|
-
# will likely look very different (and much more reasonable).
|
|
10
|
-
# See Cargo.toml.orig for the original contents.
|
|
11
|
-
|
|
12
|
-
[package]
|
|
13
|
-
edition = "2024"
|
|
14
|
-
name = "chrono-machines"
|
|
15
|
-
version = "0.3.2"
|
|
16
|
-
authors = ["Abdelkader Boudih <terminale@gmail.com>"]
|
|
17
|
-
build = false
|
|
18
|
-
autolib = false
|
|
19
|
-
autobins = false
|
|
20
|
-
autoexamples = false
|
|
21
|
-
autotests = false
|
|
22
|
-
autobenches = false
|
|
23
|
-
description = "Exponential, constant, and Fibonacci backoff retry library with full jitter support - no_std compatible"
|
|
24
|
-
readme = "README.md"
|
|
25
|
-
keywords = [
|
|
26
|
-
"retry",
|
|
27
|
-
"backoff",
|
|
28
|
-
"exponential",
|
|
29
|
-
"jitter",
|
|
30
|
-
"resilience",
|
|
31
|
-
]
|
|
32
|
-
categories = [
|
|
33
|
-
"no-std",
|
|
34
|
-
"algorithms",
|
|
35
|
-
]
|
|
36
|
-
license = "MIT"
|
|
37
|
-
repository = "https://github.com/seuros/chrono_machines"
|
|
38
|
-
resolver = "2"
|
|
39
|
-
|
|
40
|
-
[features]
|
|
41
|
-
alloc = []
|
|
42
|
-
async = [
|
|
43
|
-
"std",
|
|
44
|
-
"dep:async-trait",
|
|
45
|
-
]
|
|
46
|
-
default = ["std"]
|
|
47
|
-
std = [
|
|
48
|
-
"alloc",
|
|
49
|
-
"rand/std",
|
|
50
|
-
"rand/std_rng",
|
|
51
|
-
]
|
|
52
|
-
|
|
53
|
-
[lib]
|
|
54
|
-
name = "chrono_machines"
|
|
55
|
-
crate-type = ["lib"]
|
|
56
|
-
path = "src/lib.rs"
|
|
57
|
-
|
|
58
|
-
[dependencies.async-trait]
|
|
59
|
-
version = "0.1"
|
|
60
|
-
optional = true
|
|
61
|
-
|
|
62
|
-
[dependencies.rand]
|
|
63
|
-
version = "0.10"
|
|
64
|
-
features = [
|
|
65
|
-
"std_rng",
|
|
66
|
-
"sys_rng",
|
|
67
|
-
]
|
|
68
|
-
default-features = false
|
|
69
|
-
|
|
70
|
-
[dev-dependencies.tokio]
|
|
71
|
-
version = "1"
|
|
72
|
-
features = [
|
|
73
|
-
"rt",
|
|
74
|
-
"macros",
|
|
75
|
-
"time",
|
|
76
|
-
]
|
|
@@ -1,497 +0,0 @@
|
|
|
1
|
-
//! Backoff strategy implementations for retry mechanisms
|
|
2
|
-
//!
|
|
3
|
-
//! This module provides various backoff strategies to control delay timing
|
|
4
|
-
//! between retry attempts.
|
|
5
|
-
|
|
6
|
-
use rand::Rng;
|
|
7
|
-
use rand::RngExt;
|
|
8
|
-
|
|
9
|
-
/// Calculate the nth Fibonacci number (1-indexed): 1, 1, 2, 3, 5, 8, 13, ...
|
|
10
|
-
pub fn fibonacci(n: u8) -> u64 {
|
|
11
|
-
match n {
|
|
12
|
-
0 => 0,
|
|
13
|
-
1 | 2 => 1,
|
|
14
|
-
_ => {
|
|
15
|
-
let mut a = 1u64;
|
|
16
|
-
let mut b = 1u64;
|
|
17
|
-
for _ in 2..n {
|
|
18
|
-
let next = a.saturating_add(b);
|
|
19
|
-
a = b;
|
|
20
|
-
b = next;
|
|
21
|
-
}
|
|
22
|
-
b
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/// Blend a base delay with jitter using the full-jitter algorithm.
|
|
28
|
-
///
|
|
29
|
-
/// `jitter_factor` is clamped to `[0.0, 1.0]`: `0.0` returns `base` unchanged,
|
|
30
|
-
/// `1.0` yields a uniform value in `[0, base]`.
|
|
31
|
-
fn apply_jitter<R: Rng>(base: f64, jitter_factor: f64, rng: &mut R) -> u64 {
|
|
32
|
-
let jitter_factor = jitter_factor.clamp(0.0, 1.0);
|
|
33
|
-
let random_scalar: f64 = rng.random_range(0.0..=1.0);
|
|
34
|
-
let jitter_blend = 1.0 - jitter_factor + random_scalar * jitter_factor;
|
|
35
|
-
(base * jitter_blend) as u64
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/// Trait for backoff strategies that calculate delays between retry attempts
|
|
39
|
-
pub trait BackoffStrategy {
|
|
40
|
-
/// Calculate the delay in milliseconds for the given attempt number
|
|
41
|
-
///
|
|
42
|
-
/// # Arguments
|
|
43
|
-
///
|
|
44
|
-
/// * `attempt` - Current attempt number (1-indexed)
|
|
45
|
-
/// * `rng` - Random number generator for jitter
|
|
46
|
-
///
|
|
47
|
-
/// # Returns
|
|
48
|
-
///
|
|
49
|
-
/// Delay in milliseconds, or `None` if retries should stop
|
|
50
|
-
fn delay<R: Rng>(&self, attempt: u8, rng: &mut R) -> Option<u64>;
|
|
51
|
-
|
|
52
|
-
/// Check if another retry should be attempted
|
|
53
|
-
///
|
|
54
|
-
/// # Arguments
|
|
55
|
-
///
|
|
56
|
-
/// * `attempt` - Current attempt number (1-indexed)
|
|
57
|
-
///
|
|
58
|
-
/// # Returns
|
|
59
|
-
///
|
|
60
|
-
/// `true` if another retry is allowed, `false` otherwise
|
|
61
|
-
fn should_retry(&self, attempt: u8) -> bool;
|
|
62
|
-
|
|
63
|
-
/// Maximum number of retry attempts permitted by this strategy.
|
|
64
|
-
fn max_attempts(&self) -> u8;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/// Exponential backoff strategy with configurable jitter
|
|
68
|
-
///
|
|
69
|
-
/// Delays grow exponentially: base_delay * multiplier^(attempt-1)
|
|
70
|
-
///
|
|
71
|
-
/// # Example
|
|
72
|
-
///
|
|
73
|
-
/// ```rust
|
|
74
|
-
/// use chrono_machines::ExponentialBackoff;
|
|
75
|
-
///
|
|
76
|
-
/// let backoff = ExponentialBackoff::new()
|
|
77
|
-
/// .base_delay_ms(100)
|
|
78
|
-
/// .multiplier(2.0)
|
|
79
|
-
/// .max_delay_ms(10_000)
|
|
80
|
-
/// .max_attempts(5)
|
|
81
|
-
/// .jitter_factor(1.0); // Full jitter
|
|
82
|
-
/// ```
|
|
83
|
-
#[derive(Debug, Clone, Copy)]
|
|
84
|
-
pub struct ExponentialBackoff {
|
|
85
|
-
/// Maximum number of retry attempts
|
|
86
|
-
pub max_attempts: u8,
|
|
87
|
-
/// Base delay in milliseconds
|
|
88
|
-
pub base_delay_ms: u64,
|
|
89
|
-
/// Exponential backoff multiplier
|
|
90
|
-
pub multiplier: f64,
|
|
91
|
-
/// Maximum delay cap in milliseconds
|
|
92
|
-
pub max_delay_ms: u64,
|
|
93
|
-
/// Jitter factor (0.0 = no jitter, 1.0 = full jitter)
|
|
94
|
-
pub jitter_factor: f64,
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
impl ExponentialBackoff {
|
|
98
|
-
/// Create a new exponential backoff builder with default values
|
|
99
|
-
pub fn new() -> Self {
|
|
100
|
-
Self::default()
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/// Set the base delay in milliseconds
|
|
104
|
-
pub fn base_delay_ms(mut self, ms: u64) -> Self {
|
|
105
|
-
self.base_delay_ms = ms;
|
|
106
|
-
self
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/// Set the exponential multiplier
|
|
110
|
-
pub fn multiplier(mut self, multiplier: f64) -> Self {
|
|
111
|
-
self.multiplier = multiplier;
|
|
112
|
-
self
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/// Set the maximum delay cap in milliseconds
|
|
116
|
-
pub fn max_delay_ms(mut self, ms: u64) -> Self {
|
|
117
|
-
self.max_delay_ms = ms;
|
|
118
|
-
self
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/// Set the maximum number of attempts
|
|
122
|
-
pub fn max_attempts(mut self, attempts: u8) -> Self {
|
|
123
|
-
self.max_attempts = attempts;
|
|
124
|
-
self
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/// Set the jitter factor (0.0 = no jitter, 1.0 = full jitter)
|
|
128
|
-
pub fn jitter_factor(mut self, factor: f64) -> Self {
|
|
129
|
-
self.jitter_factor = factor.clamp(0.0, 1.0);
|
|
130
|
-
self
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
impl Default for ExponentialBackoff {
|
|
135
|
-
fn default() -> Self {
|
|
136
|
-
Self {
|
|
137
|
-
max_attempts: 3,
|
|
138
|
-
base_delay_ms: 100,
|
|
139
|
-
multiplier: 2.0,
|
|
140
|
-
max_delay_ms: 10_000,
|
|
141
|
-
jitter_factor: 1.0, // Full jitter by default
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
impl BackoffStrategy for ExponentialBackoff {
|
|
147
|
-
fn delay<R: Rng>(&self, attempt: u8, rng: &mut R) -> Option<u64> {
|
|
148
|
-
if attempt >= self.max_attempts {
|
|
149
|
-
return None;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
let exponent = attempt.saturating_sub(1) as i32;
|
|
153
|
-
let base_exponential = (self.base_delay_ms as f64) * self.multiplier.powi(exponent);
|
|
154
|
-
let capped = base_exponential.min(self.max_delay_ms as f64);
|
|
155
|
-
|
|
156
|
-
Some(apply_jitter(capped, self.jitter_factor, rng))
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
fn should_retry(&self, attempt: u8) -> bool {
|
|
160
|
-
attempt < self.max_attempts
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
fn max_attempts(&self) -> u8 {
|
|
164
|
-
self.max_attempts
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/// Constant backoff strategy with fixed delay
|
|
169
|
-
///
|
|
170
|
-
/// All retry delays are the same constant value.
|
|
171
|
-
///
|
|
172
|
-
/// # Example
|
|
173
|
-
///
|
|
174
|
-
/// ```rust
|
|
175
|
-
/// use chrono_machines::ConstantBackoff;
|
|
176
|
-
///
|
|
177
|
-
/// let backoff = ConstantBackoff::new()
|
|
178
|
-
/// .delay_ms(500)
|
|
179
|
-
/// .max_attempts(5)
|
|
180
|
-
/// .jitter_factor(0.1); // 10% jitter
|
|
181
|
-
/// ```
|
|
182
|
-
#[derive(Debug, Clone, Copy)]
|
|
183
|
-
pub struct ConstantBackoff {
|
|
184
|
-
/// Fixed delay in milliseconds
|
|
185
|
-
pub delay_ms: u64,
|
|
186
|
-
/// Maximum number of retry attempts
|
|
187
|
-
pub max_attempts: u8,
|
|
188
|
-
/// Jitter factor (0.0 = no jitter, 1.0 = full jitter)
|
|
189
|
-
pub jitter_factor: f64,
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
impl ConstantBackoff {
|
|
193
|
-
/// Create a new constant backoff builder with default values
|
|
194
|
-
pub fn new() -> Self {
|
|
195
|
-
Self::default()
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/// Set the constant delay in milliseconds
|
|
199
|
-
pub fn delay_ms(mut self, ms: u64) -> Self {
|
|
200
|
-
self.delay_ms = ms;
|
|
201
|
-
self
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/// Set the maximum number of attempts
|
|
205
|
-
pub fn max_attempts(mut self, attempts: u8) -> Self {
|
|
206
|
-
self.max_attempts = attempts;
|
|
207
|
-
self
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/// Set the jitter factor (0.0 = no jitter, 1.0 = full jitter)
|
|
211
|
-
pub fn jitter_factor(mut self, factor: f64) -> Self {
|
|
212
|
-
self.jitter_factor = factor.clamp(0.0, 1.0);
|
|
213
|
-
self
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
impl Default for ConstantBackoff {
|
|
218
|
-
fn default() -> Self {
|
|
219
|
-
Self {
|
|
220
|
-
delay_ms: 100,
|
|
221
|
-
max_attempts: 3,
|
|
222
|
-
jitter_factor: 0.0, // No jitter for constant by default
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
impl BackoffStrategy for ConstantBackoff {
|
|
228
|
-
fn delay<R: Rng>(&self, attempt: u8, rng: &mut R) -> Option<u64> {
|
|
229
|
-
if attempt >= self.max_attempts {
|
|
230
|
-
return None;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
Some(apply_jitter(self.delay_ms as f64, self.jitter_factor, rng))
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
fn should_retry(&self, attempt: u8) -> bool {
|
|
237
|
-
attempt < self.max_attempts
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
fn max_attempts(&self) -> u8 {
|
|
241
|
-
self.max_attempts
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/// Fibonacci backoff strategy
|
|
246
|
-
///
|
|
247
|
-
/// Delays follow the Fibonacci sequence: 1, 1, 2, 3, 5, 8, 13, ...
|
|
248
|
-
/// Each delay is base_delay_ms * fibonacci(attempt).
|
|
249
|
-
///
|
|
250
|
-
/// # Example
|
|
251
|
-
///
|
|
252
|
-
/// ```rust
|
|
253
|
-
/// use chrono_machines::FibonacciBackoff;
|
|
254
|
-
///
|
|
255
|
-
/// let backoff = FibonacciBackoff::new()
|
|
256
|
-
/// .base_delay_ms(100) // 100ms, 100ms, 200ms, 300ms, 500ms...
|
|
257
|
-
/// .max_delay_ms(5_000)
|
|
258
|
-
/// .max_attempts(8)
|
|
259
|
-
/// .jitter_factor(0.5); // 50% jitter
|
|
260
|
-
/// ```
|
|
261
|
-
#[derive(Debug, Clone, Copy)]
|
|
262
|
-
pub struct FibonacciBackoff {
|
|
263
|
-
/// Base delay in milliseconds (multiplied by Fibonacci number)
|
|
264
|
-
pub base_delay_ms: u64,
|
|
265
|
-
/// Maximum delay cap in milliseconds
|
|
266
|
-
pub max_delay_ms: u64,
|
|
267
|
-
/// Maximum number of retry attempts
|
|
268
|
-
pub max_attempts: u8,
|
|
269
|
-
/// Jitter factor (0.0 = no jitter, 1.0 = full jitter)
|
|
270
|
-
pub jitter_factor: f64,
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
impl FibonacciBackoff {
|
|
274
|
-
/// Create a new Fibonacci backoff builder with default values
|
|
275
|
-
pub fn new() -> Self {
|
|
276
|
-
Self::default()
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
/// Set the base delay in milliseconds
|
|
280
|
-
pub fn base_delay_ms(mut self, ms: u64) -> Self {
|
|
281
|
-
self.base_delay_ms = ms;
|
|
282
|
-
self
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
/// Set the maximum delay cap in milliseconds
|
|
286
|
-
pub fn max_delay_ms(mut self, ms: u64) -> Self {
|
|
287
|
-
self.max_delay_ms = ms;
|
|
288
|
-
self
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
/// Set the maximum number of attempts
|
|
292
|
-
pub fn max_attempts(mut self, attempts: u8) -> Self {
|
|
293
|
-
self.max_attempts = attempts;
|
|
294
|
-
self
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/// Set the jitter factor (0.0 = no jitter, 1.0 = full jitter)
|
|
298
|
-
pub fn jitter_factor(mut self, factor: f64) -> Self {
|
|
299
|
-
self.jitter_factor = factor.clamp(0.0, 1.0);
|
|
300
|
-
self
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
impl Default for FibonacciBackoff {
|
|
305
|
-
fn default() -> Self {
|
|
306
|
-
Self {
|
|
307
|
-
base_delay_ms: 100,
|
|
308
|
-
max_delay_ms: 10_000,
|
|
309
|
-
max_attempts: 8,
|
|
310
|
-
jitter_factor: 1.0, // Full jitter by default
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
impl BackoffStrategy for FibonacciBackoff {
|
|
316
|
-
fn delay<R: Rng>(&self, attempt: u8, rng: &mut R) -> Option<u64> {
|
|
317
|
-
if attempt >= self.max_attempts {
|
|
318
|
-
return None;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
let fib = fibonacci(attempt);
|
|
322
|
-
let base = ((self.base_delay_ms as f64) * (fib as f64)).min(self.max_delay_ms as f64);
|
|
323
|
-
|
|
324
|
-
Some(apply_jitter(base, self.jitter_factor, rng))
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
fn should_retry(&self, attempt: u8) -> bool {
|
|
328
|
-
attempt < self.max_attempts
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
fn max_attempts(&self) -> u8 {
|
|
332
|
-
self.max_attempts
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
/// Backoff policy that can represent any supported strategy.
|
|
337
|
-
///
|
|
338
|
-
/// The enum form makes it possible to store heterogeneous strategies in a
|
|
339
|
-
/// registry or configuration without heap allocation or dynamic dispatch.
|
|
340
|
-
#[derive(Debug, Clone, Copy)]
|
|
341
|
-
pub enum BackoffPolicy {
|
|
342
|
-
/// Exponential backoff policy
|
|
343
|
-
Exponential(ExponentialBackoff),
|
|
344
|
-
/// Constant backoff policy
|
|
345
|
-
Constant(ConstantBackoff),
|
|
346
|
-
/// Fibonacci backoff policy
|
|
347
|
-
Fibonacci(FibonacciBackoff),
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
impl BackoffPolicy {
|
|
351
|
-
/// Return the maximum retry attempts for the wrapped strategy.
|
|
352
|
-
pub fn max_attempts(&self) -> u8 {
|
|
353
|
-
match self {
|
|
354
|
-
BackoffPolicy::Exponential(policy) => policy.max_attempts,
|
|
355
|
-
BackoffPolicy::Constant(policy) => policy.max_attempts,
|
|
356
|
-
BackoffPolicy::Fibonacci(policy) => policy.max_attempts,
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
impl BackoffStrategy for BackoffPolicy {
|
|
362
|
-
fn delay<R: Rng>(&self, attempt: u8, rng: &mut R) -> Option<u64> {
|
|
363
|
-
match self {
|
|
364
|
-
BackoffPolicy::Exponential(policy) => policy.delay(attempt, rng),
|
|
365
|
-
BackoffPolicy::Constant(policy) => policy.delay(attempt, rng),
|
|
366
|
-
BackoffPolicy::Fibonacci(policy) => policy.delay(attempt, rng),
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
fn should_retry(&self, attempt: u8) -> bool {
|
|
371
|
-
match self {
|
|
372
|
-
BackoffPolicy::Exponential(policy) => policy.should_retry(attempt),
|
|
373
|
-
BackoffPolicy::Constant(policy) => policy.should_retry(attempt),
|
|
374
|
-
BackoffPolicy::Fibonacci(policy) => policy.should_retry(attempt),
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
fn max_attempts(&self) -> u8 {
|
|
379
|
-
match self {
|
|
380
|
-
BackoffPolicy::Exponential(policy) => policy.max_attempts(),
|
|
381
|
-
BackoffPolicy::Constant(policy) => policy.max_attempts(),
|
|
382
|
-
BackoffPolicy::Fibonacci(policy) => policy.max_attempts(),
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
impl From<ExponentialBackoff> for BackoffPolicy {
|
|
388
|
-
fn from(value: ExponentialBackoff) -> Self {
|
|
389
|
-
BackoffPolicy::Exponential(value)
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
impl From<ConstantBackoff> for BackoffPolicy {
|
|
394
|
-
fn from(value: ConstantBackoff) -> Self {
|
|
395
|
-
BackoffPolicy::Constant(value)
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
impl From<FibonacciBackoff> for BackoffPolicy {
|
|
400
|
-
fn from(value: FibonacciBackoff) -> Self {
|
|
401
|
-
BackoffPolicy::Fibonacci(value)
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
#[cfg(test)]
|
|
406
|
-
mod tests {
|
|
407
|
-
use super::*;
|
|
408
|
-
use rand::rngs::StdRng;
|
|
409
|
-
use rand::SeedableRng;
|
|
410
|
-
|
|
411
|
-
#[test]
|
|
412
|
-
fn test_exponential_backoff_builder() {
|
|
413
|
-
let backoff = ExponentialBackoff::new()
|
|
414
|
-
.base_delay_ms(200)
|
|
415
|
-
.multiplier(3.0)
|
|
416
|
-
.max_delay_ms(5000)
|
|
417
|
-
.max_attempts(5)
|
|
418
|
-
.jitter_factor(0.5);
|
|
419
|
-
|
|
420
|
-
assert_eq!(backoff.base_delay_ms, 200);
|
|
421
|
-
assert_eq!(backoff.multiplier, 3.0);
|
|
422
|
-
assert_eq!(backoff.max_delay_ms, 5000);
|
|
423
|
-
assert_eq!(backoff.max_attempts, 5);
|
|
424
|
-
assert_eq!(backoff.jitter_factor, 0.5);
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
#[test]
|
|
428
|
-
fn test_exponential_delays() {
|
|
429
|
-
let backoff = ExponentialBackoff::new()
|
|
430
|
-
.base_delay_ms(100)
|
|
431
|
-
.multiplier(2.0)
|
|
432
|
-
.jitter_factor(0.0); // No jitter for predictable testing
|
|
433
|
-
|
|
434
|
-
let mut rng = StdRng::seed_from_u64(42);
|
|
435
|
-
|
|
436
|
-
assert_eq!(backoff.delay(1, &mut rng), Some(100));
|
|
437
|
-
assert_eq!(backoff.delay(2, &mut rng), Some(200));
|
|
438
|
-
assert_eq!(backoff.delay(3, &mut rng), None); // Exceeds max_attempts (default 3)
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
#[test]
|
|
442
|
-
fn test_constant_backoff() {
|
|
443
|
-
let backoff = ConstantBackoff::new()
|
|
444
|
-
.delay_ms(500)
|
|
445
|
-
.max_attempts(4)
|
|
446
|
-
.jitter_factor(0.0);
|
|
447
|
-
|
|
448
|
-
let mut rng = StdRng::seed_from_u64(42);
|
|
449
|
-
|
|
450
|
-
assert_eq!(backoff.delay(1, &mut rng), Some(500));
|
|
451
|
-
assert_eq!(backoff.delay(2, &mut rng), Some(500));
|
|
452
|
-
assert_eq!(backoff.delay(3, &mut rng), Some(500));
|
|
453
|
-
assert_eq!(backoff.delay(4, &mut rng), None);
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
#[test]
|
|
457
|
-
fn test_fibonacci_sequence() {
|
|
458
|
-
assert_eq!(fibonacci(1), 1);
|
|
459
|
-
assert_eq!(fibonacci(2), 1);
|
|
460
|
-
assert_eq!(fibonacci(3), 2);
|
|
461
|
-
assert_eq!(fibonacci(4), 3);
|
|
462
|
-
assert_eq!(fibonacci(5), 5);
|
|
463
|
-
assert_eq!(fibonacci(6), 8);
|
|
464
|
-
assert_eq!(fibonacci(7), 13);
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
#[test]
|
|
468
|
-
fn test_fibonacci_backoff() {
|
|
469
|
-
let backoff = FibonacciBackoff::new()
|
|
470
|
-
.base_delay_ms(100)
|
|
471
|
-
.max_attempts(5)
|
|
472
|
-
.jitter_factor(0.0);
|
|
473
|
-
|
|
474
|
-
let mut rng = StdRng::seed_from_u64(42);
|
|
475
|
-
|
|
476
|
-
assert_eq!(backoff.delay(1, &mut rng), Some(100)); // 100 * 1
|
|
477
|
-
assert_eq!(backoff.delay(2, &mut rng), Some(100)); // 100 * 1
|
|
478
|
-
assert_eq!(backoff.delay(3, &mut rng), Some(200)); // 100 * 2
|
|
479
|
-
assert_eq!(backoff.delay(4, &mut rng), Some(300)); // 100 * 3
|
|
480
|
-
assert_eq!(backoff.delay(5, &mut rng), None); // Exceeds max_attempts
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
#[test]
|
|
484
|
-
fn test_jitter_application() {
|
|
485
|
-
let backoff = ConstantBackoff::new().delay_ms(1000).jitter_factor(1.0); // Full jitter
|
|
486
|
-
|
|
487
|
-
let mut rng = StdRng::seed_from_u64(42);
|
|
488
|
-
let delays: Vec<u64> = (1..10).filter_map(|i| backoff.delay(i, &mut rng)).collect();
|
|
489
|
-
|
|
490
|
-
// With full jitter, delays should vary
|
|
491
|
-
let all_different = delays.windows(2).any(|w| w[0] != w[1]);
|
|
492
|
-
assert!(all_different, "Full jitter should produce varying delays");
|
|
493
|
-
|
|
494
|
-
// All delays should be <= base delay
|
|
495
|
-
assert!(delays.iter().all(|&d| d <= 1000));
|
|
496
|
-
}
|
|
497
|
-
}
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
//! Ergonomic helpers that mirror the high-level DSL available in the Ruby gem.
|
|
2
|
-
//!
|
|
3
|
-
//! These helpers build on top of the global policy registry (std-only) to allow
|
|
4
|
-
//! concise execution using named policies.
|
|
5
|
-
|
|
6
|
-
use crate::backoff::BackoffPolicy;
|
|
7
|
-
use crate::policy::get_global_policy;
|
|
8
|
-
use crate::retry::{RetryBuilder, RetryError, RetryOutcome, Retryable};
|
|
9
|
-
use std::fmt;
|
|
10
|
-
|
|
11
|
-
/// Type alias for retry builder with policy from global registry
|
|
12
|
-
type PolicyRetryBuilder<F, T, E> = RetryBuilder<F, BackoffPolicy, T, E, fn(&E) -> bool>;
|
|
13
|
-
|
|
14
|
-
/// Errors produced by the DSL helpers.
|
|
15
|
-
#[derive(Debug)]
|
|
16
|
-
pub enum DslError<E> {
|
|
17
|
-
/// Referenced policy name is missing from the global registry.
|
|
18
|
-
PolicyMissing(String),
|
|
19
|
-
/// Underlying retry execution failed.
|
|
20
|
-
Execution(RetryError<E>),
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
impl<E> From<RetryError<E>> for DslError<E> {
|
|
24
|
-
fn from(value: RetryError<E>) -> Self {
|
|
25
|
-
DslError::Execution(value)
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
impl<E> fmt::Display for DslError<E>
|
|
30
|
-
where
|
|
31
|
-
E: fmt::Display,
|
|
32
|
-
{
|
|
33
|
-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
34
|
-
match self {
|
|
35
|
-
DslError::PolicyMissing(name) => write!(f, "retry policy '{}' is not registered", name),
|
|
36
|
-
DslError::Execution(err) => write!(f, "{err}"),
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
impl<E> std::error::Error for DslError<E> where E: fmt::Display + std::error::Error {}
|
|
42
|
-
|
|
43
|
-
/// Construct a [`RetryBuilder`] using a named policy from the global registry.
|
|
44
|
-
pub fn builder_for_policy<F, T, E>(
|
|
45
|
-
policy_name: &str,
|
|
46
|
-
operation: F,
|
|
47
|
-
) -> Result<PolicyRetryBuilder<F, T, E>, DslError<E>>
|
|
48
|
-
where
|
|
49
|
-
F: FnMut() -> Result<T, E>,
|
|
50
|
-
{
|
|
51
|
-
let policy = get_global_policy(policy_name)
|
|
52
|
-
.ok_or_else(|| DslError::PolicyMissing(policy_name.to_string()))?;
|
|
53
|
-
|
|
54
|
-
Ok(operation.retry(policy))
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/// Execute an operation using a named policy from the global registry.
|
|
58
|
-
pub fn retry_with_policy<F, T, E>(
|
|
59
|
-
policy_name: &str,
|
|
60
|
-
operation: F,
|
|
61
|
-
) -> Result<RetryOutcome<T>, DslError<E>>
|
|
62
|
-
where
|
|
63
|
-
F: FnMut() -> Result<T, E>,
|
|
64
|
-
{
|
|
65
|
-
builder_for_policy(policy_name, operation)?
|
|
66
|
-
.call()
|
|
67
|
-
.map_err(DslError::Execution)
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
#[cfg(test)]
|
|
71
|
-
mod tests {
|
|
72
|
-
use super::*;
|
|
73
|
-
use crate::backoff::{BackoffPolicy, ExponentialBackoff};
|
|
74
|
-
use crate::policy::{clear_global_policies, register_global_policy};
|
|
75
|
-
|
|
76
|
-
#[test]
|
|
77
|
-
fn test_retry_with_policy_success() {
|
|
78
|
-
clear_global_policies();
|
|
79
|
-
register_global_policy(
|
|
80
|
-
"default",
|
|
81
|
-
BackoffPolicy::from(ExponentialBackoff::new().max_attempts(2)),
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
let mut attempts = 0;
|
|
85
|
-
let outcome = retry_with_policy("default", || {
|
|
86
|
-
attempts += 1;
|
|
87
|
-
if attempts == 1 {
|
|
88
|
-
Err::<_, &'static str>("fail")
|
|
89
|
-
} else {
|
|
90
|
-
Ok("ok")
|
|
91
|
-
}
|
|
92
|
-
})
|
|
93
|
-
.expect("dsl retry should succeed");
|
|
94
|
-
|
|
95
|
-
assert_eq!(attempts, 2);
|
|
96
|
-
assert_eq!(outcome.into_inner(), "ok");
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
#[test]
|
|
100
|
-
fn test_retry_with_policy_missing() {
|
|
101
|
-
clear_global_policies();
|
|
102
|
-
let result = retry_with_policy::<_, (), &str>("missing", || Ok(()));
|
|
103
|
-
match result {
|
|
104
|
-
Err(DslError::PolicyMissing(name)) => assert_eq!(name, "missing"),
|
|
105
|
-
_ => panic!("expected policy missing error"),
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|