halldyll_core/fetch/
retry.rs1use rand::Rng;
4use std::time::Duration;
5
6use crate::types::error::Error;
7
8#[derive(Debug, Clone)]
10pub struct RetryPolicy {
11 pub max_retries: u32,
13 pub initial_delay_ms: u64,
15 pub multiplier: f64,
17 pub max_jitter_ms: u64,
19 pub max_delay_ms: u64,
21 pub retry_on_timeout: bool,
23 pub retry_on_connection_error: bool,
25 pub retry_on_5xx: bool,
27 pub retry_on_429: bool,
29}
30
31impl Default for RetryPolicy {
32 fn default() -> Self {
33 Self {
34 max_retries: 3,
35 initial_delay_ms: 1000,
36 multiplier: 2.0,
37 max_jitter_ms: 500,
38 max_delay_ms: 30000,
39 retry_on_timeout: true,
40 retry_on_connection_error: true,
41 retry_on_5xx: true,
42 retry_on_429: true,
43 }
44 }
45}
46
47impl RetryPolicy {
48 pub fn delay_for_attempt(&self, attempt: u32) -> Duration {
50 let base_delay = self.initial_delay_ms as f64 * self.multiplier.powi(attempt as i32);
51 let capped_delay = base_delay.min(self.max_delay_ms as f64);
52
53 let jitter = if self.max_jitter_ms > 0 {
55 let mut rng = rand::thread_rng();
56 rng.gen_range(0..=self.max_jitter_ms)
57 } else {
58 0
59 };
60
61 Duration::from_millis(capped_delay as u64 + jitter)
62 }
63
64 pub fn should_retry(&self, error: &Error, attempt: u32) -> bool {
66 if attempt >= self.max_retries {
67 return false;
68 }
69
70 match error {
71 Error::Timeout(_) => self.retry_on_timeout,
72 Error::Network(e) => {
73 if e.is_timeout() {
74 self.retry_on_timeout
75 } else if e.is_connect() {
76 self.retry_on_connection_error
77 } else if let Some(status) = e.status() {
78 if status.as_u16() == 429 {
79 self.retry_on_429
80 } else if status.is_server_error() {
81 self.retry_on_5xx
82 } else {
83 false
84 }
85 } else {
86 self.retry_on_connection_error
87 }
88 }
89 Error::RateLimited(_) => self.retry_on_429,
90 _ => false,
91 }
92 }
93
94 pub fn should_retry_status(&self, status_code: u16, attempt: u32) -> bool {
96 if attempt >= self.max_retries {
97 return false;
98 }
99
100 match status_code {
101 429 => self.retry_on_429,
102 500..=599 => self.retry_on_5xx,
103 _ => false,
104 }
105 }
106}
107
108#[derive(Debug)]
110pub struct RetryState {
111 policy: RetryPolicy,
112 attempts: u32,
113}
114
115impl RetryState {
116 pub fn new(policy: RetryPolicy) -> Self {
118 Self {
119 policy,
120 attempts: 0,
121 }
122 }
123
124 pub fn increment(&mut self) {
126 self.attempts += 1;
127 }
128
129 pub fn attempts(&self) -> u32 {
131 self.attempts
132 }
133
134 pub fn next_delay(&self) -> Duration {
136 self.policy.delay_for_attempt(self.attempts)
137 }
138
139 pub fn should_retry(&self, error: &Error) -> bool {
141 self.policy.should_retry(error, self.attempts)
142 }
143
144 pub fn policy(&self) -> &RetryPolicy {
146 &self.policy
147 }
148}