use std::collections::{BTreeMap, HashMap};
use std::future::Future;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
struct Entry<V> {
value: V,
expires: Instant,
seq: u64,
}
struct Inner<V> {
entries: HashMap<String, Entry<V>>,
order: BTreeMap<u64, String>,
next_seq: u64,
hits: u64,
misses: u64,
}
pub struct ValueCache<V> {
inner: Arc<Mutex<Inner<V>>>,
capacity: usize,
}
impl<V> Clone for ValueCache<V> {
fn clone(&self) -> Self {
ValueCache { inner: Arc::clone(&self.inner), capacity: self.capacity }
}
}
impl<V: Clone> ValueCache<V> {
pub fn new(capacity: usize) -> Self {
ValueCache {
inner: Arc::new(Mutex::new(Inner { entries: HashMap::new(), order: BTreeMap::new(), next_seq: 0, hits: 0, misses: 0 })),
capacity: capacity.max(1),
}
}
fn lock(&self) -> std::sync::MutexGuard<'_, Inner<V>> {
self.inner.lock().unwrap_or_else(|e| e.into_inner())
}
pub fn get(&self, key: &str) -> Option<V> {
let mut inner = self.lock();
let now = Instant::now();
let found = match inner.entries.get(key) {
Some(entry) if entry.expires > now => Some(entry.value.clone()),
Some(_) => {
if let Some(entry) = inner.entries.remove(key) {
inner.order.remove(&entry.seq);
}
None
}
None => None,
};
match found {
Some(value) => {
inner.hits += 1;
Some(value)
}
None => {
inner.misses += 1;
None
}
}
}
pub fn put(&self, key: &str, value: V, ttl: Duration) {
let mut inner = self.lock();
let now = Instant::now();
if let Some(old) = inner.entries.remove(key) {
inner.order.remove(&old.seq);
}
if inner.entries.len() >= self.capacity {
let expired: Vec<String> = inner.entries.iter().filter(|(_, e)| e.expires <= now).map(|(k, _)| k.clone()).collect();
for k in expired {
if let Some(e) = inner.entries.remove(&k) {
inner.order.remove(&e.seq);
}
}
}
while inner.entries.len() >= self.capacity {
let Some((&seq, _)) = inner.order.iter().next() else { break };
if let Some(oldest) = inner.order.remove(&seq) {
inner.entries.remove(&oldest);
}
}
let seq = inner.next_seq;
inner.next_seq += 1;
inner.order.insert(seq, key.to_string());
inner.entries.insert(key.to_string(), Entry { value, expires: now + ttl, seq });
}
pub fn fetch_or(&self, key: &str, ttl: Duration, compute: impl FnOnce() -> V) -> V {
if let Some(value) = self.get(key) {
return value;
}
let value = compute();
self.put(key, value.clone(), ttl);
value
}
pub fn try_fetch_or<E>(&self, key: &str, ttl: Duration, compute: impl FnOnce() -> Result<V, E>) -> Result<V, E> {
if let Some(value) = self.get(key) {
return Ok(value);
}
let value = compute()?;
self.put(key, value.clone(), ttl);
Ok(value)
}
pub async fn fetch_or_async<F, Fut>(&self, key: &str, ttl: Duration, compute: F) -> V
where
F: FnOnce() -> Fut,
Fut: Future<Output = V>,
{
if let Some(value) = self.get(key) {
return value;
}
let value = compute().await;
self.put(key, value.clone(), ttl);
value
}
pub async fn try_fetch_or_async<E, F, Fut>(&self, key: &str, ttl: Duration, compute: F) -> Result<V, E>
where
F: FnOnce() -> Fut,
Fut: Future<Output = Result<V, E>>,
{
if let Some(value) = self.get(key) {
return Ok(value);
}
let value = compute().await?;
self.put(key, value.clone(), ttl);
Ok(value)
}
pub fn invalidate(&self, key: &str) -> bool {
let mut inner = self.lock();
match inner.entries.remove(key) {
Some(entry) => {
inner.order.remove(&entry.seq);
true
}
None => false,
}
}
pub fn invalidate_prefix(&self, prefix: &str) -> usize {
let mut inner = self.lock();
let keys: Vec<String> = inner.entries.keys().filter(|k| k.starts_with(prefix)).cloned().collect();
for k in &keys {
if let Some(entry) = inner.entries.remove(k) {
inner.order.remove(&entry.seq);
}
}
keys.len()
}
pub fn clear(&self) {
let mut inner = self.lock();
inner.entries.clear();
inner.order.clear();
}
pub fn len(&self) -> usize {
self.lock().entries.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn stats(&self) -> (u64, u64) {
let inner = self.lock();
(inner.hits, inner.misses)
}
}
#[cfg(test)]
mod tests;