use std::{ any::Any, collections::HashMap, ops::Deref, sync::{LazyLock, Mutex}, thread, time, }; use serde::{de::DeserializeOwned, Serialize}; use sha2::{Digest, Sha256}; #[allow(clippy::type_complexity)] static CACHE: LazyLock>>>> = LazyLock::new(|| Mutex::new(HashMap::new())); pub struct CacheEntry { value: &'static T, } impl Deref for CacheEntry { type Target = T; fn deref(&self) -> &Self::Target { self.value } } /// Get the artifact named `name` from the memory cache. If it doesn't exist, it will be built by /// calling `build_fn` and stored. /// The artifact is indexed by `params: P`. pub(crate) fn get( name: &str, params: &P, build_fn: fn(&P) -> T, ) -> Result, Box> { let params_json = serde_json::to_string(params)?; let params_json_hash = Sha256::digest(¶ms_json); let params_json_hash_str_long = format!("{:x}", params_json_hash); let key = format!("{}/{}", ¶ms_json_hash_str_long[..32], name); log::debug!("getting {} from the mem cache", name); loop { let mut cache = CACHE.lock()?; if let Some(entry) = cache.get(&key) { if let Some(boxed_data) = entry { if let Some(data) = boxed_data.downcast_ref::() { log::debug!("found {} in the mem cache", name); // The data is now in the heap (boxed), and will never go away because we can // only insert into the CACHE if there's no entry, we can't delete nor update. // Since it's not going away, not moving, and the CACHE is 'static, it's safe // to extend the lifetime of data to 'static. let data_static = unsafe { std::mem::transmute::<&T, &'static T>(data) }; return Ok(CacheEntry { value: data_static }); } else { panic!( "type={} doesn't match the type in the cached boxed value with name={}", std::any::type_name::(), name ); } } else { // Another thread is building this entry, let's retry again in 100 ms drop(cache); // release the lock thread::sleep(time::Duration::from_millis(100)); continue; } } // No entry in the cache, let's put a `None` to signal that we're building the // artifact, release the lock, build the artifact and insert it. We do this to avoid // locking for a long time. cache.insert(key.clone(), None); drop(cache); // release the lock log::info!("building {} and storing to the mem cache", name); let start = std::time::Instant::now(); let data = build_fn(params); let elapsed = std::time::Instant::now() - start; log::debug!("built {} in {:?}", name, elapsed); CACHE.lock()?.insert(key, Some(Box::new(data))); // Call `get` again and this time we'll retrieve the data from the cache return get(name, params, build_fn); } }