use std::{
any::{Any, TypeId},
collections::HashMap,
sync::{Mutex, OnceLock, RwLock},
};
use anyhow::Result;
#[derive(Default)]
pub struct ModulePlanCache {
plans: RwLock<HashMap<TypeId, Box<dyn Any + Send + Sync>>>,
}
struct PlanEntry<T> {
value: OnceLock<T>,
initialize: Mutex<()>,
}
impl<T> Default for PlanEntry<T> {
fn default() -> Self {
Self {
value: OnceLock::new(),
initialize: Mutex::new(()),
}
}
}
impl ModulePlanCache {
pub fn with_or_create<K, T, R>(&self, init: impl FnOnce() -> Result<T>, use_plans: impl FnOnce(&T) -> R) -> Result<R>
where
K: 'static,
T: Any + Send + Sync,
{
let key = TypeId::of::<K>();
let mut entry = {
let entries = self.plans.read().expect("module plan cache is poisoned");
entries.get(&key).map(|entry| {
entry
.downcast_ref::<PlanEntry<T>>()
.expect("module plan cache key has a different value type") as *const PlanEntry<T>
})
};
if entry.is_none() {
let mut entries = self.plans.write().expect("module plan cache is poisoned");
let stored = entries.entry(key).or_insert_with(|| Box::new(PlanEntry::<T>::default()));
entry = Some(
stored
.downcast_ref::<PlanEntry<T>>()
.expect("module plan cache key has a different value type") as *const PlanEntry<T>,
);
}
let entry = unsafe { &*entry.expect("module plan cache entry disappeared") };
if entry.value.get().is_none() {
let _initialize = entry.initialize.lock().expect("module plan cache initializer is poisoned");
if entry.value.get().is_none() {
entry
.value
.set(init()?)
.unwrap_or_else(|_| unreachable!("plan entry initialized while holding its initializer lock"));
}
}
let plans = entry.value.get().expect("module plan cache entry was not initialized");
Ok(use_plans(plans))
}
}
pub unsafe trait ModulePlanCacheProvider {
fn module_plan_cache(&self) -> &ModulePlanCache;
}
#[cfg(test)]
mod tests {
use std::sync::{
Arc,
atomic::{AtomicUsize, Ordering},
};
use super::ModulePlanCache;
struct KeyA;
struct KeyB;
#[test]
fn logical_keys_can_store_the_same_value_type_independently() {
let cache = ModulePlanCache::default();
assert_eq!(cache.with_or_create::<KeyA, _, _>(|| Ok(3usize), |value| *value).unwrap(), 3);
assert_eq!(cache.with_or_create::<KeyB, _, _>(|| Ok(7usize), |value| *value).unwrap(), 7);
}
#[test]
fn concurrent_first_use_initializes_once() {
let cache = Arc::new(ModulePlanCache::default());
let constructions = Arc::new(AtomicUsize::new(0));
std::thread::scope(|scope| {
for _ in 0..8 {
let cache = Arc::clone(&cache);
let constructions = Arc::clone(&constructions);
scope.spawn(move || {
let value = cache
.with_or_create::<KeyA, _, _>(
|| {
constructions.fetch_add(1, Ordering::Relaxed);
Ok(11usize)
},
|value| *value,
)
.unwrap();
assert_eq!(value, 11);
});
}
});
assert_eq!(constructions.load(Ordering::Relaxed), 1);
}
}