-
Notifications
You must be signed in to change notification settings - Fork 117
Expand file tree
/
Copy pathretry.rs
More file actions
86 lines (72 loc) · 2.71 KB
/
retry.rs
File metadata and controls
86 lines (72 loc) · 2.71 KB
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
use std::fmt;
use tokio::time::Duration;
use tokio_retry::strategy::ExponentialBackoff;
use super::http::client_builder;
/// A generic error type for retryable operations
#[derive(Debug)]
pub enum RetryableError {
Permanent(String), // Non-retryable error
Temporary(String), // Retryable error
}
impl fmt::Display for RetryableError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RetryableError::Permanent(e) => write!(f, "{e}"),
RetryableError::Temporary(e) => write!(f, "{e}"),
}
}
}
impl std::error::Error for RetryableError {}
/// Build a reqwest::Client with timeout config.
/// Connection pool is unlimited - concurrency is controlled by semaphore instead.
pub fn build_dns_cached_client() -> reqwest::Client {
client_builder()
.connect_timeout(std::time::Duration::from_secs(5)) // TLS + TCP handshake
.read_timeout(std::time::Duration::from_secs(30)) // Timeout for individual read operations
// No total timeout - large files (e.g. node binary ~100MB) need longer download time
// No pool_max_idle_per_host - let reqwest manage connections freely
// Concurrency is controlled by each caller's semaphore.
.build()
.expect("Failed to build reqwest client")
}
pub fn create_retry_strategy() -> impl Iterator<Item = Duration> {
let delays = vec![
Duration::from_millis(100), // 100ms
Duration::from_millis(200), // 200ms
Duration::from_secs(1), // 1s
Duration::from_secs(1), // 1s
Duration::from_secs(1), // 1s
];
let exp_strategy = ExponentialBackoff::from_millis(1000)
.max_delay(Duration::from_secs(20))
.take(5); // 5 fixed delays
delays.into_iter().chain(exp_strategy)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fixed_delays() {
let strategy = create_retry_strategy();
let delays: Vec<Duration> = strategy.take(5).collect();
assert_eq!(delays[0], Duration::from_millis(100));
assert_eq!(delays[1], Duration::from_millis(200));
assert_eq!(delays[2], Duration::from_secs(1));
assert_eq!(delays[3], Duration::from_secs(1));
assert_eq!(delays[4], Duration::from_secs(1));
}
#[test]
fn test_total_retry_count() {
let strategy = create_retry_strategy();
let delays: Vec<Duration> = strategy.collect();
assert_eq!(delays.len(), 10);
}
#[test]
fn test_max_delay_limit() {
let strategy = create_retry_strategy();
let max_delay = Duration::from_secs(20);
for delay in strategy {
assert!(delay <= max_delay, "Delay should not exceed 20 seconds");
}
}
}