Skip to main content

lance_core/cache/
mod.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright The Lance Authors
3
4//! Lance cache system.
5//!
6//! ## For cache users
7//!
8//! Use [`LanceCache`] (or [`WeakLanceCache`]) to store and retrieve typed
9//! values. Define a [`CacheKey`] (or [`UnsizedCacheKey`] for trait objects) to
10//! describe what you're caching and its type.
11//!
12//! To make a value type serializable (so persistent backends can store it),
13//! implement [`CacheCodecImpl`] on the type, then override [`CacheKey::codec`]:
14//!
15//! ```ignore
16//! impl CacheCodecImpl for MyData {
17//!     fn serialize(&self, w: &mut dyn Write) -> Result<()> { /* ... */ }
18//!     fn deserialize(data: &Bytes) -> Result<Self> { /* ... */ }
19//! }
20//!
21//! impl CacheKey for MyDataKey {
22//!     type ValueType = MyData;
23//!     fn key(&self) -> Cow<'_, str> { /* ... */ }
24//!     fn type_name() -> &'static str { "MyData" }
25//!     fn codec() -> Option<CacheCodec> {
26//!         Some(CacheCodec::from_impl::<MyData>())
27//!     }
28//! }
29//! ```
30//!
31//! ## For backend implementors
32//!
33//! Implement [`CacheBackend`] to provide a custom storage layer (disk, Redis,
34//! etc.). Backends receive opaque, fixed-size [`InternalCacheKey`] values and
35//! type-erased [`CacheEntry`] values. The typed wrapping is handled by
36//! [`LanceCache`]. See the [`backend`] module for migration details.
37//!
38//! ## Serialization flow
39//!
40//! When a [`CacheKey`] provides a codec via [`CacheKey::codec`]:
41//!
42//! 1. [`LanceCache`] wraps the [`CacheCodec`] and passes it to the backend
43//!    alongside the entry on `insert` and `get` calls.
44//! 2. In-memory backends (like [`MokaCacheBackend`]) ignore the codec.
45//! 3. Persistent backends use `codec.serialize(entry, writer)` on insert and
46//!    `codec.deserialize(reader)` on get to persist entries across restarts.
47
48pub mod backend;
49mod backend_uri;
50pub mod codec;
51mod entry_io;
52mod key;
53mod moka;
54mod priority;
55mod quick;
56pub use priority::PriorityEntries;
57mod registry;
58
59pub use backend::{CacheBackend, CacheEntry, CacheTier};
60pub use backend_uri::{build_from_uri, parse_backend_uri};
61pub use codec::{
62    CacheCodec, CacheCodecImpl, CacheDecode, CacheMissReason, CacheRangeReader, MAGIC,
63    has_cache_envelope,
64};
65pub use entry_io::{CacheEntryReader, CacheEntryWriter};
66pub use key::{CACHE_KEY_FORMAT, CacheKeySchema, CacheNamespace, InternalCacheKey, KeyBuilder};
67pub use moka::MokaCacheBackend;
68pub use quick::{QuickCacheBackend, QuickCacheShardPolicy, recommended_cache_shards};
69pub use registry::{BackendBuildFn, BackendConfig, build_from_config, register_backend};
70
71use std::any::TypeId;
72use std::borrow::Cow;
73use std::collections::HashMap;
74use std::sync::{
75    Arc, RwLock, Weak,
76    atomic::{AtomicU64, Ordering},
77};
78
79use futures::Future;
80
81use crate::{Error, Result};
82
83pub use crate::deepsize::{Context, DeepSizeOf};
84
85// ---------------------------------------------------------------------------
86// CacheKey / UnsizedCacheKey — typed key traits for cache users
87// ---------------------------------------------------------------------------
88
89/// Typed cache key for sized value types.
90///
91/// Existing implementations can continue returning a logical string from
92/// [`key`](Self::key). Performance-sensitive implementations should also
93/// provide a stable schema and stream typed fields through
94/// [`write_key`](Self::write_key), avoiding construction of that string.
95///
96/// # Example
97///
98/// ```ignore
99/// struct MyKey { id: u64 }
100///
101/// impl CacheKey for MyKey {
102///     type ValueType = MyData;
103///     fn key(&self) -> Cow<'_, str> { self.id.to_string().into() }
104///     fn type_name() -> &'static str { "MyData" }
105/// }
106/// ```
107pub trait CacheKey {
108    type ValueType: 'static;
109
110    fn key(&self) -> Cow<'_, str>;
111
112    /// Short, stable string identifying this value type.
113    ///
114    /// Two `CacheKey` impls that store different `ValueType`s **must** return
115    /// different type names.
116    ///
117    /// Use a short literal (e.g. `"Vec<IndexMetadata>"`), not
118    /// `std::any::type_name` — the latter is not guaranteed stable across
119    /// compiler versions or build configurations.
120    fn type_name() -> &'static str;
121
122    /// Stable identity included in the physical key.
123    ///
124    /// The compatibility default preserves existing implementations by using
125    /// their author-assigned [`type_name`](Self::type_name).
126    fn stable_type_id() -> &'static str {
127        Self::type_name()
128    }
129
130    /// Versioned schema for the logical key fields.
131    fn schema() -> CacheKeySchema {
132        CacheKeySchema::LEGACY_TEXT
133    }
134
135    /// Stream the logical key fields into the canonical key builder.
136    ///
137    /// The compatibility default hashes the existing string key. In-tree hot
138    /// paths override this with typed, allocation-free field encoding.
139    fn write_key(&self, builder: &mut KeyBuilder) {
140        builder.write_str(self.key().as_ref());
141    }
142
143    /// Per-key cache policy, defaulting to the value type's codec.
144    fn codec_for_key(&self) -> Option<CacheCodec> {
145        Self::codec()
146    }
147
148    /// Optional codec for serializing/deserializing this key's value type.
149    ///
150    /// Returns `None` by default. Cache backends that support persistence
151    /// (e.g. disk-backed caches) use this to serialize entries on insert and
152    /// deserialize on get. Types without a codec will only be stored in-memory.
153    ///
154    /// [`CacheCodec`] is `Copy` (two plain function pointers), so returning it
155    /// by value is cheap — no allocation needed.
156    fn codec() -> Option<CacheCodec> {
157        None
158    }
159}
160
161/// Like [`CacheKey`] but for unsized value types (e.g. `dyn Trait`).
162///
163/// The cache wraps values in an extra `Arc` layer internally; callers pass
164/// and receive `Arc<T>` where `T: ?Sized`.
165///
166/// Unsized cache entries are always in-memory only (no serialization codec).
167/// For serializable entries, use a sized [`CacheKey`] instead.
168pub trait UnsizedCacheKey {
169    type ValueType: 'static + ?Sized;
170
171    fn key(&self) -> Cow<'_, str>;
172
173    /// Short, stable string identifying this value type.
174    /// See [`CacheKey::type_name`] for requirements.
175    fn type_name() -> &'static str;
176
177    /// Stable identity included in the physical key.
178    fn stable_type_id() -> &'static str {
179        Self::type_name()
180    }
181
182    /// Versioned schema for the logical key fields.
183    fn schema() -> CacheKeySchema {
184        CacheKeySchema::LEGACY_TEXT
185    }
186
187    /// Stream the logical key fields into the canonical key builder.
188    fn write_key(&self, builder: &mut KeyBuilder) {
189        builder.write_str(self.key().as_ref());
190    }
191}
192
193// ---------------------------------------------------------------------------
194// Internal helpers
195// ---------------------------------------------------------------------------
196
197/// Size of a cached `Arc<T>`, accounting for the Arc overhead (two atomic counters).
198fn cache_entry_size<T: DeepSizeOf + ?Sized>(value: &T) -> usize {
199    value.deep_size_of() + std::mem::size_of::<std::sync::atomic::AtomicUsize>() * 2
200}
201
202type CacheEntrySizeAccessor = fn(&CacheEntry, &mut Context) -> Option<usize>;
203
204fn cache_entry_size_with_context<T>(entry: &CacheEntry, context: &mut Context) -> Option<usize>
205where
206    T: DeepSizeOf + Send + Sync + 'static,
207{
208    let value = entry.downcast_ref::<T>()?;
209    let entry_ptr = Arc::as_ptr(entry) as *const () as usize;
210    if !context.mark_seen(entry_ptr) {
211        return Some(0);
212    }
213    Some(
214        std::mem::size_of_val(value)
215            + value.deep_size_of_children(context)
216            + std::mem::size_of::<std::sync::atomic::AtomicUsize>() * 2,
217    )
218}
219
220#[derive(Debug)]
221struct CacheState {
222    backend: Arc<dyn CacheBackend>,
223    hits: AtomicU64,
224    misses: AtomicU64,
225    entry_size_accessors: RwLock<HashMap<TypeId, CacheEntrySizeAccessor>>,
226}
227
228impl CacheState {
229    fn new(backend: Arc<dyn CacheBackend>) -> Self {
230        Self {
231            backend,
232            hits: AtomicU64::new(0),
233            misses: AtomicU64::new(0),
234            entry_size_accessors: RwLock::new(HashMap::new()),
235        }
236    }
237
238    fn entry_size<T>(&self, value: &T) -> usize
239    where
240        T: DeepSizeOf + Send + Sync + 'static,
241    {
242        let type_id = TypeId::of::<T>();
243        let is_registered = self
244            .entry_size_accessors
245            .read()
246            .unwrap_or_else(|poisoned| poisoned.into_inner())
247            .contains_key(&type_id);
248        if !is_registered {
249            self.entry_size_accessors
250                .write()
251                .unwrap_or_else(|poisoned| poisoned.into_inner())
252                .entry(type_id)
253                .or_insert(cache_entry_size_with_context::<T>);
254        }
255        cache_entry_size(value)
256    }
257}
258
259// ---------------------------------------------------------------------------
260// LanceCache — typed wrapper around dyn CacheBackend
261// ---------------------------------------------------------------------------
262
263/// Typed cache wrapper that handles key construction and type safety.
264///
265/// Internally delegates to a [`CacheBackend`]. The default backend is
266/// [`MokaCacheBackend`]; pass a custom backend via [`LanceCache::with_backend`].
267#[derive(Clone)]
268pub struct LanceCache {
269    state: Arc<CacheState>,
270    namespace: key::CacheNamespace,
271}
272
273impl std::fmt::Debug for LanceCache {
274    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
275        f.debug_struct("LanceCache")
276            .field("backend", &self.state.backend)
277            .finish_non_exhaustive()
278    }
279}
280
281impl DeepSizeOf for LanceCache {
282    fn deep_size_of_children(&self, context: &mut Context) -> usize {
283        let state_ptr = Arc::as_ptr(&self.state) as usize;
284        if !context.mark_seen(state_ptr) {
285            return 0;
286        }
287
288        let accessors = self
289            .state
290            .entry_size_accessors
291            .read()
292            .unwrap_or_else(|poisoned| poisoned.into_inner())
293            .clone();
294        self.state
295            .backend
296            .deep_size_of_entries(context, &|entry, context| {
297                accessors
298                    .get(&entry.as_ref().type_id())
299                    .and_then(|size_of_entry| size_of_entry(entry, context))
300            })
301            .unwrap_or_else(|| self.state.backend.approx_size_bytes())
302    }
303}
304
305impl LanceCache {
306    pub fn with_capacity(capacity: usize) -> Self {
307        Self::with_backend(Arc::new(MokaCacheBackend::with_capacity(capacity)))
308    }
309
310    /// Create a cache backed by a custom [`CacheBackend`].
311    pub fn with_backend(backend: Arc<dyn CacheBackend>) -> Self {
312        Self {
313            state: Arc::new(CacheState::new(backend)),
314            namespace: key::CacheNamespace::root(),
315        }
316    }
317
318    pub fn no_cache() -> Self {
319        Self::with_backend(Arc::new(MokaCacheBackend::no_cache()))
320    }
321
322    /// Derive a child namespace for all keys in the returned cache handle.
323    ///
324    /// Each call adds one framed hierarchy segment. Consequently,
325    /// `cache.with_key_prefix("a").with_key_prefix("b")` is deliberately
326    /// distinct from `cache.with_key_prefix("a/b")`.
327    pub fn with_key_prefix(&self, prefix: &str) -> Self {
328        Self {
329            state: self.state.clone(),
330            namespace: self.namespace.child(prefix),
331        }
332    }
333
334    pub async fn size(&self) -> usize {
335        self.state.backend.num_entries().await
336    }
337
338    pub fn approx_size(&self) -> usize {
339        self.state.backend.approx_num_entries()
340    }
341
342    pub async fn size_bytes(&self) -> usize {
343        self.state.backend.size_bytes().await
344    }
345
346    /// Weighted capacity in bytes, if the backend reports one.
347    pub fn capacity_bytes(&self) -> Option<usize> {
348        self.state.backend.capacity_bytes()
349    }
350
351    // -- Stats / clear --------------------------------------------------------
352
353    pub async fn stats(&self) -> CacheStats {
354        CacheStats {
355            hits: self.state.hits.load(Ordering::Relaxed),
356            misses: self.state.misses.load(Ordering::Relaxed),
357            num_entries: self.state.backend.num_entries().await,
358            size_bytes: self.state.backend.size_bytes().await,
359        }
360    }
361
362    pub async fn clear(&self) {
363        self.state.backend.clear().await;
364        self.state.hits.store(0, Ordering::Relaxed);
365        self.state.misses.store(0, Ordering::Relaxed);
366    }
367
368    // -- CacheKey-based methods -----------------------------------------------
369
370    pub async fn insert_with_key<K>(&self, cache_key: &K, metadata: Arc<K::ValueType>)
371    where
372        K: CacheKey,
373        K::ValueType: DeepSizeOf + Send + Sync + 'static,
374    {
375        let size = self.state.entry_size(metadata.as_ref());
376        let key = self.sized_key(cache_key);
377        self.state
378            .backend
379            .insert(&key, metadata, size, cache_key.codec_for_key())
380            .await;
381    }
382
383    /// Read a resident entry without loading its disk copy.
384    pub async fn get_resident_with_key<K>(&self, cache_key: &K) -> Option<Arc<K::ValueType>>
385    where
386        K: CacheKey,
387        K::ValueType: DeepSizeOf + Send + Sync + 'static,
388    {
389        let key = self.sized_key(cache_key);
390        self.state
391            .backend
392            .get_resident(&key)
393            .await?
394            .downcast::<K::ValueType>()
395            .ok()
396    }
397
398    /// Report whether an entry is resident without counting as an access.
399    pub async fn peek_resident_with_key<K: CacheKey>(&self, cache_key: &K) -> bool {
400        let key = self.sized_key(cache_key);
401        self.state.backend.peek_resident(&key).await
402    }
403
404    /// Predict which tier would serve a read of the entry, without reading it
405    /// or counting as an access; see [`CacheBackend::peek_tier`].
406    pub async fn peek_tier_with_key<K: CacheKey>(&self, cache_key: &K) -> CacheTier {
407        let key = self.sized_key(cache_key);
408        self.state.backend.peek_tier(&key).await
409    }
410
411    /// Whether lower layered planes gate RAM admission on a resident sign plane.
412    pub fn plane_admission_gated(&self) -> bool {
413        self.state.backend.plane_admission_gated()
414    }
415
416    /// Read memory or persistent storage without promoting a persistent hit.
417    pub async fn get_without_promotion_with_key<K>(
418        &self,
419        cache_key: &K,
420    ) -> Option<Arc<K::ValueType>>
421    where
422        K: CacheKey,
423        K::ValueType: DeepSizeOf + Send + Sync + 'static,
424    {
425        let key = self.sized_key(cache_key);
426        self.state
427            .backend
428            .get_without_promotion(&key, cache_key.codec_for_key())
429            .await?
430            .downcast::<K::ValueType>()
431            .ok()
432    }
433
434    /// Read selected rows from a persistent entry without memory admission.
435    pub async fn get_rows_with_key<K>(
436        &self,
437        cache_key: &K,
438        rows: &[u32],
439    ) -> Option<Arc<K::ValueType>>
440    where
441        K: CacheKey,
442        K::ValueType: DeepSizeOf + Send + Sync + 'static,
443    {
444        self.state
445            .backend
446            .get_rows(&self.sized_key(cache_key), rows, cache_key.codec_for_key())
447            .await?
448            .downcast::<K::ValueType>()
449            .ok()
450    }
451
452    pub async fn get_with_key<K>(&self, cache_key: &K) -> Option<Arc<K::ValueType>>
453    where
454        K: CacheKey,
455        K::ValueType: DeepSizeOf + Send + Sync + 'static,
456    {
457        let key = self.sized_key(cache_key);
458        let Some(entry) = self
459            .state
460            .backend
461            .get(&key, cache_key.codec_for_key())
462            .await
463        else {
464            self.state.misses.fetch_add(1, Ordering::Relaxed);
465            return None;
466        };
467        match entry.downcast::<K::ValueType>() {
468            Ok(value) => {
469                self.state.hits.fetch_add(1, Ordering::Relaxed);
470                Some(value)
471            }
472            Err(_) => {
473                // Type mismatch: the backend returned a different concrete
474                // type than expected (e.g. a disk cache may store
475                // intermediate state). Treat as a miss.
476                log::warn!(
477                    "cache backend returned a value with the wrong concrete type for key type {:?}",
478                    K::stable_type_id()
479                );
480                self.state.misses.fetch_add(1, Ordering::Relaxed);
481                None
482            }
483        }
484    }
485
486    pub async fn get_or_insert_with_key<K, F, Fut>(
487        &self,
488        cache_key: K,
489        loader: F,
490    ) -> Result<Arc<K::ValueType>>
491    where
492        K: CacheKey,
493        K::ValueType: DeepSizeOf + Send + Sync + 'static,
494        F: FnOnce() -> Fut + Send,
495        Fut: Future<Output = Result<K::ValueType>> + Send,
496    {
497        self.get_or_insert_with_key_hit(cache_key, loader)
498            .await
499            .map(|(value, _)| value)
500    }
501
502    /// Same as [`get_or_insert_with_key`](Self::get_or_insert_with_key), but
503    /// also returns a boolean indicating whether the loader was skipped for
504    /// this call.
505    ///
506    /// - `true` means this call did **not** execute the loader. That covers
507    ///   both a true cache hit on an already-populated entry and a coalesced
508    ///   concurrent load where an in-flight loader started by a different
509    ///   caller produced the value.
510    /// - `false` means the loader ran on this call (a real cache miss).
511    ///
512    /// Callers that want strict "served from cache" semantics should treat
513    /// coalesced loads as misses; the current backend does not distinguish the
514    /// two cases. Prefer this over rolling a caller-side `Arc<AtomicBool>`
515    /// when the caller needs per-query hit/miss counters — the backend already
516    /// tracks this bit internally and this method just exposes it.
517    pub async fn get_or_insert_with_key_hit<K, F, Fut>(
518        &self,
519        cache_key: K,
520        loader: F,
521    ) -> Result<(Arc<K::ValueType>, bool)>
522    where
523        K: CacheKey,
524        K::ValueType: DeepSizeOf + Send + Sync + 'static,
525        F: FnOnce() -> Fut + Send,
526        Fut: Future<Output = Result<K::ValueType>> + Send,
527    {
528        let key = self.sized_key(&cache_key);
529        let state = self.state.clone();
530        let typed_loader = Box::pin(async move {
531            let value = Arc::new(loader().await?);
532            let size = state.entry_size(value.as_ref());
533            Ok((value as CacheEntry, size))
534        });
535
536        let (entry, was_cached) = self
537            .state
538            .backend
539            .get_or_insert(&key, typed_loader, cache_key.codec_for_key())
540            .await?;
541        let entry = entry.downcast::<K::ValueType>().map_err(|_| {
542            self.state.misses.fetch_add(1, Ordering::Relaxed);
543            Error::io(format!(
544                "cache backend returned a value with the wrong concrete type for key type {:?}",
545                K::stable_type_id()
546            ))
547        })?;
548        if was_cached {
549            self.state.hits.fetch_add(1, Ordering::Relaxed);
550        } else {
551            self.state.misses.fetch_add(1, Ordering::Relaxed);
552        }
553        Ok((entry, was_cached))
554    }
555
556    pub async fn insert_unsized_with_key<K>(&self, cache_key: &K, metadata: Arc<K::ValueType>)
557    where
558        K: UnsizedCacheKey,
559        K::ValueType: DeepSizeOf + Send + Sync + 'static,
560    {
561        let metadata = Arc::new(metadata);
562        let size = self.state.entry_size(metadata.as_ref());
563        let key = self.unsized_key(cache_key);
564        self.state.backend.insert(&key, metadata, size, None).await;
565    }
566
567    pub async fn get_or_insert_unsized_with_key<K, F, Fut>(
568        &self,
569        cache_key: K,
570        loader: F,
571    ) -> Result<Arc<K::ValueType>>
572    where
573        K: UnsizedCacheKey,
574        K::ValueType: DeepSizeOf + Send + Sync + 'static,
575        F: FnOnce() -> Fut + Send,
576        Fut: Future<Output = Result<Arc<K::ValueType>>> + Send,
577    {
578        let key = self.unsized_key(&cache_key);
579        let state = self.state.clone();
580        let typed_loader = Box::pin(async move {
581            let value = loader().await?;
582            let size = state.entry_size(&value);
583            Ok((Arc::new(value) as CacheEntry, size))
584        });
585
586        let (entry, was_cached) = self
587            .state
588            .backend
589            .get_or_insert(&key, typed_loader, None)
590            .await?;
591        let entry = entry.downcast::<Arc<K::ValueType>>().map_err(|_| {
592            self.state.misses.fetch_add(1, Ordering::Relaxed);
593            Error::io(format!(
594                "cache backend returned a value with the wrong concrete type for unsized key type {:?}",
595                K::stable_type_id()
596            ))
597        })?;
598        if was_cached {
599            self.state.hits.fetch_add(1, Ordering::Relaxed);
600        } else {
601            self.state.misses.fetch_add(1, Ordering::Relaxed);
602        }
603        Ok(entry.as_ref().clone())
604    }
605
606    pub async fn get_unsized_with_key<K>(&self, cache_key: &K) -> Option<Arc<K::ValueType>>
607    where
608        K: UnsizedCacheKey,
609        K::ValueType: DeepSizeOf + Send + Sync + 'static,
610    {
611        let key = self.unsized_key(cache_key);
612        let Some(entry) = self.state.backend.get(&key, None).await else {
613            self.state.misses.fetch_add(1, Ordering::Relaxed);
614            return None;
615        };
616        match entry.downcast::<Arc<K::ValueType>>() {
617            Ok(value) => {
618                self.state.hits.fetch_add(1, Ordering::Relaxed);
619                Some(value.as_ref().clone())
620            }
621            Err(_) => {
622                // Type mismatch: the backend returned a different concrete
623                // type than expected (e.g. a disk cache may store
624                // intermediate state). Treat as a miss.
625                log::warn!(
626                    "cache backend returned a value with the wrong concrete type for unsized key type {:?}",
627                    K::stable_type_id()
628                );
629                self.state.misses.fetch_add(1, Ordering::Relaxed);
630                None
631            }
632        }
633    }
634
635    fn sized_key<K: CacheKey>(&self, cache_key: &K) -> InternalCacheKey {
636        let mut builder = KeyBuilder::new(self.namespace, K::stable_type_id(), K::schema());
637        cache_key.write_key(&mut builder);
638        builder.finish()
639    }
640
641    fn unsized_key<K: UnsizedCacheKey>(&self, cache_key: &K) -> InternalCacheKey {
642        let mut builder = KeyBuilder::new(self.namespace, K::stable_type_id(), K::schema());
643        cache_key.write_key(&mut builder);
644        builder.finish()
645    }
646}
647
648// ---------------------------------------------------------------------------
649// WeakLanceCache
650// ---------------------------------------------------------------------------
651
652/// A weak reference to a LanceCache, used by indices to avoid circular references.
653/// When the original cache is dropped, operations on this will gracefully no-op.
654#[derive(Clone, Debug)]
655pub struct WeakLanceCache {
656    state: Weak<CacheState>,
657    namespace: key::CacheNamespace,
658}
659
660impl WeakLanceCache {
661    pub fn from(cache: &LanceCache) -> Self {
662        Self {
663            state: Arc::downgrade(&cache.state),
664            namespace: cache.namespace,
665        }
666    }
667
668    pub fn with_key_prefix(&self, prefix: &str) -> Self {
669        Self {
670            state: self.state.clone(),
671            namespace: self.namespace.child(prefix),
672        }
673    }
674
675    /// Read RAM only, avoiding whole-entry promotion for candidate range reads.
676    pub async fn get_resident_with_key<K>(&self, cache_key: &K) -> Option<Arc<K::ValueType>>
677    where
678        K: CacheKey,
679        K::ValueType: DeepSizeOf + Send + Sync + 'static,
680    {
681        self.upgrade()?.get_resident_with_key(cache_key).await
682    }
683
684    /// Report residency without counting as an access; a dropped cache holds nothing.
685    pub async fn peek_resident_with_key<K: CacheKey>(&self, cache_key: &K) -> bool {
686        match self.upgrade() {
687            Some(cache) => cache.peek_resident_with_key(cache_key).await,
688            None => false,
689        }
690    }
691
692    /// Predict the serving tier without counting as an access; a dropped
693    /// cache holds nothing.
694    pub async fn peek_tier_with_key<K: CacheKey>(&self, cache_key: &K) -> CacheTier {
695        match self.upgrade() {
696            Some(cache) => cache.peek_tier_with_key(cache_key).await,
697            None => CacheTier::Absent,
698        }
699    }
700
701    /// Whether lower layered planes gate RAM admission; a dropped cache keeps the default.
702    pub fn plane_admission_gated(&self) -> bool {
703        self.upgrade()
704            .is_none_or(|cache| cache.plane_admission_gated())
705    }
706
707    /// Read a cached candidate plane without whole-entry RAM admission.
708    pub async fn get_without_promotion_with_key<K>(
709        &self,
710        cache_key: &K,
711    ) -> Option<Arc<K::ValueType>>
712    where
713        K: CacheKey,
714        K::ValueType: DeepSizeOf + Send + Sync + 'static,
715    {
716        self.upgrade()?
717            .get_without_promotion_with_key(cache_key)
718            .await
719    }
720
721    /// Read selected cached rows without promoting a whole plane.
722    pub async fn get_rows_with_key<K>(
723        &self,
724        cache_key: &K,
725        rows: &[u32],
726    ) -> Option<Arc<K::ValueType>>
727    where
728        K: CacheKey,
729        K::ValueType: DeepSizeOf + Send + Sync + 'static,
730    {
731        self.upgrade()?.get_rows_with_key(cache_key, rows).await
732    }
733
734    /// Weighted capacity in bytes, if the cache is alive and its backend
735    /// reports one.
736    pub fn capacity_bytes(&self) -> Option<usize> {
737        self.upgrade()?.capacity_bytes()
738    }
739
740    pub async fn get_with_key<K>(&self, cache_key: &K) -> Option<Arc<K::ValueType>>
741    where
742        K: CacheKey,
743        K::ValueType: DeepSizeOf + Send + Sync + 'static,
744    {
745        self.upgrade()?.get_with_key(cache_key).await
746    }
747
748    pub async fn insert_with_key<K>(&self, cache_key: &K, value: Arc<K::ValueType>) -> bool
749    where
750        K: CacheKey,
751        K::ValueType: DeepSizeOf + Send + Sync + 'static,
752    {
753        let Some(cache) = self.upgrade() else {
754            log::warn!("WeakLanceCache: cache no longer available, unable to insert item");
755            return false;
756        };
757        cache.insert_with_key(cache_key, value).await;
758        true
759    }
760
761    /// Get or insert an item, computing it if necessary.
762    ///
763    /// Deduplication of concurrent loads is handled by the backend.
764    pub async fn get_or_insert_with_key<K, F, Fut>(
765        &self,
766        cache_key: K,
767        loader: F,
768    ) -> Result<Arc<K::ValueType>>
769    where
770        K: CacheKey,
771        K::ValueType: DeepSizeOf + Send + Sync + 'static,
772        F: FnOnce() -> Fut + Send,
773        Fut: Future<Output = Result<K::ValueType>> + Send,
774    {
775        self.get_or_insert_with_key_hit(cache_key, loader)
776            .await
777            .map(|(value, _)| value)
778    }
779
780    /// Same as [`get_or_insert_with_key`](Self::get_or_insert_with_key), but
781    /// also returns a boolean indicating whether the loader was skipped for
782    /// this call. See [`LanceCache::get_or_insert_with_key_hit`] for the
783    /// coalesced-load caveat.
784    pub async fn get_or_insert_with_key_hit<K, F, Fut>(
785        &self,
786        cache_key: K,
787        loader: F,
788    ) -> Result<(Arc<K::ValueType>, bool)>
789    where
790        K: CacheKey,
791        K::ValueType: DeepSizeOf + Send + Sync + 'static,
792        F: FnOnce() -> Fut + Send,
793        Fut: Future<Output = Result<K::ValueType>> + Send,
794    {
795        let Some(cache) = self.upgrade() else {
796            log::warn!("WeakLanceCache: cache no longer available, computing without caching");
797            return loader().await.map(|value| (Arc::new(value), false));
798        };
799        cache.get_or_insert_with_key_hit(cache_key, loader).await
800    }
801
802    pub async fn get_unsized_with_key<K>(&self, cache_key: &K) -> Option<Arc<K::ValueType>>
803    where
804        K: UnsizedCacheKey,
805        K::ValueType: DeepSizeOf + Send + Sync + 'static,
806    {
807        self.upgrade()?.get_unsized_with_key(cache_key).await
808    }
809
810    pub async fn insert_unsized_with_key<K>(&self, cache_key: &K, value: Arc<K::ValueType>)
811    where
812        K: UnsizedCacheKey,
813        K::ValueType: DeepSizeOf + Send + Sync + 'static,
814    {
815        let Some(cache) = self.upgrade() else {
816            log::warn!("WeakLanceCache: cache no longer available, unable to insert unsized item");
817            return;
818        };
819        cache.insert_unsized_with_key(cache_key, value).await;
820    }
821
822    fn upgrade(&self) -> Option<LanceCache> {
823        Some(LanceCache {
824            state: self.state.upgrade()?,
825            namespace: self.namespace,
826        })
827    }
828}
829
830// ---------------------------------------------------------------------------
831// CacheStats
832// ---------------------------------------------------------------------------
833
834#[derive(Debug, Clone)]
835pub struct CacheStats {
836    /// Number of times `get`, `get_unsized`, or `get_or_insert` found an item in the cache.
837    pub hits: u64,
838    /// Number of times `get`, `get_unsized`, or `get_or_insert` did not find an item in the cache.
839    pub misses: u64,
840    /// Number of entries currently in the cache.
841    pub num_entries: usize,
842    /// Total size in bytes of all entries in the cache.
843    pub size_bytes: usize,
844}
845
846impl CacheStats {
847    pub fn hit_ratio(&self) -> f32 {
848        if self.hits + self.misses == 0 {
849            0.0
850        } else {
851            self.hits as f32 / (self.hits + self.misses) as f32
852        }
853    }
854
855    pub fn miss_ratio(&self) -> f32 {
856        if self.hits + self.misses == 0 {
857            0.0
858        } else {
859            self.misses as f32 / (self.hits + self.misses) as f32
860        }
861    }
862}
863
864#[cfg(test)]
865mod tests {
866    use std::collections::HashMap;
867    use std::pin::Pin;
868    use std::sync::{
869        atomic::{AtomicUsize, Ordering},
870        mpsc,
871    };
872    use std::task::Poll;
873    use std::thread;
874    use std::time::Duration;
875
876    use super::*;
877
878    async fn report_first_pending<F>(
879        future: F,
880        parked: tokio::sync::oneshot::Sender<()>,
881    ) -> F::Output
882    where
883        F: Future,
884    {
885        tokio::pin!(future);
886        let mut parked = Some(parked);
887        futures::future::poll_fn(|cx| match future.as_mut().poll(cx) {
888            Poll::Pending => {
889                if let Some(parked) = parked.take() {
890                    let _ = parked.send(());
891                }
892                Poll::Pending
893            }
894            Poll::Ready(output) => Poll::Ready(output),
895        })
896        .await
897    }
898
899    #[derive(Clone)]
900    struct VersionedTestKey<const SCHEMA_VERSION: u32> {
901        id: u64,
902    }
903
904    type TestKey = VersionedTestKey<1>;
905    type TestKeyV2 = VersionedTestKey<2>;
906
907    impl<const SCHEMA_VERSION: u32> VersionedTestKey<SCHEMA_VERSION> {
908        fn new(id: u64) -> Self {
909            Self { id }
910        }
911    }
912
913    impl<const SCHEMA_VERSION: u32> CacheKey for VersionedTestKey<SCHEMA_VERSION> {
914        type ValueType = Vec<u32>;
915
916        fn key(&self) -> Cow<'_, str> {
917            self.id.to_string().into()
918        }
919
920        fn type_name() -> &'static str {
921            "test.VecU32"
922        }
923
924        fn schema() -> CacheKeySchema {
925            CacheKeySchema::new("test.vec-u32-key", SCHEMA_VERSION)
926        }
927
928        fn write_key(&self, builder: &mut KeyBuilder) {
929            builder.write_u64(self.id);
930        }
931    }
932
933    struct SharedTestValue {
934        data: Arc<Vec<u8>>,
935    }
936
937    impl DeepSizeOf for SharedTestValue {
938        fn deep_size_of_children(&self, context: &mut Context) -> usize {
939            self.data.deep_size_of_children(context)
940        }
941    }
942
943    struct SharedTestKey(u64);
944
945    impl CacheKey for SharedTestKey {
946        type ValueType = SharedTestValue;
947
948        fn key(&self) -> Cow<'_, str> {
949            self.0.to_string().into()
950        }
951
952        fn type_name() -> &'static str {
953            "test.SharedValue"
954        }
955
956        fn schema() -> CacheKeySchema {
957            CacheKeySchema::new("test.shared-value-key", 1)
958        }
959
960        fn write_key(&self, builder: &mut KeyBuilder) {
961            builder.write_u64(self.0);
962        }
963    }
964
965    struct ReentrantValue(LanceCache);
966
967    impl DeepSizeOf for ReentrantValue {
968        fn deep_size_of_children(&self, context: &mut Context) -> usize {
969            self.0.deep_size_of_children(context)
970        }
971    }
972
973    struct ReentrantKey;
974
975    impl CacheKey for ReentrantKey {
976        type ValueType = ReentrantValue;
977
978        fn key(&self) -> Cow<'_, str> {
979            Cow::Borrowed("reentrant")
980        }
981
982        fn type_name() -> &'static str {
983            "test.ReentrantValue"
984        }
985    }
986
987    #[derive(Clone, Copy, Debug)]
988    enum TestBackendKind {
989        Moka,
990        Quick,
991    }
992
993    impl TestBackendKind {
994        fn cache(self, capacity: usize) -> LanceCache {
995            match self {
996                Self::Moka => LanceCache::with_capacity(capacity),
997                Self::Quick => {
998                    LanceCache::with_backend(Arc::new(QuickCacheBackend::with_capacity(capacity)))
999                }
1000            }
1001        }
1002    }
1003
1004    struct LegacyBridgeKey(&'static str);
1005
1006    impl CacheKey for LegacyBridgeKey {
1007        type ValueType = Vec<u32>;
1008
1009        fn key(&self) -> Cow<'_, str> {
1010            Cow::Borrowed(self.0)
1011        }
1012
1013        fn type_name() -> &'static str {
1014            "test.LegacyBridge"
1015        }
1016    }
1017
1018    struct ExplicitBridgeKey(&'static str);
1019
1020    impl CacheKey for ExplicitBridgeKey {
1021        type ValueType = Vec<u32>;
1022
1023        fn key(&self) -> Cow<'_, str> {
1024            Cow::Borrowed(self.0)
1025        }
1026
1027        fn type_name() -> &'static str {
1028            "test.LegacyBridge"
1029        }
1030
1031        fn write_key(&self, builder: &mut KeyBuilder) {
1032            builder.write_str(self.0);
1033        }
1034    }
1035
1036    trait TestDynValue: DeepSizeOf + Send + Sync {
1037        fn values(&self) -> &[u32];
1038    }
1039
1040    impl TestDynValue for Vec<u32> {
1041        fn values(&self) -> &[u32] {
1042            self
1043        }
1044    }
1045
1046    struct LegacyUnsizedBridgeKey(&'static str);
1047
1048    impl UnsizedCacheKey for LegacyUnsizedBridgeKey {
1049        type ValueType = dyn TestDynValue;
1050
1051        fn key(&self) -> Cow<'_, str> {
1052            Cow::Borrowed(self.0)
1053        }
1054
1055        fn type_name() -> &'static str {
1056            "test.LegacyUnsizedBridge"
1057        }
1058    }
1059
1060    struct ExplicitUnsizedBridgeKey(&'static str);
1061
1062    impl UnsizedCacheKey for ExplicitUnsizedBridgeKey {
1063        type ValueType = dyn TestDynValue;
1064
1065        fn key(&self) -> Cow<'_, str> {
1066            Cow::Borrowed(self.0)
1067        }
1068
1069        fn type_name() -> &'static str {
1070            "test.LegacyUnsizedBridge"
1071        }
1072
1073        fn write_key(&self, builder: &mut KeyBuilder) {
1074            builder.write_str(self.0);
1075        }
1076    }
1077
1078    #[derive(Debug, Default)]
1079    struct HashMapBackend {
1080        entries: tokio::sync::Mutex<HashMap<InternalCacheKey, (CacheEntry, usize)>>,
1081    }
1082
1083    #[async_trait::async_trait]
1084    impl CacheBackend for HashMapBackend {
1085        async fn get(
1086            &self,
1087            key: &InternalCacheKey,
1088            _codec: Option<CacheCodec>,
1089        ) -> Option<CacheEntry> {
1090            self.entries
1091                .lock()
1092                .await
1093                .get(key)
1094                .map(|(entry, _)| entry.clone())
1095        }
1096
1097        async fn insert(
1098            &self,
1099            key: &InternalCacheKey,
1100            entry: CacheEntry,
1101            size_bytes: usize,
1102            _codec: Option<CacheCodec>,
1103        ) {
1104            self.entries.lock().await.insert(*key, (entry, size_bytes));
1105        }
1106
1107        async fn get_or_insert<'a>(
1108            &self,
1109            key: &InternalCacheKey,
1110            loader: Pin<Box<dyn Future<Output = Result<(CacheEntry, usize)>> + Send + 'a>>,
1111            codec: Option<CacheCodec>,
1112        ) -> Result<(CacheEntry, bool)> {
1113            if let Some(entry) = self.get(key, codec).await {
1114                return Ok((entry, true));
1115            }
1116            let (entry, size_bytes) = loader.await?;
1117            self.insert(key, entry.clone(), size_bytes, codec).await;
1118            Ok((entry, false))
1119        }
1120
1121        async fn clear(&self) {
1122            self.entries.lock().await.clear();
1123        }
1124
1125        async fn num_entries(&self) -> usize {
1126            self.entries.lock().await.len()
1127        }
1128
1129        async fn size_bytes(&self) -> usize {
1130            self.entries
1131                .lock()
1132                .await
1133                .values()
1134                .map(|(_, size_bytes)| size_bytes)
1135                .sum()
1136        }
1137    }
1138
1139    #[derive(Debug)]
1140    struct WrongTypeBackend;
1141
1142    #[async_trait::async_trait]
1143    impl CacheBackend for WrongTypeBackend {
1144        async fn get(
1145            &self,
1146            _key: &InternalCacheKey,
1147            _codec: Option<CacheCodec>,
1148        ) -> Option<CacheEntry> {
1149            Some(Arc::new(String::from("wrong type")))
1150        }
1151
1152        async fn insert(
1153            &self,
1154            _key: &InternalCacheKey,
1155            _entry: CacheEntry,
1156            _size_bytes: usize,
1157            _codec: Option<CacheCodec>,
1158        ) {
1159        }
1160
1161        async fn get_or_insert<'a>(
1162            &self,
1163            _key: &InternalCacheKey,
1164            _loader: Pin<Box<dyn Future<Output = Result<(CacheEntry, usize)>> + Send + 'a>>,
1165            _codec: Option<CacheCodec>,
1166        ) -> Result<(CacheEntry, bool)> {
1167            Ok((Arc::new(String::from("wrong type")), true))
1168        }
1169
1170        async fn clear(&self) {}
1171
1172        async fn num_entries(&self) -> usize {
1173            0
1174        }
1175
1176        async fn size_bytes(&self) -> usize {
1177            0
1178        }
1179    }
1180
1181    #[tokio::test]
1182    async fn typed_roundtrip_stats_clear_and_namespace_isolation() {
1183        let cache = LanceCache::with_capacity(4096);
1184        let left = cache.with_key_prefix("left");
1185        let right = cache.with_key_prefix("right");
1186        left.insert_with_key(&TestKey::new(7), Arc::new(vec![1, 2, 3]))
1187            .await;
1188
1189        assert_eq!(
1190            left.get_with_key(&TestKey::new(7)).await.as_deref(),
1191            Some(&vec![1, 2, 3])
1192        );
1193        assert!(right.get_with_key(&TestKey::new(7)).await.is_none());
1194        let stats = cache.stats().await;
1195        assert_eq!((stats.hits, stats.misses, stats.num_entries), (1, 1, 1));
1196
1197        cache.clear().await;
1198        let stats = left.stats().await;
1199        assert_eq!((stats.hits, stats.misses, stats.num_entries), (0, 0, 0));
1200    }
1201
1202    #[tokio::test]
1203    async fn strong_and_weak_handles_share_state_and_namespace() {
1204        let cache = LanceCache::with_capacity(4096);
1205        let child = cache.with_key_prefix("child");
1206        let weak = WeakLanceCache::from(&child);
1207
1208        assert!(
1209            weak.insert_with_key(&TestKey::new(1), Arc::new(vec![1]))
1210                .await
1211        );
1212        assert_eq!(
1213            child.get_with_key(&TestKey::new(1)).await.as_deref(),
1214            Some(&vec![1])
1215        );
1216        child
1217            .insert_with_key(&TestKey::new(2), Arc::new(vec![2]))
1218            .await;
1219        assert_eq!(
1220            weak.get_with_key(&TestKey::new(2)).await.as_deref(),
1221            Some(&vec![2])
1222        );
1223        assert_eq!((cache.stats().await.hits, cache.size().await), (2, 2));
1224    }
1225
1226    #[test]
1227    fn plane_admission_gating_passes_through() {
1228        let cache = LanceCache::with_capacity(4096);
1229        let weak = WeakLanceCache::from(&cache);
1230        assert!(cache.plane_admission_gated() && weak.plane_admission_gated());
1231
1232        drop(cache);
1233        assert!(weak.plane_admission_gated());
1234    }
1235
1236    #[tokio::test]
1237    async fn residency_peek_passes_through() {
1238        let cache = LanceCache::with_capacity(4096);
1239        let weak = WeakLanceCache::from(&cache);
1240        assert!(!cache.peek_resident_with_key(&TestKey::new(1)).await);
1241        cache
1242            .insert_with_key(&TestKey::new(1), Arc::new(vec![1]))
1243            .await;
1244        assert!(cache.peek_resident_with_key(&TestKey::new(1)).await);
1245        assert!(weak.peek_resident_with_key(&TestKey::new(1)).await);
1246        assert!(!weak.peek_resident_with_key(&TestKey::new(2)).await);
1247
1248        drop(cache);
1249        assert!(!weak.peek_resident_with_key(&TestKey::new(1)).await);
1250    }
1251
1252    /// Lance's in-memory backends have no tier below RAM, so the default tier
1253    /// peek reports resident entries and nothing else as held. Peeks are not
1254    /// accesses: they count neither hits nor misses.
1255    #[tokio::test]
1256    async fn tier_peek_defaults_to_residency_without_access() {
1257        for kind in [TestBackendKind::Moka, TestBackendKind::Quick] {
1258            let cache = kind.cache(4096);
1259            let weak = WeakLanceCache::from(&cache);
1260            assert_eq!(
1261                cache.peek_tier_with_key(&TestKey::new(1)).await,
1262                CacheTier::Absent,
1263                "{kind:?}"
1264            );
1265            cache
1266                .insert_with_key(&TestKey::new(1), Arc::new(vec![1]))
1267                .await;
1268            assert_eq!(
1269                cache.peek_tier_with_key(&TestKey::new(1)).await,
1270                CacheTier::Resident,
1271                "{kind:?}"
1272            );
1273            assert_eq!(
1274                weak.peek_tier_with_key(&TestKey::new(1)).await,
1275                CacheTier::Resident,
1276                "{kind:?}"
1277            );
1278            assert_eq!(
1279                weak.peek_tier_with_key(&TestKey::new(2)).await,
1280                CacheTier::Absent,
1281                "{kind:?}"
1282            );
1283            let stats = cache.stats().await;
1284            assert_eq!((stats.hits, stats.misses), (0, 0), "{kind:?}");
1285
1286            cache.clear().await;
1287            assert_eq!(
1288                weak.peek_tier_with_key(&TestKey::new(1)).await,
1289                CacheTier::Absent,
1290                "{kind:?}"
1291            );
1292            cache
1293                .insert_with_key(&TestKey::new(1), Arc::new(vec![1]))
1294                .await;
1295            drop(cache);
1296            assert_eq!(
1297                weak.peek_tier_with_key(&TestKey::new(1)).await,
1298                CacheTier::Absent,
1299                "{kind:?}"
1300            );
1301        }
1302    }
1303
1304    #[tokio::test]
1305    async fn nested_namespace_segments_do_not_alias_combined_segments() {
1306        let cache = LanceCache::with_capacity(4096);
1307        let nested = cache.with_key_prefix("a").with_key_prefix("b");
1308        let combined = cache.with_key_prefix("a/b");
1309        nested
1310            .insert_with_key(&TestKey::new(1), Arc::new(vec![10]))
1311            .await;
1312        assert!(combined.get_with_key(&TestKey::new(1)).await.is_none());
1313    }
1314
1315    #[tokio::test]
1316    async fn schema_change_produces_a_cold_miss() {
1317        let cache = LanceCache::with_capacity(4096);
1318        cache
1319            .insert_with_key(&TestKey::new(1), Arc::new(vec![10]))
1320            .await;
1321        assert!(cache.get_with_key(&TestKeyV2::new(1)).await.is_none());
1322    }
1323
1324    #[tokio::test]
1325    async fn get_or_insert_with_key_hit_reports_loader_execution() {
1326        let cache = LanceCache::with_capacity(4096);
1327
1328        // Cold: loader runs, was_cached = false.
1329        let (value, was_cached) = cache
1330            .get_or_insert_with_key_hit(TestKey::new(1), || async { Ok(vec![1, 2, 3]) })
1331            .await
1332            .unwrap();
1333        assert_eq!(*value, vec![1, 2, 3]);
1334        assert!(!was_cached);
1335
1336        // Warm: loader must not run and was_cached = true.
1337        let (value, was_cached) = cache
1338            .get_or_insert_with_key_hit(TestKey::new(1), || async {
1339                panic!("should not be called")
1340            })
1341            .await
1342            .unwrap();
1343        assert_eq!(*value, vec![1, 2, 3]);
1344        assert!(was_cached);
1345    }
1346
1347    #[tokio::test]
1348    async fn default_string_bridge_matches_explicit_legacy_encoding() {
1349        let cache = LanceCache::with_capacity(4096);
1350        cache
1351            .insert_with_key(&LegacyBridgeKey("same"), Arc::new(vec![10]))
1352            .await;
1353        assert_eq!(
1354            cache
1355                .get_with_key(&ExplicitBridgeKey("same"))
1356                .await
1357                .as_deref(),
1358            Some(&vec![10])
1359        );
1360    }
1361
1362    #[tokio::test]
1363    async fn unsized_default_string_bridge_matches_explicit_legacy_encoding() {
1364        let cache = LanceCache::with_capacity(4096);
1365        let value: Arc<dyn TestDynValue> = Arc::new(vec![10, 20]);
1366        cache
1367            .insert_unsized_with_key(&LegacyUnsizedBridgeKey("same"), value)
1368            .await;
1369
1370        let cached = cache
1371            .get_unsized_with_key(&ExplicitUnsizedBridgeKey("same"))
1372            .await
1373            .unwrap();
1374        assert_eq!(cached.values(), &[10, 20]);
1375    }
1376
1377    #[tokio::test]
1378    async fn custom_backend_receives_opaque_keys_and_shared_clear() {
1379        let backend = Arc::new(HashMapBackend::default());
1380        let cache = LanceCache::with_backend(backend.clone());
1381        let child = cache.with_key_prefix("child");
1382        let value = Arc::new(vec![1, 2, 3]);
1383        let value_size = cache_entry_size(value.as_ref());
1384
1385        child.insert_with_key(&TestKey::new(7), value).await;
1386        assert_eq!(
1387            child.get_with_key(&TestKey::new(7)).await.as_deref(),
1388            Some(&vec![1, 2, 3])
1389        );
1390        assert_eq!(backend.entries.lock().await.len(), 1);
1391        assert_eq!(cache.size_bytes().await, value_size);
1392
1393        cache.clear().await;
1394        assert!(backend.entries.lock().await.is_empty());
1395        assert_eq!(child.stats().await.hits, 0);
1396    }
1397
1398    #[tokio::test]
1399    async fn backend_type_collisions_are_contextual_misses_or_errors() {
1400        let cache = LanceCache::with_backend(Arc::new(WrongTypeBackend));
1401
1402        assert!(cache.get_with_key(&TestKey::new(1)).await.is_none());
1403        let error = cache
1404            .get_or_insert_with_key(TestKey::new(2), || async { Ok(vec![2]) })
1405            .await
1406            .unwrap_err();
1407        assert!(error.to_string().contains("test.VecU32"));
1408        let stats = cache.stats().await;
1409        assert_eq!((stats.hits, stats.misses), (0, 2));
1410    }
1411
1412    #[tokio::test]
1413    async fn moka_weight_includes_the_fixed_physical_key() {
1414        let value = Arc::new(vec![0_u32; 3]);
1415        let expected = cache_entry_size(value.as_ref())
1416            .checked_add(std::mem::size_of::<InternalCacheKey>())
1417            .unwrap();
1418        let cache = LanceCache::with_capacity(expected * 2);
1419        cache.insert_with_key(&TestKey::new(1), value).await;
1420        assert_eq!(cache.size_bytes().await, expected);
1421    }
1422
1423    #[rstest::rstest]
1424    #[case::moka(TestBackendKind::Moka)]
1425    #[case::quick(TestBackendKind::Quick)]
1426    #[tokio::test]
1427    async fn deep_size_deduplicates_shared_entry_allocations(
1428        #[case] backend_kind: TestBackendKind,
1429    ) {
1430        let cache = backend_kind.cache(1 << 20);
1431        let shared_data = Arc::new(vec![0_u8; 1024]);
1432
1433        for id in 0..2 {
1434            let data = shared_data.clone();
1435            cache
1436                .get_or_insert_with_key(SharedTestKey(id), || async move {
1437                    Ok(SharedTestValue { data })
1438                })
1439                .await
1440                .unwrap();
1441        }
1442
1443        let arc_overhead = std::mem::size_of::<AtomicUsize>() * 2;
1444        let shared_allocation = std::mem::size_of::<Vec<u8>>() + shared_data.capacity();
1445        let expected_entries = 2 * std::mem::size_of::<InternalCacheKey>()
1446            + 2 * (std::mem::size_of::<SharedTestValue>() + arc_overhead)
1447            + shared_allocation;
1448
1449        let weighted_size = cache.size_bytes().await;
1450        assert_eq!(weighted_size, expected_entries + shared_allocation);
1451        assert_eq!(
1452            cache.deep_size_of(),
1453            std::mem::size_of::<LanceCache>() + expected_entries
1454        );
1455
1456        let mut context = Context::new();
1457        assert_eq!(cache.deep_size_of_children(&mut context), expected_entries);
1458        assert_eq!(
1459            cache
1460                .with_key_prefix("another-handle")
1461                .deep_size_of_children(&mut context),
1462            0
1463        );
1464    }
1465
1466    #[test]
1467    fn sizing_can_reenter_the_same_cache() {
1468        let (done_tx, done_rx) = mpsc::channel();
1469        let worker = thread::spawn(move || {
1470            let runtime = tokio::runtime::Builder::new_current_thread()
1471                .enable_all()
1472                .build()
1473                .unwrap();
1474            runtime.block_on(async move {
1475                let cache = LanceCache::with_capacity(4096);
1476                cache
1477                    .insert_with_key(&ReentrantKey, Arc::new(ReentrantValue(cache.clone())))
1478                    .await;
1479                done_tx.send(()).unwrap();
1480            });
1481        });
1482
1483        done_rx
1484            .recv_timeout(Duration::from_secs(1))
1485            .expect("cache insertion deadlocked during sizing");
1486        worker.join().unwrap();
1487    }
1488
1489    #[tokio::test]
1490    async fn no_cache_computes_each_time() {
1491        let cache = LanceCache::no_cache();
1492        let loads = Arc::new(AtomicUsize::new(0));
1493        for _ in 0..2 {
1494            let loads = loads.clone();
1495            let value = cache
1496                .get_or_insert_with_key(TestKey::new(1), move || async move {
1497                    loads.fetch_add(1, Ordering::SeqCst);
1498                    Ok(vec![42])
1499                })
1500                .await
1501                .unwrap();
1502            assert_eq!(value.as_slice(), &[42]);
1503        }
1504        assert_eq!(loads.load(Ordering::SeqCst), 2);
1505        assert_eq!(cache.size().await, 0);
1506    }
1507
1508    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1509    async fn single_flight_coalesces_success_after_contenders_are_parked() {
1510        const CONTENDERS: usize = 4;
1511
1512        let cache = Arc::new(LanceCache::with_capacity(4096));
1513        let loader_calls = Arc::new(AtomicUsize::new(0));
1514        let release = Arc::new(tokio::sync::Notify::new());
1515        let (started_tx, started_rx) = tokio::sync::oneshot::channel();
1516
1517        let owner = {
1518            let cache = cache.clone();
1519            let loader_calls = loader_calls.clone();
1520            let release = release.clone();
1521            tokio::spawn(async move {
1522                cache
1523                    .get_or_insert_with_key(TestKey::new(10), move || async move {
1524                        loader_calls.fetch_add(1, Ordering::SeqCst);
1525                        let _ = started_tx.send(());
1526                        release.notified().await;
1527                        Ok(vec![10])
1528                    })
1529                    .await
1530            })
1531        };
1532        started_rx.await.unwrap();
1533
1534        let mut contenders = Vec::new();
1535        let mut parked = Vec::new();
1536        for _ in 0..CONTENDERS {
1537            let cache = cache.clone();
1538            let loader_calls = loader_calls.clone();
1539            let (parked_tx, parked_rx) = tokio::sync::oneshot::channel();
1540            parked.push(parked_rx);
1541            contenders.push(tokio::spawn(async move {
1542                report_first_pending(
1543                    cache.get_or_insert_with_key(TestKey::new(10), move || async move {
1544                        loader_calls.fetch_add(1, Ordering::SeqCst);
1545                        Ok(vec![99])
1546                    }),
1547                    parked_tx,
1548                )
1549                .await
1550            }));
1551        }
1552        for parked in parked {
1553            parked
1554                .await
1555                .expect("contender completed instead of parking behind owner");
1556        }
1557        assert_eq!(loader_calls.load(Ordering::SeqCst), 1);
1558        assert!(contenders.iter().all(|handle| !handle.is_finished()));
1559
1560        release.notify_one();
1561        assert_eq!(owner.await.unwrap().unwrap().as_slice(), &[10]);
1562        for contender in contenders {
1563            assert_eq!(contender.await.unwrap().unwrap().as_slice(), &[10]);
1564        }
1565        let stats = cache.stats().await;
1566        assert_eq!((stats.hits, stats.misses), (CONTENDERS as u64, 1),);
1567    }
1568
1569    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1570    async fn single_flight_coalesces_errors_after_contenders_are_parked() {
1571        const CONTENDERS: usize = 4;
1572
1573        let cache = Arc::new(LanceCache::with_capacity(4096));
1574        let loader_calls = Arc::new(AtomicUsize::new(0));
1575        let release = Arc::new(tokio::sync::Notify::new());
1576        let (started_tx, started_rx) = tokio::sync::oneshot::channel();
1577
1578        let owner = {
1579            let cache = cache.clone();
1580            let loader_calls = loader_calls.clone();
1581            let release = release.clone();
1582            tokio::spawn(async move {
1583                cache
1584                    .get_or_insert_with_key(TestKey::new(20), move || async move {
1585                        loader_calls.fetch_add(1, Ordering::SeqCst);
1586                        let _ = started_tx.send(());
1587                        release.notified().await;
1588                        Err(Error::timeout("owner loader timed out"))
1589                    })
1590                    .await
1591            })
1592        };
1593        started_rx.await.unwrap();
1594
1595        let mut contenders = Vec::new();
1596        let mut parked = Vec::new();
1597        for _ in 0..CONTENDERS {
1598            let cache = cache.clone();
1599            let loader_calls = loader_calls.clone();
1600            let (parked_tx, parked_rx) = tokio::sync::oneshot::channel();
1601            parked.push(parked_rx);
1602            contenders.push(tokio::spawn(async move {
1603                report_first_pending(
1604                    cache.get_or_insert_with_key(TestKey::new(20), move || async move {
1605                        loader_calls.fetch_add(1, Ordering::SeqCst);
1606                        Err(Error::timeout("contender loader timed out"))
1607                    }),
1608                    parked_tx,
1609                )
1610                .await
1611            }));
1612        }
1613        for parked in parked {
1614            parked
1615                .await
1616                .expect("contender completed instead of parking behind owner");
1617        }
1618        assert_eq!(loader_calls.load(Ordering::SeqCst), 1);
1619        assert!(contenders.iter().all(|handle| !handle.is_finished()));
1620
1621        release.notify_one();
1622        // The loader error is shared across every waiter, so it comes back
1623        // wrapped in `Error::Shared`; `find_root` recovers the timeout.
1624        let owner_err = owner.await.unwrap().expect_err("owner must see the error");
1625        assert!(matches!(owner_err.find_root(), Error::Timeout { .. }));
1626        for contender in contenders {
1627            let err = contender
1628                .await
1629                .unwrap()
1630                .expect_err("contender must see the error");
1631            assert!(matches!(err.find_root(), Error::Timeout { .. }));
1632        }
1633        assert_eq!(loader_calls.load(Ordering::SeqCst), 1);
1634    }
1635
1636    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1637    async fn single_flight_retries_after_the_owner_is_cancelled() {
1638        let cache = Arc::new(LanceCache::with_capacity(4096));
1639        let loader_calls = Arc::new(AtomicUsize::new(0));
1640        let (started_tx, started_rx) = tokio::sync::oneshot::channel();
1641
1642        let owner = {
1643            let cache = cache.clone();
1644            let loader_calls = loader_calls.clone();
1645            tokio::spawn(async move {
1646                cache
1647                    .get_or_insert_with_key(TestKey::new(30), move || async move {
1648                        loader_calls.fetch_add(1, Ordering::SeqCst);
1649                        let _ = started_tx.send(());
1650                        std::future::pending::<()>().await;
1651                        Ok(vec![30])
1652                    })
1653                    .await
1654            })
1655        };
1656        started_rx.await.unwrap();
1657
1658        let (parked_tx, parked_rx) = tokio::sync::oneshot::channel();
1659        let contender = {
1660            let cache = cache.clone();
1661            let loader_calls = loader_calls.clone();
1662            tokio::spawn(async move {
1663                report_first_pending(
1664                    cache.get_or_insert_with_key(TestKey::new(30), move || async move {
1665                        loader_calls.fetch_add(1, Ordering::SeqCst);
1666                        Ok(vec![31])
1667                    }),
1668                    parked_tx,
1669                )
1670                .await
1671            })
1672        };
1673        parked_rx
1674            .await
1675            .expect("contender completed instead of parking behind owner");
1676        assert_eq!(loader_calls.load(Ordering::SeqCst), 1);
1677        assert!(!contender.is_finished());
1678
1679        owner.abort();
1680        assert!(owner.await.unwrap_err().is_cancelled());
1681        let value = tokio::time::timeout(std::time::Duration::from_secs(5), contender)
1682            .await
1683            .expect("contender remained parked after owner cancellation")
1684            .unwrap()
1685            .unwrap();
1686        assert_eq!(value.as_slice(), &[31]);
1687        assert_eq!(loader_calls.load(Ordering::SeqCst), 2);
1688    }
1689
1690    #[tokio::test]
1691    async fn expired_weak_cache_degrades_without_retaining_state() {
1692        let cache = LanceCache::with_capacity(4096);
1693        let weak = WeakLanceCache::from(&cache);
1694        drop(cache);
1695
1696        assert!(weak.get_with_key(&TestKey::new(1)).await.is_none());
1697        assert!(
1698            !weak
1699                .insert_with_key(&TestKey::new(1), Arc::new(vec![1]))
1700                .await
1701        );
1702        let value = weak
1703            .get_or_insert_with_key(TestKey::new(1), || async { Ok(vec![7]) })
1704            .await
1705            .unwrap();
1706        assert_eq!(value.as_slice(), &[7]);
1707    }
1708}