use dashmap::DashMap;
use parking_lot::Mutex;
use rand::seq::IteratorRandom;
use std::{
borrow::Borrow,
hash::Hash,
sync::Arc,
time::{
Duration,
Instant,
},
};
const DEFAULT_EXPIRE_TIME: Duration = Duration::from_secs(10 * 60);
pub struct TimedCache<K, V>(Arc<TimedCacheInner<K, V>>);
struct TimedCacheInner<K, V> {
cache: DashMap<K, Arc<TimedCacheEntry<V>>>,
last_trim: Mutex<Instant>,
trim_time: Duration,
expiry_time: Duration,
}
impl<K, V> TimedCache<K, V>
where
K: Eq + Hash + 'static,
V: 'static,
{
pub fn new() -> Self {
TimedCache(Arc::new(TimedCacheInner {
cache: DashMap::new(),
last_trim: Mutex::new(Instant::now()),
trim_time: DEFAULT_EXPIRE_TIME,
expiry_time: DEFAULT_EXPIRE_TIME,
}))
}
pub fn get_if_fresh<Q>(&self, key: &Q) -> Option<Arc<TimedCacheEntry<V>>>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
self.0.cache.get(key).and_then(|entry| {
if entry.is_fresh(self.0.expiry_time) {
Some(entry.value().clone())
} else {
None
}
})
}
pub fn get_random_if_fresh(&self) -> Option<Arc<TimedCacheEntry<V>>> {
self.0
.cache
.iter()
.filter(|entry| entry.is_fresh(self.0.expiry_time))
.choose(&mut rand::thread_rng())
.map(|v| v.value().clone())
}
pub fn insert(&self, key: K, value: V) {
self.0.cache.insert(
key,
Arc::new(TimedCacheEntry {
data: value,
last_update: Instant::now(),
}),
);
}
pub fn insert_and_get(&self, key: K, value: V) -> Arc<TimedCacheEntry<V>> {
let data = Arc::new(TimedCacheEntry {
data: value,
last_update: Instant::now(),
});
self.0.cache.insert(key, data.clone());
data
}
pub fn trim(&self) -> bool {
let mut last_trim = self.0.last_trim.lock();
if Instant::now().duration_since(*last_trim) > self.0.trim_time {
*last_trim = Instant::now();
drop(last_trim);
self.force_trim();
true
} else {
false
}
}
pub fn force_trim(&self) {
let expiry_time = self.0.expiry_time;
self.0.cache.retain(|_, v| !v.is_fresh(expiry_time));
}
pub fn len(&self) -> usize {
self.0.cache.len()
}
pub fn is_empty(&self) -> bool {
self.0.cache.is_empty()
}
}
impl<K, V> Default for TimedCache<K, V>
where
K: Eq + Hash + 'static,
V: 'static,
{
fn default() -> Self {
Self::new()
}
}
impl<K, V> Clone for TimedCache<K, V>
where
K: Eq + Hash + 'static,
V: 'static,
{
fn clone(&self) -> Self {
TimedCache(self.0.clone())
}
}
impl<K, V> std::fmt::Debug for TimedCache<K, V>
where
K: Eq + std::fmt::Debug + Hash,
V: std::fmt::Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TimedCache")
.field("cache", &self.0.cache)
.finish()
}
}
#[derive(Debug)]
pub struct TimedCacheEntry<T> {
data: T,
last_update: Instant,
}
impl<T> TimedCacheEntry<T> {
pub fn is_fresh(&self, time: Duration) -> bool {
self.last_update.elapsed() < time
}
pub fn data(&self) -> &T {
&self.data
}
}