use parking_lot::RwLock;
use std::{
collections::{HashMap, VecDeque},
sync::Arc,
time::{Duration, Instant},
};
pub struct IdentityCache {
inner: Arc<RwLock<CacheInner>>,
}
struct CacheInner {
keys: HashMap<Arc<str>, Option<Instant>>,
eviction_queue: VecDeque<(Arc<str>, Option<Instant>)>,
max_size: usize,
}
impl IdentityCache {
pub fn new() -> Self {
Self::with_capacity(1000)
}
pub fn with_capacity(max_size: usize) -> Self {
Self {
inner: Arc::new(RwLock::new(CacheInner {
keys: HashMap::with_capacity(max_size),
eviction_queue: VecDeque::with_capacity(max_size),
max_size,
})),
}
}
pub fn insert(&self, key: &str, ttl: Option<Duration>) -> bool {
let mut cache = self.inner.write();
let key: Arc<str> = key.into();
let now = Instant::now();
let expiry = ttl.map(|d| now + d);
if let Some(existing_expiry) = cache.keys.get(&key) {
match existing_expiry {
None => return false, Some(exp) if now < *exp => return false, Some(_) => {
cache.keys.remove(&key);
}
}
}
loop {
let should_remove = cache.eviction_queue.front().and_then(|(_, expiry)| {
expiry.map(|exp| now >= exp)
}).unwrap_or(false);
if should_remove {
if let Some((front_key, _)) = cache.eviction_queue.pop_front() {
cache.keys.remove(&front_key);
}
} else {
break;
}
}
if cache.keys.len() >= cache.max_size {
if let Some((old_key, _)) = cache.eviction_queue.pop_front() {
cache.keys.remove(&old_key);
}
}
cache.keys.insert(Arc::clone(&key), expiry);
cache.eviction_queue.push_back((key, expiry));
true
}
pub fn contains(&self, key: &str) -> bool {
let cache = self.inner.read();
cache.keys.get(key).map(|expiry| match expiry {
None => true, Some(exp) => Instant::now() < *exp,
}).unwrap_or(false)
}
pub fn len(&self) -> usize {
self.inner.read().keys.len()
}
}
impl Clone for IdentityCache {
fn clone(&self) -> Self {
Self {
inner: Arc::clone(&self.inner),
}
}
}
impl Default for IdentityCache {
fn default() -> Self {
Self::new()
}
}