use std::future::Future;
use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use async_trait::async_trait;
#[async_trait]
pub trait CleanupStrategy: Send + Sync {
async fn should_cleanup(&self) -> bool;
async fn mark_as_cleaned(&self);
}
pub struct HybridCleanupStrategy {
count_threshold: u32,
time_threshold: Duration,
request_count: AtomicU32,
last_cleanup_time: AtomicU64,
}
impl HybridCleanupStrategy {
pub fn new(count_threshold: u32, time_threshold: Duration) -> Self {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
Self {
count_threshold,
time_threshold,
request_count: AtomicU32::new(0),
last_cleanup_time: AtomicU64::new(now),
}
}
pub fn set_thresholds(&mut self, count_threshold: u32, time_threshold: Duration) {
self.count_threshold = count_threshold;
self.time_threshold = time_threshold;
}
}
#[async_trait]
impl CleanupStrategy for HybridCleanupStrategy {
async fn should_cleanup(&self) -> bool {
let count = self.request_count.fetch_add(1, Ordering::SeqCst) + 1;
if count >= self.count_threshold {
return true;
}
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
let last_cleanup = self.last_cleanup_time.load(Ordering::SeqCst);
let elapsed = now.saturating_sub(last_cleanup);
elapsed >= self.time_threshold.as_secs()
}
async fn mark_as_cleaned(&self) {
self.request_count.store(0, Ordering::SeqCst);
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
self.last_cleanup_time.store(now, Ordering::SeqCst);
}
}
impl Default for HybridCleanupStrategy {
fn default() -> Self {
Self::new(100, Duration::from_secs(300))
}
}
pub struct CustomCleanupStrategy<F, Fut>
where
F: Fn() -> Fut + Send + Sync + 'static,
Fut: Future<Output = bool> + Send + 'static,
{
strategy_fn: F,
}
impl<F, Fut> CustomCleanupStrategy<F, Fut>
where
F: Fn() -> Fut + Send + Sync + 'static,
Fut: Future<Output = bool> + Send + 'static,
{
pub fn new(strategy_fn: F) -> Self {
Self { strategy_fn }
}
}
#[async_trait]
impl<F, Fut> CleanupStrategy for CustomCleanupStrategy<F, Fut>
where
F: Fn() -> Fut + Send + Sync + 'static,
Fut: Future<Output = bool> + Send + 'static,
{
async fn should_cleanup(&self) -> bool {
(self.strategy_fn)().await
}
async fn mark_as_cleaned(&self) {
}
}
pub type BoxedCleanupStrategy = Box<dyn CleanupStrategy>;
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU32};
use tokio::time::{Duration as TokioDuration, sleep};
#[tokio::test]
async fn test_hybrid_strategy_count_threshold() {
let strategy = HybridCleanupStrategy::new(3, Duration::from_secs(3600));
assert!(!strategy.should_cleanup().await);
assert!(!strategy.should_cleanup().await);
assert!(strategy.should_cleanup().await);
}
#[tokio::test]
async fn test_hybrid_strategy_time_threshold() {
let strategy = HybridCleanupStrategy::new(100, Duration::from_secs(1));
let result1 = strategy.should_cleanup().await;
assert!(!result1, "First request should not trigger cleanup");
sleep(TokioDuration::from_millis(1100)).await;
let result2 = strategy.should_cleanup().await;
assert!(
result2,
"Second request after time threshold should trigger cleanup"
);
}
#[tokio::test]
async fn test_hybrid_strategy_reset_after_cleanup() {
let strategy = HybridCleanupStrategy::new(2, Duration::from_secs(3600));
assert!(!strategy.should_cleanup().await);
assert!(strategy.should_cleanup().await);
strategy.mark_as_cleaned().await;
assert!(!strategy.should_cleanup().await);
}
#[tokio::test]
async fn test_custom_strategy() {
let counter = Arc::new(AtomicU32::new(0));
let counter_clone = Arc::clone(&counter);
let strategy = CustomCleanupStrategy::new(move || {
let counter = Arc::clone(&counter_clone);
async move {
let count = counter.fetch_add(1, Ordering::SeqCst) + 1;
count.is_multiple_of(2) }
});
assert!(!strategy.should_cleanup().await);
assert!(strategy.should_cleanup().await);
assert!(!strategy.should_cleanup().await);
}
#[tokio::test]
async fn test_custom_strategy_mark_as_cleaned_noop() {
let flag = Arc::new(AtomicBool::new(false));
let flag_clone = Arc::clone(&flag);
let strategy = CustomCleanupStrategy::new(move || {
let flag = Arc::clone(&flag_clone);
async move { flag.load(Ordering::SeqCst) }
});
assert!(!strategy.should_cleanup().await);
strategy.mark_as_cleaned().await;
assert!(!strategy.should_cleanup().await);
flag.store(true, Ordering::SeqCst);
assert!(strategy.should_cleanup().await);
}
}