1pub mod backend;
49pub mod codec;
50mod entry_io;
51mod key;
52mod moka;
53mod quick;
54
55pub use backend::{CacheBackend, CacheEntry};
56pub use codec::{
57 CacheCodec, CacheCodecImpl, CacheDecode, CacheMissReason, MAGIC, has_cache_envelope,
58};
59pub use entry_io::{CacheEntryReader, CacheEntryWriter};
60pub use key::{CACHE_KEY_FORMAT, CacheKeySchema, CacheNamespace, InternalCacheKey, KeyBuilder};
61pub use moka::MokaCacheBackend;
62pub use quick::{QuickCacheBackend, recommended_cache_shards};
63
64use std::borrow::Cow;
65use std::sync::{
66 Arc, Weak,
67 atomic::{AtomicU64, Ordering},
68};
69
70use futures::Future;
71
72use crate::{Error, Result};
73
74pub use crate::deepsize::{Context, DeepSizeOf};
75
76pub trait CacheKey {
99 type ValueType: 'static;
100
101 fn key(&self) -> Cow<'_, str>;
102
103 fn type_name() -> &'static str;
112
113 fn stable_type_id() -> &'static str {
118 Self::type_name()
119 }
120
121 fn schema() -> CacheKeySchema {
123 CacheKeySchema::LEGACY_TEXT
124 }
125
126 fn write_key(&self, builder: &mut KeyBuilder) {
131 builder.write_str(self.key().as_ref());
132 }
133
134 fn codec() -> Option<CacheCodec> {
143 None
144 }
145}
146
147pub trait UnsizedCacheKey {
155 type ValueType: 'static + ?Sized;
156
157 fn key(&self) -> Cow<'_, str>;
158
159 fn type_name() -> &'static str;
162
163 fn stable_type_id() -> &'static str {
165 Self::type_name()
166 }
167
168 fn schema() -> CacheKeySchema {
170 CacheKeySchema::LEGACY_TEXT
171 }
172
173 fn write_key(&self, builder: &mut KeyBuilder) {
175 builder.write_str(self.key().as_ref());
176 }
177}
178
179fn cache_entry_size<T: DeepSizeOf + ?Sized>(value: &T) -> usize {
185 value.deep_size_of() + std::mem::size_of::<std::sync::atomic::AtomicUsize>() * 2
186}
187
188#[derive(Debug)]
189struct CacheState {
190 backend: Arc<dyn CacheBackend>,
191 hits: AtomicU64,
192 misses: AtomicU64,
193}
194
195impl CacheState {
196 fn new(backend: Arc<dyn CacheBackend>) -> Self {
197 Self {
198 backend,
199 hits: AtomicU64::new(0),
200 misses: AtomicU64::new(0),
201 }
202 }
203}
204
205#[derive(Clone)]
214pub struct LanceCache {
215 state: Arc<CacheState>,
216 namespace: key::CacheNamespace,
217}
218
219impl std::fmt::Debug for LanceCache {
220 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
221 f.debug_struct("LanceCache")
222 .field("backend", &self.state.backend)
223 .finish_non_exhaustive()
224 }
225}
226
227impl DeepSizeOf for LanceCache {
228 fn deep_size_of_children(&self, _: &mut Context) -> usize {
229 self.state.backend.approx_size_bytes()
230 }
231}
232
233impl LanceCache {
234 pub fn with_capacity(capacity: usize) -> Self {
235 Self::with_backend(Arc::new(MokaCacheBackend::with_capacity(capacity)))
236 }
237
238 pub fn with_backend(backend: Arc<dyn CacheBackend>) -> Self {
240 Self {
241 state: Arc::new(CacheState::new(backend)),
242 namespace: key::CacheNamespace::root(),
243 }
244 }
245
246 pub fn no_cache() -> Self {
247 Self::with_backend(Arc::new(MokaCacheBackend::no_cache()))
248 }
249
250 pub fn with_key_prefix(&self, prefix: &str) -> Self {
256 Self {
257 state: self.state.clone(),
258 namespace: self.namespace.child(prefix),
259 }
260 }
261
262 pub async fn size(&self) -> usize {
263 self.state.backend.num_entries().await
264 }
265
266 pub fn approx_size(&self) -> usize {
267 self.state.backend.approx_num_entries()
268 }
269
270 pub async fn size_bytes(&self) -> usize {
271 self.state.backend.size_bytes().await
272 }
273
274 pub async fn stats(&self) -> CacheStats {
277 CacheStats {
278 hits: self.state.hits.load(Ordering::Relaxed),
279 misses: self.state.misses.load(Ordering::Relaxed),
280 num_entries: self.state.backend.num_entries().await,
281 size_bytes: self.state.backend.size_bytes().await,
282 }
283 }
284
285 pub async fn clear(&self) {
286 self.state.backend.clear().await;
287 self.state.hits.store(0, Ordering::Relaxed);
288 self.state.misses.store(0, Ordering::Relaxed);
289 }
290
291 pub async fn insert_with_key<K>(&self, cache_key: &K, metadata: Arc<K::ValueType>)
294 where
295 K: CacheKey,
296 K::ValueType: DeepSizeOf + Send + Sync + 'static,
297 {
298 let size = cache_entry_size(metadata.as_ref());
299 let key = self.sized_key(cache_key);
300 self.state
301 .backend
302 .insert(&key, metadata, size, K::codec())
303 .await;
304 }
305
306 pub async fn get_with_key<K>(&self, cache_key: &K) -> Option<Arc<K::ValueType>>
307 where
308 K: CacheKey,
309 K::ValueType: DeepSizeOf + Send + Sync + 'static,
310 {
311 let key = self.sized_key(cache_key);
312 let Some(entry) = self.state.backend.get(&key, K::codec()).await else {
313 self.state.misses.fetch_add(1, Ordering::Relaxed);
314 return None;
315 };
316 match entry.downcast::<K::ValueType>() {
317 Ok(value) => {
318 self.state.hits.fetch_add(1, Ordering::Relaxed);
319 Some(value)
320 }
321 Err(_) => {
322 log::warn!(
326 "cache backend returned a value with the wrong concrete type for key type {:?}",
327 K::stable_type_id()
328 );
329 self.state.misses.fetch_add(1, Ordering::Relaxed);
330 None
331 }
332 }
333 }
334
335 pub async fn get_or_insert_with_key<K, F, Fut>(
336 &self,
337 cache_key: K,
338 loader: F,
339 ) -> Result<Arc<K::ValueType>>
340 where
341 K: CacheKey,
342 K::ValueType: DeepSizeOf + Send + Sync + 'static,
343 F: FnOnce() -> Fut + Send,
344 Fut: Future<Output = Result<K::ValueType>> + Send,
345 {
346 self.get_or_insert_with_key_hit(cache_key, loader)
347 .await
348 .map(|(value, _)| value)
349 }
350
351 pub async fn get_or_insert_with_key_hit<K, F, Fut>(
367 &self,
368 cache_key: K,
369 loader: F,
370 ) -> Result<(Arc<K::ValueType>, bool)>
371 where
372 K: CacheKey,
373 K::ValueType: DeepSizeOf + Send + Sync + 'static,
374 F: FnOnce() -> Fut + Send,
375 Fut: Future<Output = Result<K::ValueType>> + Send,
376 {
377 let key = self.sized_key(&cache_key);
378 let typed_loader = Box::pin(async move {
379 let value = Arc::new(loader().await?);
380 let size = cache_entry_size(value.as_ref());
381 Ok((value as CacheEntry, size))
382 });
383
384 let (entry, was_cached) = self
385 .state
386 .backend
387 .get_or_insert(&key, typed_loader, K::codec())
388 .await?;
389 let entry = entry.downcast::<K::ValueType>().map_err(|_| {
390 self.state.misses.fetch_add(1, Ordering::Relaxed);
391 Error::io(format!(
392 "cache backend returned a value with the wrong concrete type for key type {:?}",
393 K::stable_type_id()
394 ))
395 })?;
396 if was_cached {
397 self.state.hits.fetch_add(1, Ordering::Relaxed);
398 } else {
399 self.state.misses.fetch_add(1, Ordering::Relaxed);
400 }
401 Ok((entry, was_cached))
402 }
403
404 pub async fn insert_unsized_with_key<K>(&self, cache_key: &K, metadata: Arc<K::ValueType>)
405 where
406 K: UnsizedCacheKey,
407 K::ValueType: DeepSizeOf + Send + Sync + 'static,
408 {
409 let metadata = Arc::new(metadata);
410 let size = cache_entry_size(metadata.as_ref());
411 let key = self.unsized_key(cache_key);
412 self.state.backend.insert(&key, metadata, size, None).await;
413 }
414
415 pub async fn get_unsized_with_key<K>(&self, cache_key: &K) -> Option<Arc<K::ValueType>>
416 where
417 K: UnsizedCacheKey,
418 K::ValueType: DeepSizeOf + Send + Sync + 'static,
419 {
420 let key = self.unsized_key(cache_key);
421 let Some(entry) = self.state.backend.get(&key, None).await else {
422 self.state.misses.fetch_add(1, Ordering::Relaxed);
423 return None;
424 };
425 match entry.downcast::<Arc<K::ValueType>>() {
426 Ok(value) => {
427 self.state.hits.fetch_add(1, Ordering::Relaxed);
428 Some(value.as_ref().clone())
429 }
430 Err(_) => {
431 log::warn!(
435 "cache backend returned a value with the wrong concrete type for unsized key type {:?}",
436 K::stable_type_id()
437 );
438 self.state.misses.fetch_add(1, Ordering::Relaxed);
439 None
440 }
441 }
442 }
443
444 fn sized_key<K: CacheKey>(&self, cache_key: &K) -> InternalCacheKey {
445 let mut builder = KeyBuilder::new(self.namespace, K::stable_type_id(), K::schema());
446 cache_key.write_key(&mut builder);
447 builder.finish()
448 }
449
450 fn unsized_key<K: UnsizedCacheKey>(&self, cache_key: &K) -> InternalCacheKey {
451 let mut builder = KeyBuilder::new(self.namespace, K::stable_type_id(), K::schema());
452 cache_key.write_key(&mut builder);
453 builder.finish()
454 }
455}
456
457#[derive(Clone, Debug)]
464pub struct WeakLanceCache {
465 state: Weak<CacheState>,
466 namespace: key::CacheNamespace,
467}
468
469impl WeakLanceCache {
470 pub fn from(cache: &LanceCache) -> Self {
471 Self {
472 state: Arc::downgrade(&cache.state),
473 namespace: cache.namespace,
474 }
475 }
476
477 pub fn with_key_prefix(&self, prefix: &str) -> Self {
478 Self {
479 state: self.state.clone(),
480 namespace: self.namespace.child(prefix),
481 }
482 }
483
484 pub async fn get_with_key<K>(&self, cache_key: &K) -> Option<Arc<K::ValueType>>
485 where
486 K: CacheKey,
487 K::ValueType: DeepSizeOf + Send + Sync + 'static,
488 {
489 self.upgrade()?.get_with_key(cache_key).await
490 }
491
492 pub async fn insert_with_key<K>(&self, cache_key: &K, value: Arc<K::ValueType>) -> bool
493 where
494 K: CacheKey,
495 K::ValueType: DeepSizeOf + Send + Sync + 'static,
496 {
497 let Some(cache) = self.upgrade() else {
498 log::warn!("WeakLanceCache: cache no longer available, unable to insert item");
499 return false;
500 };
501 cache.insert_with_key(cache_key, value).await;
502 true
503 }
504
505 pub async fn get_or_insert_with_key<K, F, Fut>(
509 &self,
510 cache_key: K,
511 loader: F,
512 ) -> Result<Arc<K::ValueType>>
513 where
514 K: CacheKey,
515 K::ValueType: DeepSizeOf + Send + Sync + 'static,
516 F: FnOnce() -> Fut + Send,
517 Fut: Future<Output = Result<K::ValueType>> + Send,
518 {
519 self.get_or_insert_with_key_hit(cache_key, loader)
520 .await
521 .map(|(value, _)| value)
522 }
523
524 pub async fn get_or_insert_with_key_hit<K, F, Fut>(
529 &self,
530 cache_key: K,
531 loader: F,
532 ) -> Result<(Arc<K::ValueType>, bool)>
533 where
534 K: CacheKey,
535 K::ValueType: DeepSizeOf + Send + Sync + 'static,
536 F: FnOnce() -> Fut + Send,
537 Fut: Future<Output = Result<K::ValueType>> + Send,
538 {
539 let Some(cache) = self.upgrade() else {
540 log::warn!("WeakLanceCache: cache no longer available, computing without caching");
541 return loader().await.map(|value| (Arc::new(value), false));
542 };
543 cache.get_or_insert_with_key_hit(cache_key, loader).await
544 }
545
546 pub async fn get_unsized_with_key<K>(&self, cache_key: &K) -> Option<Arc<K::ValueType>>
547 where
548 K: UnsizedCacheKey,
549 K::ValueType: DeepSizeOf + Send + Sync + 'static,
550 {
551 self.upgrade()?.get_unsized_with_key(cache_key).await
552 }
553
554 pub async fn insert_unsized_with_key<K>(&self, cache_key: &K, value: Arc<K::ValueType>)
555 where
556 K: UnsizedCacheKey,
557 K::ValueType: DeepSizeOf + Send + Sync + 'static,
558 {
559 let Some(cache) = self.upgrade() else {
560 log::warn!("WeakLanceCache: cache no longer available, unable to insert unsized item");
561 return;
562 };
563 cache.insert_unsized_with_key(cache_key, value).await;
564 }
565
566 fn upgrade(&self) -> Option<LanceCache> {
567 Some(LanceCache {
568 state: self.state.upgrade()?,
569 namespace: self.namespace,
570 })
571 }
572}
573
574#[derive(Debug, Clone)]
579pub struct CacheStats {
580 pub hits: u64,
582 pub misses: u64,
584 pub num_entries: usize,
586 pub size_bytes: usize,
588}
589
590impl CacheStats {
591 pub fn hit_ratio(&self) -> f32 {
592 if self.hits + self.misses == 0 {
593 0.0
594 } else {
595 self.hits as f32 / (self.hits + self.misses) as f32
596 }
597 }
598
599 pub fn miss_ratio(&self) -> f32 {
600 if self.hits + self.misses == 0 {
601 0.0
602 } else {
603 self.misses as f32 / (self.hits + self.misses) as f32
604 }
605 }
606}
607
608#[cfg(test)]
609mod tests {
610 use std::collections::HashMap;
611 use std::pin::Pin;
612 use std::sync::atomic::{AtomicUsize, Ordering};
613 use std::task::Poll;
614
615 use super::*;
616
617 async fn report_first_pending<F>(
618 future: F,
619 parked: tokio::sync::oneshot::Sender<()>,
620 ) -> F::Output
621 where
622 F: Future,
623 {
624 tokio::pin!(future);
625 let mut parked = Some(parked);
626 futures::future::poll_fn(|cx| match future.as_mut().poll(cx) {
627 Poll::Pending => {
628 if let Some(parked) = parked.take() {
629 let _ = parked.send(());
630 }
631 Poll::Pending
632 }
633 Poll::Ready(output) => Poll::Ready(output),
634 })
635 .await
636 }
637
638 #[derive(Clone)]
639 struct VersionedTestKey<const SCHEMA_VERSION: u32> {
640 id: u64,
641 }
642
643 type TestKey = VersionedTestKey<1>;
644 type TestKeyV2 = VersionedTestKey<2>;
645
646 impl<const SCHEMA_VERSION: u32> VersionedTestKey<SCHEMA_VERSION> {
647 fn new(id: u64) -> Self {
648 Self { id }
649 }
650 }
651
652 impl<const SCHEMA_VERSION: u32> CacheKey for VersionedTestKey<SCHEMA_VERSION> {
653 type ValueType = Vec<u32>;
654
655 fn key(&self) -> Cow<'_, str> {
656 self.id.to_string().into()
657 }
658
659 fn type_name() -> &'static str {
660 "test.VecU32"
661 }
662
663 fn schema() -> CacheKeySchema {
664 CacheKeySchema::new("test.vec-u32-key", SCHEMA_VERSION)
665 }
666
667 fn write_key(&self, builder: &mut KeyBuilder) {
668 builder.write_u64(self.id);
669 }
670 }
671
672 struct LegacyBridgeKey(&'static str);
673
674 impl CacheKey for LegacyBridgeKey {
675 type ValueType = Vec<u32>;
676
677 fn key(&self) -> Cow<'_, str> {
678 Cow::Borrowed(self.0)
679 }
680
681 fn type_name() -> &'static str {
682 "test.LegacyBridge"
683 }
684 }
685
686 struct ExplicitBridgeKey(&'static str);
687
688 impl CacheKey for ExplicitBridgeKey {
689 type ValueType = Vec<u32>;
690
691 fn key(&self) -> Cow<'_, str> {
692 Cow::Borrowed(self.0)
693 }
694
695 fn type_name() -> &'static str {
696 "test.LegacyBridge"
697 }
698
699 fn write_key(&self, builder: &mut KeyBuilder) {
700 builder.write_str(self.0);
701 }
702 }
703
704 trait TestDynValue: DeepSizeOf + Send + Sync {
705 fn values(&self) -> &[u32];
706 }
707
708 impl TestDynValue for Vec<u32> {
709 fn values(&self) -> &[u32] {
710 self
711 }
712 }
713
714 struct LegacyUnsizedBridgeKey(&'static str);
715
716 impl UnsizedCacheKey for LegacyUnsizedBridgeKey {
717 type ValueType = dyn TestDynValue;
718
719 fn key(&self) -> Cow<'_, str> {
720 Cow::Borrowed(self.0)
721 }
722
723 fn type_name() -> &'static str {
724 "test.LegacyUnsizedBridge"
725 }
726 }
727
728 struct ExplicitUnsizedBridgeKey(&'static str);
729
730 impl UnsizedCacheKey for ExplicitUnsizedBridgeKey {
731 type ValueType = dyn TestDynValue;
732
733 fn key(&self) -> Cow<'_, str> {
734 Cow::Borrowed(self.0)
735 }
736
737 fn type_name() -> &'static str {
738 "test.LegacyUnsizedBridge"
739 }
740
741 fn write_key(&self, builder: &mut KeyBuilder) {
742 builder.write_str(self.0);
743 }
744 }
745
746 #[derive(Debug, Default)]
747 struct HashMapBackend {
748 entries: tokio::sync::Mutex<HashMap<InternalCacheKey, (CacheEntry, usize)>>,
749 }
750
751 #[async_trait::async_trait]
752 impl CacheBackend for HashMapBackend {
753 async fn get(
754 &self,
755 key: &InternalCacheKey,
756 _codec: Option<CacheCodec>,
757 ) -> Option<CacheEntry> {
758 self.entries
759 .lock()
760 .await
761 .get(key)
762 .map(|(entry, _)| entry.clone())
763 }
764
765 async fn insert(
766 &self,
767 key: &InternalCacheKey,
768 entry: CacheEntry,
769 size_bytes: usize,
770 _codec: Option<CacheCodec>,
771 ) {
772 self.entries.lock().await.insert(*key, (entry, size_bytes));
773 }
774
775 async fn get_or_insert<'a>(
776 &self,
777 key: &InternalCacheKey,
778 loader: Pin<Box<dyn Future<Output = Result<(CacheEntry, usize)>> + Send + 'a>>,
779 codec: Option<CacheCodec>,
780 ) -> Result<(CacheEntry, bool)> {
781 if let Some(entry) = self.get(key, codec).await {
782 return Ok((entry, true));
783 }
784 let (entry, size_bytes) = loader.await?;
785 self.insert(key, entry.clone(), size_bytes, codec).await;
786 Ok((entry, false))
787 }
788
789 async fn clear(&self) {
790 self.entries.lock().await.clear();
791 }
792
793 async fn num_entries(&self) -> usize {
794 self.entries.lock().await.len()
795 }
796
797 async fn size_bytes(&self) -> usize {
798 self.entries
799 .lock()
800 .await
801 .values()
802 .map(|(_, size_bytes)| size_bytes)
803 .sum()
804 }
805 }
806
807 #[derive(Debug)]
808 struct WrongTypeBackend;
809
810 #[async_trait::async_trait]
811 impl CacheBackend for WrongTypeBackend {
812 async fn get(
813 &self,
814 _key: &InternalCacheKey,
815 _codec: Option<CacheCodec>,
816 ) -> Option<CacheEntry> {
817 Some(Arc::new(String::from("wrong type")))
818 }
819
820 async fn insert(
821 &self,
822 _key: &InternalCacheKey,
823 _entry: CacheEntry,
824 _size_bytes: usize,
825 _codec: Option<CacheCodec>,
826 ) {
827 }
828
829 async fn get_or_insert<'a>(
830 &self,
831 _key: &InternalCacheKey,
832 _loader: Pin<Box<dyn Future<Output = Result<(CacheEntry, usize)>> + Send + 'a>>,
833 _codec: Option<CacheCodec>,
834 ) -> Result<(CacheEntry, bool)> {
835 Ok((Arc::new(String::from("wrong type")), true))
836 }
837
838 async fn clear(&self) {}
839
840 async fn num_entries(&self) -> usize {
841 0
842 }
843
844 async fn size_bytes(&self) -> usize {
845 0
846 }
847 }
848
849 #[tokio::test]
850 async fn typed_roundtrip_stats_clear_and_namespace_isolation() {
851 let cache = LanceCache::with_capacity(4096);
852 let left = cache.with_key_prefix("left");
853 let right = cache.with_key_prefix("right");
854 left.insert_with_key(&TestKey::new(7), Arc::new(vec![1, 2, 3]))
855 .await;
856
857 assert_eq!(
858 left.get_with_key(&TestKey::new(7)).await.as_deref(),
859 Some(&vec![1, 2, 3])
860 );
861 assert!(right.get_with_key(&TestKey::new(7)).await.is_none());
862 let stats = cache.stats().await;
863 assert_eq!((stats.hits, stats.misses, stats.num_entries), (1, 1, 1));
864
865 cache.clear().await;
866 let stats = left.stats().await;
867 assert_eq!((stats.hits, stats.misses, stats.num_entries), (0, 0, 0));
868 }
869
870 #[tokio::test]
871 async fn strong_and_weak_handles_share_state_and_namespace() {
872 let cache = LanceCache::with_capacity(4096);
873 let child = cache.with_key_prefix("child");
874 let weak = WeakLanceCache::from(&child);
875
876 assert!(
877 weak.insert_with_key(&TestKey::new(1), Arc::new(vec![1]))
878 .await
879 );
880 assert_eq!(
881 child.get_with_key(&TestKey::new(1)).await.as_deref(),
882 Some(&vec![1])
883 );
884 child
885 .insert_with_key(&TestKey::new(2), Arc::new(vec![2]))
886 .await;
887 assert_eq!(
888 weak.get_with_key(&TestKey::new(2)).await.as_deref(),
889 Some(&vec![2])
890 );
891 assert_eq!((cache.stats().await.hits, cache.size().await), (2, 2));
892 }
893
894 #[tokio::test]
895 async fn nested_namespace_segments_do_not_alias_combined_segments() {
896 let cache = LanceCache::with_capacity(4096);
897 let nested = cache.with_key_prefix("a").with_key_prefix("b");
898 let combined = cache.with_key_prefix("a/b");
899 nested
900 .insert_with_key(&TestKey::new(1), Arc::new(vec![10]))
901 .await;
902 assert!(combined.get_with_key(&TestKey::new(1)).await.is_none());
903 }
904
905 #[tokio::test]
906 async fn schema_change_produces_a_cold_miss() {
907 let cache = LanceCache::with_capacity(4096);
908 cache
909 .insert_with_key(&TestKey::new(1), Arc::new(vec![10]))
910 .await;
911 assert!(cache.get_with_key(&TestKeyV2::new(1)).await.is_none());
912 }
913
914 #[tokio::test]
915 async fn get_or_insert_with_key_hit_reports_loader_execution() {
916 let cache = LanceCache::with_capacity(4096);
917
918 let (value, was_cached) = cache
920 .get_or_insert_with_key_hit(TestKey::new(1), || async { Ok(vec![1, 2, 3]) })
921 .await
922 .unwrap();
923 assert_eq!(*value, vec![1, 2, 3]);
924 assert!(!was_cached);
925
926 let (value, was_cached) = cache
928 .get_or_insert_with_key_hit(TestKey::new(1), || async {
929 panic!("should not be called")
930 })
931 .await
932 .unwrap();
933 assert_eq!(*value, vec![1, 2, 3]);
934 assert!(was_cached);
935 }
936
937 #[tokio::test]
938 async fn default_string_bridge_matches_explicit_legacy_encoding() {
939 let cache = LanceCache::with_capacity(4096);
940 cache
941 .insert_with_key(&LegacyBridgeKey("same"), Arc::new(vec![10]))
942 .await;
943 assert_eq!(
944 cache
945 .get_with_key(&ExplicitBridgeKey("same"))
946 .await
947 .as_deref(),
948 Some(&vec![10])
949 );
950 }
951
952 #[tokio::test]
953 async fn unsized_default_string_bridge_matches_explicit_legacy_encoding() {
954 let cache = LanceCache::with_capacity(4096);
955 let value: Arc<dyn TestDynValue> = Arc::new(vec![10, 20]);
956 cache
957 .insert_unsized_with_key(&LegacyUnsizedBridgeKey("same"), value)
958 .await;
959
960 let cached = cache
961 .get_unsized_with_key(&ExplicitUnsizedBridgeKey("same"))
962 .await
963 .unwrap();
964 assert_eq!(cached.values(), &[10, 20]);
965 }
966
967 #[tokio::test]
968 async fn custom_backend_receives_opaque_keys_and_shared_clear() {
969 let backend = Arc::new(HashMapBackend::default());
970 let cache = LanceCache::with_backend(backend.clone());
971 let child = cache.with_key_prefix("child");
972 let value = Arc::new(vec![1, 2, 3]);
973 let value_size = cache_entry_size(value.as_ref());
974
975 child.insert_with_key(&TestKey::new(7), value).await;
976 assert_eq!(
977 child.get_with_key(&TestKey::new(7)).await.as_deref(),
978 Some(&vec![1, 2, 3])
979 );
980 assert_eq!(backend.entries.lock().await.len(), 1);
981 assert_eq!(cache.size_bytes().await, value_size);
982
983 cache.clear().await;
984 assert!(backend.entries.lock().await.is_empty());
985 assert_eq!(child.stats().await.hits, 0);
986 }
987
988 #[tokio::test]
989 async fn backend_type_collisions_are_contextual_misses_or_errors() {
990 let cache = LanceCache::with_backend(Arc::new(WrongTypeBackend));
991
992 assert!(cache.get_with_key(&TestKey::new(1)).await.is_none());
993 let error = cache
994 .get_or_insert_with_key(TestKey::new(2), || async { Ok(vec![2]) })
995 .await
996 .unwrap_err();
997 assert!(error.to_string().contains("test.VecU32"));
998 let stats = cache.stats().await;
999 assert_eq!((stats.hits, stats.misses), (0, 2));
1000 }
1001
1002 #[tokio::test]
1003 async fn moka_weight_includes_the_fixed_physical_key() {
1004 let value = Arc::new(vec![0_u32; 3]);
1005 let expected = cache_entry_size(value.as_ref())
1006 .checked_add(std::mem::size_of::<InternalCacheKey>())
1007 .unwrap();
1008 let cache = LanceCache::with_capacity(expected * 2);
1009 cache.insert_with_key(&TestKey::new(1), value).await;
1010 assert_eq!(cache.size_bytes().await, expected);
1011 }
1012
1013 #[tokio::test]
1014 async fn no_cache_computes_each_time() {
1015 let cache = LanceCache::no_cache();
1016 let loads = Arc::new(AtomicUsize::new(0));
1017 for _ in 0..2 {
1018 let loads = loads.clone();
1019 let value = cache
1020 .get_or_insert_with_key(TestKey::new(1), move || async move {
1021 loads.fetch_add(1, Ordering::SeqCst);
1022 Ok(vec![42])
1023 })
1024 .await
1025 .unwrap();
1026 assert_eq!(value.as_slice(), &[42]);
1027 }
1028 assert_eq!(loads.load(Ordering::SeqCst), 2);
1029 assert_eq!(cache.size().await, 0);
1030 }
1031
1032 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1033 async fn single_flight_coalesces_success_after_contenders_are_parked() {
1034 const CONTENDERS: usize = 4;
1035
1036 let cache = Arc::new(LanceCache::with_capacity(4096));
1037 let loader_calls = Arc::new(AtomicUsize::new(0));
1038 let release = Arc::new(tokio::sync::Notify::new());
1039 let (started_tx, started_rx) = tokio::sync::oneshot::channel();
1040
1041 let owner = {
1042 let cache = cache.clone();
1043 let loader_calls = loader_calls.clone();
1044 let release = release.clone();
1045 tokio::spawn(async move {
1046 cache
1047 .get_or_insert_with_key(TestKey::new(10), move || async move {
1048 loader_calls.fetch_add(1, Ordering::SeqCst);
1049 let _ = started_tx.send(());
1050 release.notified().await;
1051 Ok(vec![10])
1052 })
1053 .await
1054 })
1055 };
1056 started_rx.await.unwrap();
1057
1058 let mut contenders = Vec::new();
1059 let mut parked = Vec::new();
1060 for _ in 0..CONTENDERS {
1061 let cache = cache.clone();
1062 let loader_calls = loader_calls.clone();
1063 let (parked_tx, parked_rx) = tokio::sync::oneshot::channel();
1064 parked.push(parked_rx);
1065 contenders.push(tokio::spawn(async move {
1066 report_first_pending(
1067 cache.get_or_insert_with_key(TestKey::new(10), move || async move {
1068 loader_calls.fetch_add(1, Ordering::SeqCst);
1069 Ok(vec![99])
1070 }),
1071 parked_tx,
1072 )
1073 .await
1074 }));
1075 }
1076 for parked in parked {
1077 parked
1078 .await
1079 .expect("contender completed instead of parking behind owner");
1080 }
1081 assert_eq!(loader_calls.load(Ordering::SeqCst), 1);
1082 assert!(contenders.iter().all(|handle| !handle.is_finished()));
1083
1084 release.notify_one();
1085 assert_eq!(owner.await.unwrap().unwrap().as_slice(), &[10]);
1086 for contender in contenders {
1087 assert_eq!(contender.await.unwrap().unwrap().as_slice(), &[10]);
1088 }
1089 let stats = cache.stats().await;
1090 assert_eq!((stats.hits, stats.misses), (CONTENDERS as u64, 1),);
1091 }
1092
1093 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1094 async fn single_flight_coalesces_errors_after_contenders_are_parked() {
1095 const CONTENDERS: usize = 4;
1096
1097 let cache = Arc::new(LanceCache::with_capacity(4096));
1098 let loader_calls = Arc::new(AtomicUsize::new(0));
1099 let release = Arc::new(tokio::sync::Notify::new());
1100 let (started_tx, started_rx) = tokio::sync::oneshot::channel();
1101
1102 let owner = {
1103 let cache = cache.clone();
1104 let loader_calls = loader_calls.clone();
1105 let release = release.clone();
1106 tokio::spawn(async move {
1107 cache
1108 .get_or_insert_with_key(TestKey::new(20), move || async move {
1109 loader_calls.fetch_add(1, Ordering::SeqCst);
1110 let _ = started_tx.send(());
1111 release.notified().await;
1112 Err(Error::timeout("owner loader timed out"))
1113 })
1114 .await
1115 })
1116 };
1117 started_rx.await.unwrap();
1118
1119 let mut contenders = Vec::new();
1120 let mut parked = Vec::new();
1121 for _ in 0..CONTENDERS {
1122 let cache = cache.clone();
1123 let loader_calls = loader_calls.clone();
1124 let (parked_tx, parked_rx) = tokio::sync::oneshot::channel();
1125 parked.push(parked_rx);
1126 contenders.push(tokio::spawn(async move {
1127 report_first_pending(
1128 cache.get_or_insert_with_key(TestKey::new(20), move || async move {
1129 loader_calls.fetch_add(1, Ordering::SeqCst);
1130 Err(Error::timeout("contender loader timed out"))
1131 }),
1132 parked_tx,
1133 )
1134 .await
1135 }));
1136 }
1137 for parked in parked {
1138 parked
1139 .await
1140 .expect("contender completed instead of parking behind owner");
1141 }
1142 assert_eq!(loader_calls.load(Ordering::SeqCst), 1);
1143 assert!(contenders.iter().all(|handle| !handle.is_finished()));
1144
1145 release.notify_one();
1146 assert!(matches!(owner.await.unwrap(), Err(Error::Timeout { .. })));
1147 for contender in contenders {
1148 assert!(matches!(
1149 contender.await.unwrap(),
1150 Err(Error::Timeout { .. })
1151 ));
1152 }
1153 assert_eq!(loader_calls.load(Ordering::SeqCst), 1);
1154 }
1155
1156 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1157 async fn single_flight_retries_after_the_owner_is_cancelled() {
1158 let cache = Arc::new(LanceCache::with_capacity(4096));
1159 let loader_calls = Arc::new(AtomicUsize::new(0));
1160 let (started_tx, started_rx) = tokio::sync::oneshot::channel();
1161
1162 let owner = {
1163 let cache = cache.clone();
1164 let loader_calls = loader_calls.clone();
1165 tokio::spawn(async move {
1166 cache
1167 .get_or_insert_with_key(TestKey::new(30), move || async move {
1168 loader_calls.fetch_add(1, Ordering::SeqCst);
1169 let _ = started_tx.send(());
1170 std::future::pending::<()>().await;
1171 Ok(vec![30])
1172 })
1173 .await
1174 })
1175 };
1176 started_rx.await.unwrap();
1177
1178 let (parked_tx, parked_rx) = tokio::sync::oneshot::channel();
1179 let contender = {
1180 let cache = cache.clone();
1181 let loader_calls = loader_calls.clone();
1182 tokio::spawn(async move {
1183 report_first_pending(
1184 cache.get_or_insert_with_key(TestKey::new(30), move || async move {
1185 loader_calls.fetch_add(1, Ordering::SeqCst);
1186 Ok(vec![31])
1187 }),
1188 parked_tx,
1189 )
1190 .await
1191 })
1192 };
1193 parked_rx
1194 .await
1195 .expect("contender completed instead of parking behind owner");
1196 assert_eq!(loader_calls.load(Ordering::SeqCst), 1);
1197 assert!(!contender.is_finished());
1198
1199 owner.abort();
1200 assert!(owner.await.unwrap_err().is_cancelled());
1201 let value = tokio::time::timeout(std::time::Duration::from_secs(5), contender)
1202 .await
1203 .expect("contender remained parked after owner cancellation")
1204 .unwrap()
1205 .unwrap();
1206 assert_eq!(value.as_slice(), &[31]);
1207 assert_eq!(loader_calls.load(Ordering::SeqCst), 2);
1208 }
1209
1210 #[tokio::test]
1211 async fn expired_weak_cache_degrades_without_retaining_state() {
1212 let cache = LanceCache::with_capacity(4096);
1213 let weak = WeakLanceCache::from(&cache);
1214 drop(cache);
1215
1216 assert!(weak.get_with_key(&TestKey::new(1)).await.is_none());
1217 assert!(
1218 !weak
1219 .insert_with_key(&TestKey::new(1), Arc::new(vec![1]))
1220 .await
1221 );
1222 let value = weak
1223 .get_or_insert_with_key(TestKey::new(1), || async { Ok(vec![7]) })
1224 .await
1225 .unwrap();
1226 assert_eq!(value.as_slice(), &[7]);
1227 }
1228}