globalcache-0.2.0/.gitignore000064400000000000000000000000240072674642500140630ustar 00000000000000/target /Cargo.lock globalcache-0.2.0/Cargo.toml0000644000000017760000000000100112700ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "globalcache" version = "0.2.0" description = "Cache utility that allows per-process managent of many cache instances" license = "MIT" resolver = "2" [dependencies.async-trait] version = "0.1.53" [dependencies.once_cell] version = "1.10.0" optional = true [dependencies.tokio] version = "1.18.2" features = [ "sync", "rt", "time", ] optional = true [dependencies.tracing] version = "0.1.34" optional = true [features] async = ["tokio"] global = [ "tokio", "tracing", "once_cell", ] sync = [] globalcache-0.2.0/Cargo.toml.orig000064400000000000000000000010760072674642500147720ustar 00000000000000[package] name = "globalcache" version = "0.2.0" edition = "2021" description = "Cache utility that allows per-process managent of many cache instances" license = "MIT" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] sync = [] async = ["tokio"] global = ["tokio", "tracing", "once_cell"] [dependencies] tokio = { version = "1.18.2", optional = true, features = ["sync", "rt", "time"] } tracing = { version = "0.1.34", optional = true } once_cell = { version = "1.10.0", optional = true } async-trait = "0.1.53" globalcache-0.2.0/src/async.rs000064400000000000000000000117120072674642500143530ustar 00000000000000use std::collections::hash_map::{HashMap, Entry}; use std::hash::Hash; use std::mem::replace; use std::sync::Arc; use tokio::sync::{Notify, Mutex}; use tokio::time::{Instant, Duration}; use super::{ValueSize, CacheControl, global::GlobalCache}; use std::future::{Future, ready}; use async_trait::async_trait; struct Computed { value: V, size: usize, time: f64, last_used: Instant, } enum Value { InProcess(Arc), Computed(Computed), } impl Value { fn unwrap(self) -> V { match self { Value::Computed(v) => v.value, _ => unreachable!() } } } pub struct AsyncCache { name: Option, inner: Mutex>, } struct CacheInner { entries: HashMap>, } impl AsyncCache where K: Clone + Hash + Eq + Send + 'static, V: Clone + ValueSize + Send + 'static, { pub fn new() -> Arc { Self::with_name(None) } pub fn with_name(name: Option) -> Arc { let cache = Arc::new(AsyncCache { name: name.into(), inner: Mutex::new(CacheInner { entries: HashMap::new(), }) }); GlobalCache::register(Arc::downgrade(&cache)); cache } pub fn entries(self) -> impl Iterator { self.inner.into_inner() .entries .into_iter() .map(|(k, v)| (k, v.unwrap())) } pub async fn get(&self, key: K, compute: impl FnOnce() -> V) -> V { self.get_async(key, || ready(compute())).await } pub async fn get_async(&self, key: K, compute: C) -> V where F: Future, C: FnOnce() -> F { let mut guard = self.inner.lock().await; let key2 = key.clone(); match guard.entries.entry(key) { Entry::Occupied(mut e) => match e.get_mut() { &mut Value::Computed(ref mut v) => { v.last_used = Instant::now(); return v.value.clone() } &mut Value::InProcess(ref condvar) => { let condvar = condvar.clone(); drop(guard); return self.poll(key2, condvar).await; } } Entry::Vacant(e) => { let key = e.key().clone(); let notify = Arc::new(Notify::new()); e.insert(Value::InProcess(notify)); drop(guard); let start = Instant::now(); let value = compute().await; let size = value.size(); let duration = start.elapsed(); let value2 = value.clone(); let time = duration.as_secs_f64() + 0.000001; let mut guard = self.inner.lock().await; let c = Computed { value, size, time, last_used: start }; let slot = guard.entries.get_mut(&key).unwrap(); let slot = replace(slot, Value::Computed(c)); match slot { Value::InProcess(ref notify) => notify.notify_waiters(), _ => unreachable!() } return value2; } } } async fn poll(&self, key: K, notify: Arc) -> V { loop { notify.notified().await; let mut guard = self.inner.lock().await; let inner = &mut *guard; if let &mut Value::Computed(ref mut v) = inner.entries.get_mut(&key).unwrap() { v.last_used = Instant::now(); return v.value.clone(); } } } } #[async_trait] impl CacheControl for AsyncCache where K: Eq + Hash + Send + 'static, V: Clone + ValueSize + Send + 'static { fn name(&self) -> Option<&str> { self.name.as_deref() } async fn clean(&self, threshold: f64, time_scale: f64) -> (usize, f64) { self.inner.lock().await.clean(threshold, time_scale) } } impl CacheInner { fn clean(&mut self, threshold: f64, time_scale: f64) -> (usize, f64) { let mut size_sum = 0; let now = Instant::now() + Duration::from_secs_f64(time_scale); let mut time_sum = 0.0; self.entries.retain(|_, value| { match value { Value::Computed(ref entry) => { let elapsed = now.duration_since(entry.last_used); let value = entry.time / (entry.size as f64 * elapsed.as_secs_f64()); if value > threshold { time_sum += entry.time; size_sum += entry.size; true } else { false } } Value::InProcess(_) => true } }); (size_sum, time_sum) } } globalcache-0.2.0/src/global.rs000064400000000000000000000044310072674642500144760ustar 00000000000000use tracing::info; use tokio::sync::mpsc; use std::sync::Weak; use once_cell::sync::OnceCell; use super::CacheControl; use std::future::Future; pub struct GlobalCache { register: mpsc::UnboundedSender>, } static GLOBAL: OnceCell = OnceCell::new(); pub fn global_init(config: CacheConfig) { tokio::spawn(global_cleaner(config)); } pub struct CacheConfig { //// memory limit in bytes pub memory_limit: usize, /// time scale in seconds pub time_scale: f64, } pub fn global_cleaner(config: CacheConfig) -> impl Future { use tokio::time::{Duration, sleep}; let (tx, mut rx) = mpsc::unbounded_channel(); GlobalCache { register: tx }.make_global(); async move { let mut slots = vec![]; let mut last_threshold = 0.0; let mut new_slots = vec![]; loop { while let Ok(new) = rx.try_recv() { slots.push(new); } let mut total_size = 0; let mut total_time = 0.; for slot in slots.drain(..) { if let Some(cache) = Weak::upgrade(&slot) { let (size, time_sum) = cache.clean(last_threshold, config.time_scale).await; total_size += size; total_time += time_sum; new_slots.push(slot); // keep it } } std::mem::swap(&mut slots, &mut new_slots); if total_size > 0 { let value = total_time / (total_size as f64 * config.time_scale); let scale = total_size as f64 / config.memory_limit as f64; last_threshold = value * scale; } else { last_threshold = 0.0; } info!("{} seconds cached in {} bytes", total_time, total_size); //info!("new threshold: {}", last_threshold); sleep(Duration::from_secs(1)).await; } } } impl GlobalCache { pub fn global() -> &'static Self { GLOBAL.get().unwrap() } pub fn make_global(self) { GLOBAL.set(self).ok().expect(""); } pub fn register(cache: Weak) { if let Some(global) = GLOBAL.get() { global.register.send(cache as _).ok().unwrap(); } } } globalcache-0.2.0/src/lib.rs000064400000000000000000000034600072674642500140050ustar 00000000000000/** Goals: - limit global memory use - minimize computation time Track globally: - average value - average age Track for each entry: - age: time since last use - count: how often it was used - amout of memory used - time required to compute the value use = count / (age) value = time / memory Don't add: - values lower than average Don't evict: - newer values **/ use std::sync::{Arc}; use async_trait::async_trait; #[cfg(feature="sync")] pub mod sync; #[cfg(feature="async")] pub mod r#async; #[cfg(feature="global")] pub mod global; #[cfg(not(feature="global"))] pub mod global { use std::sync::Weak; use super::CacheControl; pub struct GlobalCache; impl GlobalCache { pub fn register(_: Weak) {} } } #[async_trait] pub trait CacheControl: Sync + Send + 'static { fn name(&self) -> Option<&str>; async fn clean(&self, threshold: f64, time_scale: f64) -> (usize, f64); } pub trait ValueSize { fn size(&self) -> usize; } impl ValueSize for Result { #[inline] fn size(&self) -> usize { match self { &Ok(ref t) => t.size(), &Err(ref e) => e.size(), } } } impl ValueSize for Arc { #[inline] fn size(&self) -> usize { ValueSize::size(&**self) } } impl ValueSize for [u8] { #[inline] fn size(&self) -> usize { self.len() } } impl ValueSize for Option { #[inline] fn size(&self) -> usize { match *self { None => 0, Some(ref val) => val.size() } } } impl ValueSize for () { #[inline] fn size(&self) -> usize { 0 } } impl ValueSize for String { #[inline] fn size(&self) -> usize { self.capacity() } } globalcache-0.2.0/src/sync.rs000064400000000000000000000113410072674642500142100ustar 00000000000000use std::sync::{Mutex, Condvar, Arc, MutexGuard}; use std::collections::hash_map::{HashMap, Entry}; use std::hash::Hash; use std::mem::replace; use std::time::{Instant, Duration}; use super::{ValueSize, CacheControl, global::GlobalCache}; use async_trait::async_trait; struct Computed { value: V, time: f64, size: usize, last_used: Instant, } enum Value { InProcess(Arc), Computed(Computed), } impl Value { fn unwrap(self) -> V { match self { Value::Computed(v) => v.value, _ => unreachable!() } } } pub struct SyncCache { name: Option, inner: Mutex>, } struct CacheInner { entries: HashMap>, } impl SyncCache where K: Hash + Eq + Clone + Send + 'static, V: Clone + ValueSize + Send + 'static, { pub fn new() -> Arc { Self::with_name(None) } pub fn with_name(name: Option) -> Arc { let cache = Arc::new(SyncCache { name, inner: Mutex::new(CacheInner { entries: HashMap::new(), }) }); GlobalCache::register(Arc::downgrade(&cache)); cache } pub fn get(&self, key: K, compute: impl FnOnce() -> V) -> V { let mut guard = self.inner.lock().unwrap(); match guard.entries.entry(key) { Entry::Occupied(e) => match e.get() { &Value::Computed(ref v) => return v.value.clone(), &Value::InProcess(ref condvar) => { let key = e.key().clone(); let condvar = condvar.clone(); return Self::poll(key, guard, condvar); } } Entry::Vacant(e) => { let key = e.key().clone(); let condvar = Arc::new(Condvar::new()); e.insert(Value::InProcess(condvar)); drop(guard); let start = Instant::now(); let value = compute(); let size = value.size(); let duration = start.elapsed(); let value2 = value.clone(); let time = duration.as_secs_f64() + 0.000001; let last_used = Instant::now(); let mut guard = self.inner.lock().unwrap(); let c = Computed { value, size, time, last_used, }; let slot = guard.entries.get_mut(&key).unwrap(); let slot = replace(slot, Value::Computed(c)); match slot { Value::InProcess(ref condvar) => condvar.notify_all(), _ => unreachable!() } return value2; } } } pub fn entries(arc: Arc) -> impl Iterator { let cache = Arc::try_unwrap(arc).ok().unwrap(); cache.inner.into_inner().unwrap() .entries .into_iter() .map(|(k, v)| (k, v.unwrap())) } fn poll(key: K, mut guard: MutexGuard>, condvar: Arc) -> V { let last_used = Instant::now(); loop { guard = condvar.wait(guard).unwrap(); let inner = &mut *guard; if let &mut Value::Computed(ref mut v) = inner.entries.get_mut(&key).unwrap() { v.last_used = last_used; return v.value.clone(); } } } } #[async_trait] impl CacheControl for SyncCache where K: Eq + Hash + Send + 'static, V: Clone + ValueSize + Send + 'static { fn name(&self) -> Option<&str> { self.name.as_deref() } async fn clean(&self, threshold: f64, time_scale: f64) -> (usize, f64) { self.inner.lock().unwrap().clean(threshold, time_scale) } } impl CacheInner { fn clean(&mut self, threshold: f64, time_scale: f64) -> (usize, f64) { let now = Instant::now() + Duration::from_secs_f64(time_scale); let mut size_sum = 0; let mut time_sum = 0.0; self.entries.retain(|_, value| { match value { Value::Computed(ref entry) => { let elapsed = now.duration_since(entry.last_used); let value = entry.time / (entry.size as f64 * elapsed.as_secs_f64()); if value > threshold { time_sum += entry.time; size_sum += entry.size; true } else { false } } Value::InProcess(_) => true } }); (size_sum, time_sum) } }