1pub 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
85pub trait CacheKey {
108 type ValueType: 'static;
109
110 fn key(&self) -> Cow<'_, str>;
111
112 fn type_name() -> &'static str;
121
122 fn stable_type_id() -> &'static str {
127 Self::type_name()
128 }
129
130 fn schema() -> CacheKeySchema {
132 CacheKeySchema::LEGACY_TEXT
133 }
134
135 fn write_key(&self, builder: &mut KeyBuilder) {
140 builder.write_str(self.key().as_ref());
141 }
142
143 fn codec_for_key(&self) -> Option<CacheCodec> {
145 Self::codec()
146 }
147
148 fn codec() -> Option<CacheCodec> {
157 None
158 }
159}
160
161pub trait UnsizedCacheKey {
169 type ValueType: 'static + ?Sized;
170
171 fn key(&self) -> Cow<'_, str>;
172
173 fn type_name() -> &'static str;
176
177 fn stable_type_id() -> &'static str {
179 Self::type_name()
180 }
181
182 fn schema() -> CacheKeySchema {
184 CacheKeySchema::LEGACY_TEXT
185 }
186
187 fn write_key(&self, builder: &mut KeyBuilder) {
189 builder.write_str(self.key().as_ref());
190 }
191}
192
193fn 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#[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 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 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 pub fn capacity_bytes(&self) -> Option<usize> {
348 self.state.backend.capacity_bytes()
349 }
350
351 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 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 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 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 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 pub fn plane_admission_gated(&self) -> bool {
413 self.state.backend.plane_admission_gated()
414 }
415
416 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 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 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 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 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#[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 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 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 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 pub fn plane_admission_gated(&self) -> bool {
703 self.upgrade()
704 .is_none_or(|cache| cache.plane_admission_gated())
705 }
706
707 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 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 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 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 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#[derive(Debug, Clone)]
835pub struct CacheStats {
836 pub hits: u64,
838 pub misses: u64,
840 pub num_entries: usize,
842 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 #[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 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 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 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}