use std::borrow::Borrow;
use std::cmp;
use std::collections::BTreeMap;
use std::time::{Duration, Instant};
use async_io::Timer;
use async_lock::{RwLock, RwLockUpgradableReadGuard};
use futures_lite::stream::StreamExt;
use log::{debug, log_enabled, trace, Level};
use rand::seq::index;
use crate::entry::{CacheEntry, CacheExpiration, CacheReadGuard, CacheWriteGuard};
pub struct Cache<K, V> {
store: RwLock<BTreeMap<K, CacheEntry<V>>>,
label: String,
}
impl<K, V> Cache<K, V>
where
K: Ord + Clone,
{
pub fn new() -> Self {
Self {
store: RwLock::new(BTreeMap::new()),
label: "".to_owned(),
}
}
pub fn with_label(mut self, s: &str) -> Self {
self.label = format!("cache({}): ", s);
self
}
pub async fn clear(&self) {
self.store.write().await.clear()
}
pub async fn expired(&self) -> usize {
self.store
.read()
.await
.iter()
.filter(|(_, entry)| entry.expiration().is_expired())
.count()
}
pub async fn get<B>(&self, k: &B) -> Option<CacheReadGuard<'_, K, V>>
where
K: Borrow<B>,
B: Ord + ?Sized,
{
let guard = self.store.read().await;
let found = guard.get(k)?;
if found.expiration().is_expired() {
return None;
}
Some(CacheReadGuard {
entry: found as *const CacheEntry<V>,
_lock: guard,
})
}
pub async fn get_mut<B>(&self, k: &B) -> Option<CacheWriteGuard<'_, K, V>>
where
K: Borrow<B>,
B: Ord + ?Sized,
{
let mut guard = self.store.write().await;
let found = guard.get_mut(k)?;
if found.expiration().is_expired() {
return None;
}
Some(CacheWriteGuard {
entry: found as *mut CacheEntry<V>,
_lock: guard,
})
}
pub async fn len(&self) -> usize {
self.store.read().await.len()
}
pub async fn insert<E>(&self, k: K, v: V, e: E) -> Option<V>
where
E: Into<CacheExpiration>,
{
let entry = CacheEntry::new(v, e.into());
self.store.write().await.insert(k, entry).and_then(|entry| {
if entry.expiration().is_expired() {
None
} else {
Some(entry.into_inner())
}
})
}
pub async fn is_empty(&self) -> bool {
self.store.read().await.is_empty()
}
pub async fn monitor(&self, sample: usize, threshold: f64, frequency: Duration) {
assert!(sample > 0, "sample must be > 0");
assert!((0.0..=1.0).contains(&threshold), "threshold must be 0..=1");
assert!(frequency > Duration::from_secs(0), "frequency must be > 0");
let mut interval = Timer::interval(frequency);
loop {
interval.next().await;
self.purge(sample, threshold).await;
}
}
pub async fn purge(&self, sample: usize, threshold: f64) {
assert!(sample > 0, "sample must be > 0");
assert!((0.0..=1.0).contains(&threshold), "threshold must be 0..=1");
let start = Instant::now();
let mut locked = Duration::from_nanos(0);
let mut removed = 0;
loop {
let store = self.store.upgradable_read().await;
if store.is_empty() {
break;
}
let total = store.len();
let sample = cmp::min(sample, total);
let mut indices = index::sample(&mut rand::rng(), total, sample).into_vec();
let mut entries = store.iter();
indices.sort_unstable();
let mut keys = Vec::new();
let mut previous = 0;
for (position, index) in indices.into_iter().enumerate() {
let offset = if position == 0 {
index
} else {
index - previous - 1
};
let (key, entry) = entries.nth(offset).expect("sampled cache index must exist");
if entry.expiration().is_expired() {
keys.push(key.clone());
}
previous = index;
}
if !keys.is_empty() {
let acquired = Instant::now();
let mut store = RwLockUpgradableReadGuard::upgrade(store).await;
for key in &keys {
store.remove(key);
}
locked = locked.checked_add(acquired.elapsed()).unwrap();
}
let gone = keys.len();
let ratio = gone as f64 / sample as f64;
if log_enabled!(Level::Trace) {
trace!(
"{}removed {} / {} ({:.2}%) of the sampled keys",
self.label,
gone,
sample,
ratio * 100f64,
);
}
removed += gone;
if ratio <= threshold {
break;
}
}
if log_enabled!(Level::Debug) {
debug!(
"{}purge loop removed {} entries in {:.0?} ({:.0?} locked)",
self.label,
removed,
start.elapsed(),
locked
);
}
}
pub async fn remove<B>(&self, k: &B) -> Option<V>
where
K: Borrow<B>,
B: Ord + ?Sized,
{
self.store.write().await.remove(k).and_then(|entry| {
if entry.expiration().is_expired() {
None
} else {
Some(entry.into_inner())
}
})
}
pub async fn unexpired(&self) -> usize {
self.store
.read()
.await
.iter()
.filter(|(_, entry)| !entry.expiration().is_expired())
.count()
}
pub async fn update<B, F>(&self, k: &B, f: F)
where
K: Borrow<B>,
B: Ord + ?Sized,
F: FnOnce(&mut V),
{
if let Some(mut entry) = self.get_mut(k).await {
f(entry.value_mut());
}
}
}
impl<K, V> Default for Cache<K, V>
where
K: Ord + Clone,
{
fn default() -> Self {
Cache::new()
}
}