nonce_auth/nonce/
cleanup.rs1use std::future::Future;
2use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
3use std::time::{Duration, SystemTime, UNIX_EPOCH};
4
5use async_trait::async_trait;
6
7#[async_trait]
13pub trait CleanupStrategy: Send + Sync {
14 async fn should_cleanup(&self) -> bool;
19
20 async fn mark_as_cleaned(&self);
25}
26
27pub struct HybridCleanupStrategy {
34 count_threshold: u32,
35 time_threshold: Duration,
36 request_count: AtomicU32,
37 last_cleanup_time: AtomicU64,
38}
39
40impl HybridCleanupStrategy {
41 pub fn new(count_threshold: u32, time_threshold: Duration) -> Self {
58 let now = SystemTime::now()
59 .duration_since(UNIX_EPOCH)
60 .unwrap_or_default()
61 .as_secs();
62
63 Self {
64 count_threshold,
65 time_threshold,
66 request_count: AtomicU32::new(0),
67 last_cleanup_time: AtomicU64::new(now),
68 }
69 }
70
71 pub fn set_thresholds(&mut self, count_threshold: u32, time_threshold: Duration) {
75 self.count_threshold = count_threshold;
76 self.time_threshold = time_threshold;
77 }
78}
79
80#[async_trait]
81impl CleanupStrategy for HybridCleanupStrategy {
82 async fn should_cleanup(&self) -> bool {
83 let count = self.request_count.fetch_add(1, Ordering::SeqCst) + 1;
85
86 if count >= self.count_threshold {
88 return true;
89 }
90
91 let now = SystemTime::now()
93 .duration_since(UNIX_EPOCH)
94 .unwrap_or_default()
95 .as_secs();
96 let last_cleanup = self.last_cleanup_time.load(Ordering::SeqCst);
97 let elapsed = now.saturating_sub(last_cleanup);
98
99 elapsed >= self.time_threshold.as_secs()
100 }
101
102 async fn mark_as_cleaned(&self) {
103 self.request_count.store(0, Ordering::SeqCst);
104 let now = SystemTime::now()
105 .duration_since(UNIX_EPOCH)
106 .unwrap_or_default()
107 .as_secs();
108 self.last_cleanup_time.store(now, Ordering::SeqCst);
109 }
110}
111
112impl Default for HybridCleanupStrategy {
113 fn default() -> Self {
117 Self::new(100, Duration::from_secs(300))
118 }
119}
120
121pub struct CustomCleanupStrategy<F, Fut>
126where
127 F: Fn() -> Fut + Send + Sync + 'static,
128 Fut: Future<Output = bool> + Send + 'static,
129{
130 strategy_fn: F,
131}
132
133impl<F, Fut> CustomCleanupStrategy<F, Fut>
134where
135 F: Fn() -> Fut + Send + Sync + 'static,
136 Fut: Future<Output = bool> + Send + 'static,
137{
138 pub fn new(strategy_fn: F) -> Self {
160 Self { strategy_fn }
161 }
162}
163
164#[async_trait]
165impl<F, Fut> CleanupStrategy for CustomCleanupStrategy<F, Fut>
166where
167 F: Fn() -> Fut + Send + Sync + 'static,
168 Fut: Future<Output = bool> + Send + 'static,
169{
170 async fn should_cleanup(&self) -> bool {
171 (self.strategy_fn)().await
172 }
173
174 async fn mark_as_cleaned(&self) {
175 }
178}
179
180pub type BoxedCleanupStrategy = Box<dyn CleanupStrategy>;
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186 use std::sync::Arc;
187 use std::sync::atomic::{AtomicBool, AtomicU32};
188 use tokio::time::{Duration as TokioDuration, sleep};
189
190 #[tokio::test]
191 async fn test_hybrid_strategy_count_threshold() {
192 let strategy = HybridCleanupStrategy::new(3, Duration::from_secs(3600)); assert!(!strategy.should_cleanup().await);
196 assert!(!strategy.should_cleanup().await);
197
198 assert!(strategy.should_cleanup().await);
200 }
201
202 #[tokio::test]
203 async fn test_hybrid_strategy_time_threshold() {
204 let strategy = HybridCleanupStrategy::new(100, Duration::from_secs(1)); let result1 = strategy.should_cleanup().await;
208 assert!(!result1, "First request should not trigger cleanup");
209
210 sleep(TokioDuration::from_millis(1100)).await;
212
213 let result2 = strategy.should_cleanup().await;
215 assert!(
216 result2,
217 "Second request after time threshold should trigger cleanup"
218 );
219 }
220
221 #[tokio::test]
222 async fn test_hybrid_strategy_reset_after_cleanup() {
223 let strategy = HybridCleanupStrategy::new(2, Duration::from_secs(3600));
224
225 assert!(!strategy.should_cleanup().await);
227 assert!(strategy.should_cleanup().await);
228
229 strategy.mark_as_cleaned().await;
231
232 assert!(!strategy.should_cleanup().await);
234 }
235
236 #[tokio::test]
237 async fn test_custom_strategy() {
238 let counter = Arc::new(AtomicU32::new(0));
239 let counter_clone = Arc::clone(&counter);
240
241 let strategy = CustomCleanupStrategy::new(move || {
242 let counter = Arc::clone(&counter_clone);
243 async move {
244 let count = counter.fetch_add(1, Ordering::SeqCst) + 1;
245 count.is_multiple_of(2) }
247 });
248
249 assert!(!strategy.should_cleanup().await);
251
252 assert!(strategy.should_cleanup().await);
254
255 assert!(!strategy.should_cleanup().await);
257 }
258
259 #[tokio::test]
260 async fn test_custom_strategy_mark_as_cleaned_noop() {
261 let flag = Arc::new(AtomicBool::new(false));
262 let flag_clone = Arc::clone(&flag);
263
264 let strategy = CustomCleanupStrategy::new(move || {
265 let flag = Arc::clone(&flag_clone);
266 async move { flag.load(Ordering::SeqCst) }
267 });
268
269 assert!(!strategy.should_cleanup().await);
271
272 strategy.mark_as_cleaned().await;
274
275 assert!(!strategy.should_cleanup().await);
277
278 flag.store(true, Ordering::SeqCst);
280
281 assert!(strategy.should_cleanup().await);
283 }
284}