Skip to main content

Module cache

Module cache 

Source
Expand description

Race-free per-key once-init cache.

Generalizes the “cached shared resource” pattern that Polydat node functions repeatedly need: a global, expensive-to-construct handle (a vectordata TestDataGroup, a typed reader, an HTTP client, a parsed schema, a compiled regex, …) that should be built at most once per unique key, regardless of how many fibers race to the access point.

§Why a dedicated type

The naïve form — Mutex<HashMap<K, V>> with lock → check missing → unlock → load → relock → insert — exhibits a TOCTOU bug under concurrency: N fibers all see “missing” simultaneously, each runs the (expensive) loader, and the last writer wins. nb-rs hit exactly this in polydat::library::vectors: 20 fibers all opened their own vectordata::Storage per facet, each constructing a fresh reqwest::blocking::Client (≈ load-native-certs + TLS-bootstrap), driving the per-cycle reqwest cost into observable flamegraph dominance.

OnceCache caches an Arc<OnceLock<Result<V, String>>> per key, holds the outer Mutex only long enough to insert the slot, then dispatches the actual loader through OnceLock::get_or_init. Concurrent callers for the same key block on the OnceLock and reuse the cached Result — exactly one loader run per (key, lifetime).

§Failure semantics

Failed loads are sticky: every concurrent caller for the same key sees the same Err rather than triggering a retry storm. This is deliberate — the caller in nb-rs treats a load failure as a workload-config issue (missing dataset, bad URL, permission), and re-attempting per fiber wouldn’t change the diagnostic. If a future caller wants retry semantics, it should clear the slot via a purge method (not yet exposed; add when needed).

§Hot-path cost

After the first successful load, every subsequent get_or_init is: one outer Mutex::lock (for the HashMap::entry lookup), one Arc::clone, one OnceLock::get_or_init (returns immediately because the slot is initialized), and one Result::clone (the V is typically Arc<…> — a refcount bump). No load runs, no I/O happens. This is comparable to a plain Mutex<HashMap> read and not on any hot path nb-rs cares about (cycle-time reads go through pre-resolved handles, not through the cache).

Structs§

OnceCache
Per-key once-init cache. See module docs for the rationale and pattern.