use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, LazyLock, RwLock};
use std::time::Instant;
use cljrs_ir::IrFunction;
pub enum IrCacheEntry {
NotAttempted,
Unsupported,
Cached {
ir: Arc<IrFunction>,
last_access: AtomicU64,
},
}
static PROCESS_EPOCH: LazyLock<Instant> = LazyLock::new(Instant::now);
pub fn now_secs() -> u64 {
PROCESS_EPOCH.elapsed().as_secs()
}
pub fn ir_cache_ttl_secs() -> u64 {
std::env::var("CLJRS_IR_CACHE_TTL")
.ok()
.and_then(|s| s.parse::<u64>().ok())
.unwrap_or(600)
}
pub struct IrCache {
entries: RwLock<HashMap<u64, IrCacheEntry>>,
}
impl IrCache {
pub fn new() -> Self {
Self {
entries: RwLock::new(HashMap::new()),
}
}
pub fn get(&self, id: u64) -> Option<Arc<IrFunction>> {
let guard = self.entries.read().unwrap();
match guard.get(&id) {
Some(IrCacheEntry::Cached { ir, last_access }) => {
last_access.store(now_secs(), Ordering::Relaxed);
Some(ir.clone())
}
_ => None,
}
}
pub fn should_attempt(&self, id: u64) -> bool {
!self.entries.read().unwrap().contains_key(&id)
}
pub fn store(&self, id: u64, ir: Arc<IrFunction>) {
self.entries.write().unwrap().insert(
id,
IrCacheEntry::Cached {
ir,
last_access: AtomicU64::new(now_secs()),
},
);
}
pub fn store_unsupported(&self, id: u64) {
self.entries
.write()
.unwrap()
.insert(id, IrCacheEntry::Unsupported);
}
pub fn invalidate(&self, id: u64) {
self.entries.write().unwrap().remove(&id);
}
pub fn sweep(&self, now: u64, ttl_secs: u64, pinned: impl Fn(u64) -> bool) -> Vec<u64> {
let mut evicted = Vec::new();
let mut guard = self.entries.write().unwrap();
guard.retain(|&id, entry| {
let IrCacheEntry::Cached { last_access, .. } = entry else {
return true;
};
let idle = now.saturating_sub(last_access.load(Ordering::Relaxed));
if idle <= ttl_secs {
return true;
}
if pinned(id) {
return true;
}
evicted.push(id);
false
});
evicted
}
}
impl Default for IrCache {
fn default() -> Self {
Self::new()
}
}