use std::borrow::Borrow;
use std::collections::HashMap;
use std::hash::Hash;
use parking_lot::Mutex;
use tokio::time::{Duration, Instant};
#[derive(Debug)]
pub struct TimedCache<K, V> {
inner: Mutex<HashMap<K, (Instant, V)>>,
min_ttl: f64,
max_ttl: f64,
}
impl<K: Eq + Hash, V: Clone> TimedCache<K, V> {
pub fn new(min_ttl: f64, max_ttl: f64) -> Self {
Self {
inner: Default::default(),
min_ttl,
max_ttl,
}
}
pub fn get<Q>(&self, k: &Q) -> Option<V>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
let mut inner = self.inner.lock();
let (expire_time, value) = inner.get(k)?;
if *expire_time >= Instant::now() {
Some(value.clone())
} else {
inner.remove(k);
None
}
}
pub fn insert(&self, k: K, v: V) {
let ttl = self.min_ttl + fastrand::f64() * (self.max_ttl - self.min_ttl);
let expire_time = Instant::now() + Duration::from_secs_f64(ttl);
self.inner.lock().insert(k, (expire_time, v));
}
}
#[cfg(test)]
mod tests {
use tokio::time::{self, Duration};
use super::TimedCache;
#[tokio::test(start_paused = true)]
async fn timed_expire() {
let cache = TimedCache::new(5.0, 10.0);
cache.insert("foo", "bar");
time::advance(Duration::from_secs(3)).await;
assert_eq!(cache.get("foo"), Some("bar"));
time::advance(Duration::from_secs(10)).await;
assert_eq!(cache.get("foo"), None);
}
}