Skip to main content

rucora_core/
retry.rs

1//! RetryPolicy(重试策略)接口
2//!
3//! 提供通用的重试逻辑抽象,支持指数退避、抖动等策略。
4//!
5//! # 设计原则
6//!
7//! - **可组合**: 支持装饰器模式增强策略
8//! - **可观测**: 提供详细的重试日志
9//! - **灵活**: 支持自定义延迟计算和终止条件
10//!
11//! # 使用示例
12//!
13//! ## 使用内置指数退避策略
14//!
15//! ```rust
16//! use rucora_core::retry::{RetryPolicy, ExponentialBackoff};
17//!
18//! let policy = ExponentialBackoff::new(3, std::time::Duration::from_millis(100));
19//! for attempt in 0..5 {
20//!     if let Some(delay) = policy.should_retry(attempt) {
21//!         println!("重试 {attempt},等待 {delay:?}");
22//!     } else {
23//!         println!("不应重试");
24//!         break;
25//!     }
26//! }
27//! ```
28//!
29//! ## 实现自定义策略
30//!
31//! ```rust
32//! use rucora_core::retry::{RetryPolicy, RetryAction};
33//! use std::time::Duration;
34//!
35//! struct MyPolicy;
36//!
37//! impl RetryPolicy for MyPolicy {
38//!     fn should_retry(&self, attempt: u32) -> Option<Duration> {
39//!         if attempt < 3 {
40//!             Some(Duration::from_millis(100 * 2u64.pow(attempt)))
41//!         } else {
42//!             None
43//!         }
44//!     }
45//! }
46//! ```
47
48use std::time::Duration;
49
50/// 重试动作
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum RetryAction {
53    /// 应该重试
54    ShouldRetry(Duration),
55    /// 不应该重试
56    NoRetry,
57    /// 永久失败
58    PermanentFailure,
59}
60
61/// 重试策略 trait
62///
63/// 所有重试策略必须实现此 trait。
64pub trait RetryPolicy: Send + Sync {
65    /// 判断是否应该重试
66    ///
67    /// # 参数
68    ///
69    /// * `attempt`: 当前重试次数(从 0 开始)
70    ///
71    /// # 返回
72    ///
73    /// - `Some(Duration)`: 应该重试,返回等待时间
74    /// - `None`: 不应该重试
75    fn should_retry(&self, attempt: u32) -> Option<Duration>;
76
77    /// 判断是否应该重试(带错误信息)
78    ///
79    /// 默认实现调用 `should_retry`,忽略错误信息。
80    /// 子类可以重写此方法根据错误类型决定是否重试。
81    fn should_retry_with_error(&self, attempt: u32, _error: &str) -> Option<Duration> {
82        self.should_retry(attempt)
83    }
84
85    /// 获取最大重试次数
86    fn max_retries(&self) -> u32 {
87        u32::MAX
88    }
89}
90
91/// 指数退避策略
92#[derive(Debug, Clone)]
93pub struct ExponentialBackoff {
94    max_retries: u32,
95    initial_delay: Duration,
96    max_delay: Duration,
97    jitter: bool,
98}
99
100impl ExponentialBackoff {
101    /// 创建新的指数退避策略
102    ///
103    /// # 参数
104    ///
105    /// * `max_retries`: 最大重试次数
106    /// * `initial_delay`: 初始延迟
107    pub fn new(max_retries: u32, initial_delay: Duration) -> Self {
108        Self {
109            max_retries,
110            initial_delay,
111            max_delay: Duration::from_secs(30),
112            jitter: false,
113        }
114    }
115
116    /// 设置最大延迟
117    pub fn with_max_delay(mut self, max_delay: Duration) -> Self {
118        self.max_delay = max_delay;
119        self
120    }
121
122    /// 启用抖动(Jitter)
123    ///
124    /// 抖动可以避免多客户端同时重试造成的雷鸣羊群效应(Thundering Herd)。
125    pub fn with_jitter(mut self) -> Self {
126        self.jitter = true;
127        self
128    }
129
130    fn calculate_delay(&self, attempt: u32) -> f64 {
131        let base_delay_ms = self.initial_delay.as_millis() as f64;
132        let max_delay_ms = self.max_delay.as_millis() as f64;
133        let delay = base_delay_ms * 2f64.powi(attempt as i32);
134
135        let delay = if delay > max_delay_ms {
136            max_delay_ms
137        } else {
138            delay
139        };
140
141        if self.jitter {
142            use std::time::Instant;
143            let now = Instant::now();
144            let nanos = now.elapsed().as_nanos() as f64;
145            let jitter_range = delay * 0.2;
146            let jitter = nanos as f64 % jitter_range;
147            delay - jitter_range / 2.0 + jitter
148        } else {
149            delay
150        }
151    }
152}
153
154impl RetryPolicy for ExponentialBackoff {
155    fn should_retry(&self, attempt: u32) -> Option<Duration> {
156        if attempt >= self.max_retries {
157            return None;
158        }
159        let delay_ms = self.calculate_delay(attempt);
160        Some(Duration::from_millis(delay_ms as u64))
161    }
162
163    fn max_retries(&self) -> u32 {
164        self.max_retries
165    }
166}
167
168/// 固定间隔策略
169#[derive(Debug, Clone)]
170pub struct FixedDelay {
171    max_retries: u32,
172    delay: Duration,
173}
174
175impl FixedDelay {
176    /// 创建新的固定间隔策略
177    pub fn new(max_retries: u32, delay: Duration) -> Self {
178        Self { max_retries, delay }
179    }
180}
181
182impl RetryPolicy for FixedDelay {
183    fn should_retry(&self, attempt: u32) -> Option<Duration> {
184        if attempt >= self.max_retries {
185            return None;
186        }
187        Some(self.delay)
188    }
189
190    fn max_retries(&self) -> u32 {
191        self.max_retries
192    }
193}
194
195/// 空策略(不重试)
196#[derive(Debug, Clone, Copy, Default)]
197pub struct NoRetry;
198
199impl RetryPolicy for NoRetry {
200    fn should_retry(&self, _attempt: u32) -> Option<Duration> {
201        None
202    }
203
204    fn max_retries(&self) -> u32 {
205        0
206    }
207}
208
209/// 装饰器:添加临时错误过滤
210///
211/// 只有满足条件的错误才会触发重试。
212pub struct TransientFilter<P> {
213    inner: P,
214    predicate: Box<dyn Fn(&str) -> bool + Send + Sync>,
215}
216
217impl<P: RetryPolicy> TransientFilter<P> {
218    /// 创建新的临时错误过滤器
219    pub fn new(policy: P, predicate: impl Fn(&str) -> bool + Send + Sync + 'static) -> Self {
220        Self {
221            inner: policy,
222            predicate: Box::new(predicate),
223        }
224    }
225}
226
227impl<P: RetryPolicy> RetryPolicy for TransientFilter<P> {
228    fn should_retry(&self, attempt: u32) -> Option<Duration> {
229        self.inner.should_retry(attempt)
230    }
231
232    fn should_retry_with_error(&self, attempt: u32, error: &str) -> Option<Duration> {
233        if (self.predicate)(error) {
234            self.inner.should_retry_with_error(attempt, error)
235        } else {
236            None
237        }
238    }
239
240    fn max_retries(&self) -> u32 {
241        self.inner.max_retries()
242    }
243}
244
245/// RetryPolicy 扩展方法
246pub trait RetryPolicyExt: RetryPolicy + Sized {
247    /// 获取重试延迟的迭代器
248    fn delays(&self) -> DelayIterator<'_, Self> {
249        DelayIterator {
250            policy: self,
251            attempt: 0,
252        }
253    }
254}
255
256impl<T: RetryPolicy + Sized> RetryPolicyExt for T {}
257
258/// 重试延迟迭代器
259#[derive(Debug)]
260pub struct DelayIterator<'a, P: RetryPolicy> {
261    policy: &'a P,
262    attempt: u32,
263}
264
265impl<P: RetryPolicy> Iterator for DelayIterator<'_, P> {
266    type Item = Duration;
267
268    fn next(&mut self) -> Option<Self::Item> {
269        let delay = self.policy.should_retry(self.attempt);
270        self.attempt += 1;
271        delay
272    }
273}
274
275#[cfg(test)]
276mod tests {
277    use super::*;
278
279    #[test]
280    fn test_exponential_backoff() {
281        let policy = ExponentialBackoff::new(3, Duration::from_millis(100));
282
283        assert_eq!(policy.should_retry(0), Some(Duration::from_millis(100)));
284        assert_eq!(policy.should_retry(1), Some(Duration::from_millis(200)));
285        assert_eq!(policy.should_retry(2), Some(Duration::from_millis(400)));
286        assert_eq!(policy.should_retry(3), None);
287    }
288
289    #[test]
290    fn test_exponential_backoff_with_max_delay() {
291        let policy = ExponentialBackoff::new(10, Duration::from_millis(100))
292            .with_max_delay(Duration::from_millis(500));
293
294        assert_eq!(policy.should_retry(0), Some(Duration::from_millis(100)));
295        assert_eq!(policy.should_retry(1), Some(Duration::from_millis(200)));
296        assert_eq!(policy.should_retry(2), Some(Duration::from_millis(400)));
297        assert_eq!(policy.should_retry(3), Some(Duration::from_millis(500)));
298        assert_eq!(policy.should_retry(4), Some(Duration::from_millis(500)));
299    }
300
301    #[test]
302    fn test_fixed_delay() {
303        let policy = FixedDelay::new(3, Duration::from_secs(1));
304
305        assert_eq!(policy.should_retry(0), Some(Duration::from_secs(1)));
306        assert_eq!(policy.should_retry(1), Some(Duration::from_secs(1)));
307        assert_eq!(policy.should_retry(2), Some(Duration::from_secs(1)));
308        assert_eq!(policy.should_retry(3), None);
309    }
310
311    #[test]
312    fn test_no_retry() {
313        let policy = NoRetry;
314
315        assert_eq!(policy.should_retry(0), None);
316        assert_eq!(policy.should_retry(1), None);
317    }
318
319    #[test]
320    fn test_delay_iterator() {
321        let policy = ExponentialBackoff::new(3, Duration::from_millis(100));
322
323        let delays: Vec<_> = policy.delays().collect();
324        assert_eq!(delays.len(), 3);
325        assert_eq!(delays[0], Duration::from_millis(100));
326        assert_eq!(delays[1], Duration::from_millis(200));
327        assert_eq!(delays[2], Duration::from_millis(400));
328    }
329}