rskit_messaging/middleware/
retry.rs1use std::sync::Arc;
4
5use async_trait::async_trait;
6use rskit_errors::AppResult;
7use rskit_resilience::{ConstantBackoff, LinearBackoff, RetryError, RetryPolicy};
8
9use crate::handler::{HandlerMiddleware, MessageHandler};
10use crate::message::Message;
11
12#[derive(Debug, Clone)]
18pub struct RetryConfig {
19 policy: RetryPolicy,
20}
21
22impl Default for RetryConfig {
23 fn default() -> Self {
24 Self {
25 policy: RetryPolicy::new().with_retry_if(|_| true),
26 }
27 }
28}
29
30impl RetryConfig {
31 #[must_use]
33 pub fn new() -> Self {
34 Self::default()
35 }
36
37 #[must_use]
39 pub const fn from_policy(policy: RetryPolicy) -> Self {
40 Self { policy }
41 }
42
43 #[must_use]
45 pub fn with_max_attempts(mut self, n: usize) -> Self {
46 self.policy = self.policy.with_max_attempts(n);
47 self
48 }
49
50 #[must_use]
52 pub fn with_initial_backoff(mut self, d: std::time::Duration) -> Self {
53 self.policy = self.policy.with_initial_backoff(d);
54 self
55 }
56
57 #[must_use]
59 pub fn with_max_backoff(mut self, d: std::time::Duration) -> Self {
60 self.policy = self.policy.with_max_backoff(d);
61 self
62 }
63
64 #[must_use]
66 pub fn with_backoff_factor(mut self, factor: f64) -> Self {
67 self.policy = self.policy.with_backoff_factor(factor);
68 self
69 }
70
71 #[must_use]
73 pub fn with_constant_backoff(mut self, backoff: ConstantBackoff) -> Self {
74 self.policy = self.policy.with_constant_backoff(backoff);
75 self
76 }
77
78 #[must_use]
80 pub fn with_linear_backoff(mut self, backoff: LinearBackoff) -> Self {
81 self.policy = self.policy.with_linear_backoff(backoff);
82 self
83 }
84
85 #[must_use]
87 pub fn with_jitter(mut self, enabled: bool) -> Self {
88 self.policy = self.policy.with_jitter(enabled);
89 self
90 }
91
92 #[must_use]
94 pub fn with_retry_if(
95 mut self,
96 f: impl Fn(&rskit_errors::AppError) -> bool + Send + Sync + 'static,
97 ) -> Self {
98 self.policy = self.policy.with_retry_if(f);
99 self
100 }
101
102 #[must_use]
104 pub fn with_on_retry(
105 mut self,
106 f: impl Fn(u32, &rskit_errors::AppError) + Send + Sync + 'static,
107 ) -> Self {
108 self.policy = self.policy.with_on_retry(f);
109 self
110 }
111
112 #[must_use]
114 pub const fn policy(&self) -> &RetryPolicy {
115 &self.policy
116 }
117
118 #[must_use]
120 pub fn into_policy(self) -> RetryPolicy {
121 self.policy
122 }
123
124 async fn execute<F, Fut, T>(&self, f: F) -> Result<T, RetryError>
125 where
126 F: FnMut() -> Fut,
127 Fut: std::future::Future<Output = AppResult<T>>,
128 {
129 self.policy.execute(f).await
130 }
131}
132
133impl From<RetryPolicy> for RetryConfig {
134 fn from(policy: RetryPolicy) -> Self {
135 Self::from_policy(policy)
136 }
137}
138
139pub fn retry<T: Send + Sync + Clone + 'static>(config: RetryConfig) -> impl HandlerMiddleware<T> {
141 RetryMiddleware { config }
142}
143
144struct RetryMiddleware {
145 config: RetryConfig,
146}
147
148impl<T: Send + Sync + Clone + 'static> HandlerMiddleware<T> for RetryMiddleware {
149 fn wrap(&self, next: Arc<dyn MessageHandler<T>>) -> Arc<dyn MessageHandler<T>> {
150 Arc::new(RetryHandler {
151 config: self.config.clone(),
152 next,
153 })
154 }
155}
156
157struct RetryHandler<T: Send + Sync + 'static> {
158 config: RetryConfig,
159 next: Arc<dyn MessageHandler<T>>,
160}
161
162#[async_trait]
163impl<T: Send + Sync + Clone + 'static> MessageHandler<T> for RetryHandler<T> {
164 async fn handle(&self, msg: Message<T>) -> AppResult<()> {
165 let next = self.next.clone();
166 self.config
167 .execute(|| {
168 let next = next.clone();
169 let msg = msg.clone();
170 async move { next.handle(msg).await }
171 })
172 .await
173 .map_err(|err| err.last_error)
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use std::sync::atomic::{AtomicU32, Ordering};
180 use std::time::Duration;
181
182 use super::*;
183 use crate::handler::{FnHandler, chain_handlers};
184 use rskit_errors::{AppError, ErrorCode};
185
186 fn test_policy() -> RetryConfig {
187 RetryConfig::new()
188 .with_max_attempts(3)
189 .with_initial_backoff(Duration::from_millis(1))
190 .with_jitter(false)
191 }
192
193 #[tokio::test]
194 async fn retry_success_on_first_attempt() {
195 let counter = Arc::new(AtomicU32::new(0));
196 let c = counter.clone();
197 let base: Arc<dyn MessageHandler<String>> =
198 Arc::new(FnHandler::new(move |_msg: Message<String>| {
199 let c = c.clone();
200 async move {
201 c.fetch_add(1, Ordering::SeqCst);
202 Ok(())
203 }
204 }));
205
206 let handler = chain_handlers(
207 base,
208 &[Arc::new(RetryMiddleware {
209 config: test_policy(),
210 })],
211 );
212
213 handler
214 .handle(Message::new("t", "data".to_string()))
215 .await
216 .unwrap();
217 assert_eq!(counter.load(Ordering::SeqCst), 1);
218 }
219
220 #[tokio::test]
221 async fn retry_succeeds_on_second_attempt() {
222 let counter = Arc::new(AtomicU32::new(0));
223 let c = counter.clone();
224 let base: Arc<dyn MessageHandler<String>> =
225 Arc::new(FnHandler::new(move |_msg: Message<String>| {
226 let c = c.clone();
227 async move {
228 let n = c.fetch_add(1, Ordering::SeqCst);
229 if n == 0 {
230 Err(AppError::new(ErrorCode::Internal, "transient"))
231 } else {
232 Ok(())
233 }
234 }
235 }));
236
237 let handler = chain_handlers(
238 base,
239 &[Arc::new(RetryMiddleware {
240 config: test_policy(),
241 })],
242 );
243
244 handler
245 .handle(Message::new("t", "data".to_string()))
246 .await
247 .unwrap();
248 assert_eq!(counter.load(Ordering::SeqCst), 2);
249 }
250
251 #[tokio::test]
252 async fn retry_exhausts_all_attempts() {
253 let counter = Arc::new(AtomicU32::new(0));
254 let c = counter.clone();
255 let base: Arc<dyn MessageHandler<String>> =
256 Arc::new(FnHandler::new(move |_msg: Message<String>| {
257 let c = c.clone();
258 async move {
259 c.fetch_add(1, Ordering::SeqCst);
260 Err(AppError::new(ErrorCode::Internal, "always fails"))
261 }
262 }));
263
264 let handler = chain_handlers(
265 base,
266 &[Arc::new(RetryMiddleware {
267 config: test_policy(),
268 })],
269 );
270
271 let result = handler.handle(Message::new("t", "data".to_string())).await;
272 assert!(result.is_err());
273 assert_eq!(counter.load(Ordering::SeqCst), 3);
274 }
275 #[tokio::test]
276 async fn default_retry_config_retries_non_retryable_handler_errors() {
277 let counter = Arc::new(AtomicU32::new(0));
278 let c = counter.clone();
279 let base: Arc<dyn MessageHandler<String>> =
280 Arc::new(FnHandler::new(move |_msg: Message<String>| {
281 let c = c.clone();
282 async move {
283 c.fetch_add(1, Ordering::SeqCst);
284 Err(AppError::new(
285 ErrorCode::InvalidInput,
286 "invalid but retried",
287 ))
288 }
289 }));
290
291 let handler = chain_handlers(
292 base,
293 &[Arc::new(RetryMiddleware {
294 config: RetryConfig::new()
295 .with_max_attempts(2)
296 .with_initial_backoff(Duration::from_millis(1))
297 .with_jitter(false),
298 })],
299 );
300
301 let result = handler.handle(Message::new("t", "data".to_string())).await;
302
303 assert!(result.is_err());
304 assert_eq!(counter.load(Ordering::SeqCst), 2);
305 }
306
307 #[tokio::test]
308 async fn public_retry_constructor_and_config_builders_wrap_handler() {
309 let callback_count = Arc::new(AtomicU32::new(0));
310 let callback_seen = callback_count.clone();
311 let config = RetryConfig::from_policy(RetryPolicy::new())
312 .with_max_attempts(1)
313 .with_max_backoff(Duration::from_millis(5))
314 .with_backoff_factor(1.0)
315 .with_constant_backoff(ConstantBackoff::new(Duration::from_millis(1)))
316 .with_linear_backoff(LinearBackoff::new(
317 Duration::from_millis(1),
318 Duration::from_millis(1),
319 Duration::from_millis(5),
320 ))
321 .with_retry_if(|_| false)
322 .with_on_retry(move |_, _| {
323 callback_seen.fetch_add(1, Ordering::SeqCst);
324 });
325 let _policy = config.clone().into_policy();
326 let _borrowed = config.policy();
327
328 let base: Arc<dyn MessageHandler<String>> =
329 Arc::new(FnHandler::new(|_msg: Message<String>| async { Ok(()) }));
330 let middleware: Arc<dyn HandlerMiddleware<String>> = Arc::new(retry(config));
331 let handler = chain_handlers(base, &[middleware]);
332
333 handler
334 .handle(Message::new("t", "ok".to_string()))
335 .await
336 .unwrap();
337 assert_eq!(callback_count.load(Ordering::SeqCst), 0);
338 }
339}