coil-cache 0.1.0

Caching primitives for the Coil framework.
Documentation
use std::collections::BTreeMap;
use std::sync::{Arc, Mutex, OnceLock};

use crate::{
    CacheEntry, CacheInstant, CacheKey, CacheLookup, CacheMetrics, CacheModelError, FillDecision,
    FillLease, InvalidationSet, RequestCoalescingMode,
};

use super::state::CacheBackendState;
use super::{CacheBackendKind, DistributedCacheRuntime};

#[derive(Debug)]
struct EmulatedDistributedCacheRuntime {
    state: Mutex<CacheBackendState>,
}

impl EmulatedDistributedCacheRuntime {
    fn new() -> Self {
        Self {
            state: Mutex::new(CacheBackendState::new()),
        }
    }
}

impl DistributedCacheRuntime for EmulatedDistributedCacheRuntime {
    fn insert(&self, entry: CacheEntry) {
        let mut guard = self.state.lock().expect("cache backend mutex poisoned");
        guard.insert(entry);
    }

    fn lookup(&self, key: &CacheKey, now: CacheInstant) -> CacheLookup {
        let mut guard = self.state.lock().expect("cache backend mutex poisoned");
        guard.lookup(key, now)
    }

    fn invalidate(&self, tags: &InvalidationSet) -> Vec<CacheKey> {
        let mut guard = self.state.lock().expect("cache backend mutex poisoned");
        guard.invalidate(tags)
    }

    fn begin_fill(
        &self,
        key: &CacheKey,
        mode: RequestCoalescingMode,
        holder: String,
    ) -> FillDecision {
        let mut guard = self.state.lock().expect("cache backend mutex poisoned");
        guard.begin_fill(key, mode, holder)
    }

    fn complete_fill(&self, lease: &FillLease) -> Result<(), CacheModelError> {
        let mut guard = self.state.lock().expect("cache backend mutex poisoned");
        guard.complete_fill(lease)
    }

    fn metrics(&self) -> CacheMetrics {
        let guard = self.state.lock().expect("cache backend mutex poisoned");
        guard.metrics()
    }

    fn is_shared_backend(&self) -> bool {
        false
    }
}

pub(crate) fn test_scope_namespace() -> String {
    std::thread::current()
        .name()
        .unwrap_or("unnamed-test")
        .to_string()
}

pub(crate) fn test_only_sqlite_shared_runtime(
    kind: CacheBackendKind,
    namespace: String,
) -> Arc<dyn DistributedCacheRuntime> {
    static REGISTRY: OnceLock<Mutex<BTreeMap<String, Arc<dyn DistributedCacheRuntime>>>> =
        OnceLock::new();

    let key = format!("{}:{kind:?}:{namespace}", test_scope_namespace());
    let registry = REGISTRY.get_or_init(|| Mutex::new(BTreeMap::new()));
    let mut guard = registry.lock().expect("test cache registry mutex poisoned");
    guard
        .entry(key)
        .or_insert_with(|| Arc::new(EmulatedDistributedCacheRuntime::new()))
        .clone()
}