Skip to main content

yggdrasil/db/
retry.rs

1//! 数据库连接获取的指数退避重试策略。
2//!
3//! 取代 pool.rs 中固定 2s 间隔的重试:每次重试间隔 = base * 2^attempt,
4//! 再叠加 [0, base) 的随机 jitter,避免多请求同步重试形成惊群。
5//! 仅在 `feature = "server"` 时编译。
6
7#[cfg(feature = "server")]
8use std::time::Duration;
9
10/// 退避基准间隔(首次重试前的等待约为 base,随后翻倍)。
11#[cfg(feature = "server")]
12const BASE_BACKOFF: Duration = Duration::from_millis(200);
13
14/// 最大重试次数(不含首次尝试)。
15#[cfg(feature = "server")]
16pub const MAX_RETRIES: u32 = 3;
17
18/// 计算第 `attempt` 次重试(attempt 从 0 开始)前的等待时长。
19///
20/// 公式:base * 2^attempt,再叠加 [0, base) 的 jitter。
21/// jitter 由调用方传入的随机比例 [0.0, 1.0) 决定,便于测试时锁定为 0。
22#[cfg(feature = "server")]
23pub fn backoff_for(attempt: u32, jitter_ratio: f64) -> Duration {
24    debug_assert!((0.0..=1.0).contains(&jitter_ratio));
25    let exp = u32::checked_shl(1, attempt).unwrap_or(1 << 30);
26    let base_ms = BASE_BACKOFF.as_millis() as u64;
27    let core = base_ms.saturating_mul(exp as u64);
28    let jitter = (base_ms as f64 * jitter_ratio) as u64;
29    Duration::from_millis(core.saturating_add(jitter))
30}
31
32#[cfg(all(test, feature = "server"))]
33mod tests {
34    use super::*;
35
36    #[test]
37    fn backoff_grows_exponentially_without_jitter() {
38        // jitter=0 时序列应严格翻倍:200, 400, 800 ms。
39        assert_eq!(backoff_for(0, 0.0), Duration::from_millis(200));
40        assert_eq!(backoff_for(1, 0.0), Duration::from_millis(400));
41        assert_eq!(backoff_for(2, 0.0), Duration::from_millis(800));
42    }
43
44    #[test]
45    fn backoff_includes_jitter_within_base_range() {
46        // jitter_ratio=0.5 时在 core 上叠加 base*0.5 = 100ms。
47        assert_eq!(backoff_for(0, 0.5), Duration::from_millis(300));
48        assert_eq!(backoff_for(1, 0.5), Duration::from_millis(500));
49    }
50
51    #[test]
52    fn backoff_clamps_large_attempt() {
53        // 超大 attempt 不应 panic,应靠 saturating 保护返回一个大但有界的值。
54        let d = backoff_for(40, 0.0);
55        assert!(d.as_millis() > 0);
56    }
57}