use std::collections::HashMap;
use std::collections::VecDeque;
use std::sync::Mutex;
pub type NowFn = Box<dyn Fn() -> u64 + Send + Sync>;
pub struct ReplayCache {
map: Mutex<Inner>,
max_entries: usize,
now: NowFn,
}
struct Inner {
expirations: HashMap<String, u64>,
order: VecDeque<String>,
}
fn default_now_ms() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0)
}
impl Default for ReplayCache {
fn default() -> Self {
Self::new(100_000, Box::new(default_now_ms))
}
}
impl ReplayCache {
pub fn new(max_entries: usize, now: NowFn) -> Self {
Self {
map: Mutex::new(Inner {
expirations: HashMap::new(),
order: VecDeque::new(),
}),
max_entries,
now,
}
}
pub fn mark_used(&self, key: &str, expires_at_ms: u64) {
let mut inner = self.map.lock().unwrap();
if inner.expirations.len() >= self.max_entries {
if let Some(oldest) = inner.order.pop_front() {
inner.expirations.remove(&oldest);
}
}
if inner
.expirations
.insert(key.to_string(), expires_at_ms)
.is_none()
{
inner.order.push_back(key.to_string());
}
}
pub fn is_used(&self, key: &str) -> bool {
let mut inner = self.map.lock().unwrap();
let Some(exp) = inner.expirations.get(key).copied() else {
return false;
};
if exp <= (self.now)() {
inner.expirations.remove(key);
return false;
}
true
}
}