error_forge/recovery/
retry.rs1use std::marker::PhantomData;
2use std::time::Duration;
3use std::thread;
4use crate::recovery::backoff::{Backoff, ExponentialBackoff, FixedBackoff, LinearBackoff};
5use crate::error::ForgeError;
6
7pub type RetryPredicate<E> = Box<dyn Fn(&E) -> bool + Send + Sync + 'static>;
9
10pub enum BackoffStrategy {
12 Exponential(ExponentialBackoff),
13 Linear(LinearBackoff),
14 Fixed(FixedBackoff),
15}
16
17impl BackoffStrategy {
18 fn next_delay(&self, attempt: usize) -> Duration {
19 match self {
20 BackoffStrategy::Exponential(b) => b.next_delay(attempt),
21 BackoffStrategy::Linear(b) => b.next_delay(attempt),
22 BackoffStrategy::Fixed(b) => b.next_delay(attempt),
23 }
24 }
25}
26
27pub struct RetryExecutor<E> {
29 max_retries: usize,
30 backoff: BackoffStrategy,
31 retry_if: Option<RetryPredicate<E>>,
32 _marker: PhantomData<E>,
33}
34
35impl<E> RetryExecutor<E>
36where
37 E: std::error::Error + 'static
38{
39 pub fn new_exponential() -> Self {
41 Self {
42 max_retries: 3,
43 backoff: BackoffStrategy::Exponential(ExponentialBackoff::default()),
44 retry_if: None,
45 _marker: PhantomData,
46 }
47 }
48
49 pub fn new_linear() -> Self {
51 Self {
52 max_retries: 3,
53 backoff: BackoffStrategy::Linear(LinearBackoff::default()),
54 retry_if: None,
55 _marker: PhantomData,
56 }
57 }
58
59 pub fn new_fixed(delay_ms: u64) -> Self {
61 Self {
62 max_retries: 3,
63 backoff: BackoffStrategy::Fixed(FixedBackoff::new(delay_ms)),
64 retry_if: None,
65 _marker: PhantomData,
66 }
67 }
68
69 pub fn with_max_retries(mut self, max_retries: usize) -> Self {
71 self.max_retries = max_retries;
72 self
73 }
74
75 pub fn with_retry_if<F>(mut self, predicate: F) -> Self
77 where
78 F: Fn(&E) -> bool + Send + Sync + 'static
79 {
80 self.retry_if = Some(Box::new(predicate));
81 self
82 }
83
84 pub fn retry<F, T>(&self, mut operation: F) -> Result<T, E>
86 where
87 F: FnMut() -> Result<T, E>
88 {
89 let mut attempt = 0;
90 loop {
91 match operation() {
92 Ok(value) => return Ok(value),
93 Err(err) => {
94 if attempt >= self.max_retries {
96 return Err(err);
97 }
98
99 let should_retry = match &self.retry_if {
101 Some(predicate) => predicate(&err),
102 None => true,
103 };
104
105 if !should_retry {
106 return Err(err);
107 }
108
109 let delay = self.backoff.next_delay(attempt);
111 thread::sleep(delay);
112
113 attempt += 1;
114 }
115 }
116 }
117 }
118
119 pub fn retry_with_handler<F, H, T>(&self, mut operation: F, mut on_error: H) -> Result<T, E>
121 where
122 F: FnMut() -> Result<T, E>,
123 H: FnMut(&E, usize, Duration),
124 {
125 let mut attempt = 0;
126 loop {
127 match operation() {
128 Ok(value) => return Ok(value),
129 Err(err) => {
130 if attempt >= self.max_retries {
132 return Err(err);
133 }
134
135 let should_retry = match &self.retry_if {
137 Some(predicate) => predicate(&err),
138 None => true,
139 };
140
141 if !should_retry {
142 return Err(err);
143 }
144
145 let delay = self.backoff.next_delay(attempt);
147
148 on_error(&err, attempt, delay);
150
151 thread::sleep(delay);
153
154 attempt += 1;
155 }
156 }
157 }
158 }
159}
160
161pub struct RetryPolicy {
163 max_retries: usize,
164 backoff_type: BackoffType,
165}
166
167pub enum BackoffType {
169 Exponential,
170 Linear,
171 Fixed(u64),
172}
173
174impl RetryPolicy {
175 pub fn new_exponential() -> Self {
177 Self {
178 max_retries: 3,
179 backoff_type: BackoffType::Exponential,
180 }
181 }
182
183 pub fn new_linear() -> Self {
185 Self {
186 max_retries: 3,
187 backoff_type: BackoffType::Linear,
188 }
189 }
190
191 pub fn new_fixed(delay_ms: u64) -> Self {
193 Self {
194 max_retries: 3,
195 backoff_type: BackoffType::Fixed(delay_ms),
196 }
197 }
198
199 pub fn with_max_retries(mut self, max_retries: usize) -> Self {
201 self.max_retries = max_retries;
202 self
203 }
204
205 pub fn executor<E>(&self) -> RetryExecutor<E>
207 where
208 E: std::error::Error + 'static
209 {
210 let executor = match self.backoff_type {
211 BackoffType::Exponential => RetryExecutor::new_exponential(),
212 BackoffType::Linear => RetryExecutor::new_linear(),
213 BackoffType::Fixed(delay_ms) => RetryExecutor::new_fixed(delay_ms),
214 };
215
216 executor.with_max_retries(self.max_retries)
217 }
218
219 pub fn forge_executor<E>(&self) -> RetryExecutor<E>
221 where
222 E: ForgeError
223 {
224 self.executor::<E>()
225 .with_retry_if(|err| err.is_retryable())
226 }
227
228 pub fn retry<F, T, E>(&self, operation: F) -> Result<T, E>
230 where
231 F: FnMut() -> Result<T, E>,
232 E: std::error::Error + 'static
233 {
234 self.executor::<E>().retry(operation)
235 }
236}
237
238impl Default for RetryPolicy {
239 fn default() -> Self {
240 Self::new_exponential()
241 }
242}