1pub mod backend;
49pub mod codec;
50mod entry_io;
51mod moka;
52
53pub use backend::{CacheBackend, CacheEntry, CacheKeyIterator, InternalCacheKey};
54pub use codec::{
55 CacheCodec, CacheCodecImpl, CacheDecode, CacheMissReason, MAGIC, has_cache_envelope,
56};
57pub use entry_io::{CacheEntryReader, CacheEntryWriter};
58pub use moka::MokaCacheBackend;
59
60use std::borrow::Cow;
61use std::sync::{
62 Arc,
63 atomic::{AtomicU64, Ordering},
64};
65
66use futures::{Future, FutureExt};
67
68use crate::Result;
69
70pub use crate::deepsize::{Context, DeepSizeOf};
71
72pub trait CacheKey {
94 type ValueType: 'static;
95
96 fn key(&self) -> Cow<'_, str>;
97
98 fn type_name() -> &'static str;
108
109 fn codec() -> Option<CacheCodec> {
118 None
119 }
120}
121
122pub trait UnsizedCacheKey {
130 type ValueType: 'static + ?Sized;
131
132 fn key(&self) -> Cow<'_, str>;
133
134 fn type_name() -> &'static str;
137}
138
139fn cache_entry_size<T: DeepSizeOf + ?Sized>(value: &T) -> usize {
145 value.deep_size_of() + std::mem::size_of::<std::sync::atomic::AtomicUsize>() * 2
146}
147
148fn build_key(prefix: &Arc<str>, key: &str, type_name: &'static str) -> InternalCacheKey {
151 InternalCacheKey::new(prefix.clone(), Arc::from(key), type_name)
152}
153
154#[derive(Clone)]
163pub struct LanceCache {
164 cache: Arc<dyn CacheBackend>,
165 prefix: Arc<str>,
166 hits: Arc<AtomicU64>,
167 misses: Arc<AtomicU64>,
168}
169
170impl std::fmt::Debug for LanceCache {
171 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172 f.debug_struct("LanceCache")
173 .field("cache", &self.cache)
174 .finish()
175 }
176}
177
178impl DeepSizeOf for LanceCache {
179 fn deep_size_of_children(&self, _: &mut Context) -> usize {
180 self.cache.approx_size_bytes()
181 }
182}
183
184impl LanceCache {
185 pub fn with_capacity(capacity: usize) -> Self {
186 Self {
187 cache: Arc::new(MokaCacheBackend::with_capacity(capacity)),
188 prefix: Arc::from(""),
189 hits: Arc::new(AtomicU64::new(0)),
190 misses: Arc::new(AtomicU64::new(0)),
191 }
192 }
193
194 pub fn with_backend(backend: Arc<dyn CacheBackend>) -> Self {
196 Self {
197 cache: backend,
198 prefix: Arc::from(""),
199 hits: Arc::new(AtomicU64::new(0)),
200 misses: Arc::new(AtomicU64::new(0)),
201 }
202 }
203
204 pub fn no_cache() -> Self {
205 Self {
206 cache: Arc::new(MokaCacheBackend::no_cache()),
207 prefix: Arc::from(""),
208 hits: Arc::new(AtomicU64::new(0)),
209 misses: Arc::new(AtomicU64::new(0)),
210 }
211 }
212
213 pub fn with_backend_and_prefix(backend: Arc<dyn CacheBackend>, prefix: String) -> Self {
216 Self {
217 cache: backend,
218 prefix: Arc::from(prefix),
219 hits: Arc::new(AtomicU64::new(0)),
220 misses: Arc::new(AtomicU64::new(0)),
221 }
222 }
223
224 pub fn with_key_prefix(&self, prefix: &str) -> Self {
226 Self {
227 cache: self.cache.clone(),
228 prefix: Arc::from(format!("{}{}/", self.prefix, prefix)),
229 hits: self.hits.clone(),
230 misses: self.misses.clone(),
231 }
232 }
233
234 pub async fn invalidate_prefix(&self, prefix: &str) {
236 let full_prefix = format!("{}{}", self.prefix, prefix);
237 self.cache.invalidate_prefix(&full_prefix).await;
238 }
239
240 pub async fn size(&self) -> usize {
241 self.cache.num_entries().await
242 }
243
244 pub fn approx_size(&self) -> usize {
245 self.cache.approx_num_entries()
246 }
247
248 pub async fn size_bytes(&self) -> usize {
249 self.cache.size_bytes().await
250 }
251
252 pub async fn keys(&self) -> Option<CacheKeyIterator<'_>> {
278 Some(Box::new(
279 self.cache
280 .keys()
281 .await?
282 .filter(|key| key.starts_with(&self.prefix)),
283 ))
284 }
285
286 async fn insert_with_id<T: DeepSizeOf + Send + Sync + 'static>(
289 &self,
290 key: &str,
291 type_name: &'static str,
292 codec: Option<CacheCodec>,
293 metadata: Arc<T>,
294 ) {
295 let size = cache_entry_size(&*metadata);
296 let cache_key = build_key(&self.prefix, key, type_name);
297 self.cache.insert(&cache_key, metadata, size, codec).await;
298 }
299
300 async fn get_with_id<T: Send + Sync + 'static>(
301 &self,
302 key: &str,
303 type_name: &'static str,
304 codec: Option<CacheCodec>,
305 ) -> Option<Arc<T>> {
306 let cache_key = build_key(&self.prefix, key, type_name);
307 if let Some(entry) = self.cache.get(&cache_key, codec).await {
308 match entry.downcast::<T>() {
309 Ok(val) => {
310 self.hits.fetch_add(1, Ordering::Relaxed);
311 Some(val)
312 }
313 Err(_) => {
314 self.misses.fetch_add(1, Ordering::Relaxed);
318 None
319 }
320 }
321 } else {
322 self.misses.fetch_add(1, Ordering::Relaxed);
323 None
324 }
325 }
326
327 pub async fn stats(&self) -> CacheStats {
330 CacheStats {
331 hits: self.hits.load(Ordering::Relaxed),
332 misses: self.misses.load(Ordering::Relaxed),
333 num_entries: self.cache.num_entries().await,
334 size_bytes: self.cache.size_bytes().await,
335 }
336 }
337
338 pub async fn clear(&self) {
339 self.cache.clear().await;
340 self.hits.store(0, Ordering::Relaxed);
341 self.misses.store(0, Ordering::Relaxed);
342 }
343
344 pub async fn insert_with_key<K>(&self, cache_key: &K, metadata: Arc<K::ValueType>)
347 where
348 K: CacheKey,
349 K::ValueType: DeepSizeOf + Send + Sync + 'static,
350 {
351 self.insert_with_id(&cache_key.key(), K::type_name(), K::codec(), metadata)
352 .boxed()
353 .await
354 }
355
356 pub async fn get_with_key<K>(&self, cache_key: &K) -> Option<Arc<K::ValueType>>
357 where
358 K: CacheKey,
359 K::ValueType: DeepSizeOf + Send + Sync + 'static,
360 {
361 self.get_with_id::<K::ValueType>(&cache_key.key(), K::type_name(), K::codec())
362 .boxed()
363 .await
364 }
365
366 pub async fn get_or_insert_with_key<K, F, Fut>(
367 &self,
368 cache_key: K,
369 loader: F,
370 ) -> Result<Arc<K::ValueType>>
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 = build_key(&self.prefix, &cache_key.key(), K::type_name());
378
379 let typed_loader = Box::pin(async move {
380 let value = loader().await?;
381 let arc = Arc::new(value);
382 let size = cache_entry_size(&*arc);
383 Ok((arc as CacheEntry, size))
384 });
385
386 let (entry, was_cached) = self
387 .cache
388 .get_or_insert(&key, typed_loader, K::codec())
389 .await?;
390
391 if was_cached {
392 self.hits.fetch_add(1, Ordering::Relaxed);
393 } else {
394 self.misses.fetch_add(1, Ordering::Relaxed);
395 }
396
397 Ok(entry.downcast::<K::ValueType>().unwrap())
398 }
399
400 pub async fn insert_unsized_with_key<K>(&self, cache_key: &K, metadata: Arc<K::ValueType>)
401 where
402 K: UnsizedCacheKey,
403 K::ValueType: DeepSizeOf + Send + Sync + 'static,
404 {
405 self.insert_with_id(&cache_key.key(), K::type_name(), None, Arc::new(metadata))
406 .boxed()
407 .await
408 }
409
410 pub async fn get_unsized_with_key<K>(&self, cache_key: &K) -> Option<Arc<K::ValueType>>
411 where
412 K: UnsizedCacheKey,
413 K::ValueType: DeepSizeOf + Send + Sync + 'static,
414 {
415 let outer = self
416 .get_with_id::<Arc<K::ValueType>>(&cache_key.key(), K::type_name(), None)
417 .boxed()
418 .await?;
419 Some(outer.as_ref().clone())
420 }
421}
422
423#[derive(Clone, Debug)]
430pub struct WeakLanceCache {
431 inner: std::sync::Weak<dyn CacheBackend>,
432 prefix: Arc<str>,
433 hits: Arc<AtomicU64>,
434 misses: Arc<AtomicU64>,
435}
436
437impl WeakLanceCache {
438 pub fn from(cache: &LanceCache) -> Self {
439 Self {
440 inner: Arc::downgrade(&cache.cache),
441 prefix: cache.prefix.clone(),
442 hits: cache.hits.clone(),
443 misses: cache.misses.clone(),
444 }
445 }
446
447 pub fn with_key_prefix(&self, prefix: &str) -> Self {
448 Self {
449 inner: self.inner.clone(),
450 prefix: Arc::from(format!("{}{}/", self.prefix, prefix)),
451 hits: self.hits.clone(),
452 misses: self.misses.clone(),
453 }
454 }
455
456 pub fn prefix(&self) -> &str {
458 &self.prefix
459 }
460
461 pub async fn get_with_key<K>(&self, cache_key: &K) -> Option<Arc<K::ValueType>>
462 where
463 K: CacheKey,
464 K::ValueType: DeepSizeOf + Send + Sync + 'static,
465 {
466 let cache = self.inner.upgrade()?;
467 let key = build_key(&self.prefix, &cache_key.key(), K::type_name());
468 if let Some(entry) = cache.get(&key, K::codec()).await {
469 self.hits.fetch_add(1, Ordering::Relaxed);
470 Some(entry.downcast::<K::ValueType>().unwrap())
471 } else {
472 self.misses.fetch_add(1, Ordering::Relaxed);
473 None
474 }
475 }
476
477 pub async fn insert_with_key<K>(&self, cache_key: &K, value: Arc<K::ValueType>) -> bool
478 where
479 K: CacheKey,
480 K::ValueType: DeepSizeOf + Send + Sync + 'static,
481 {
482 if let Some(cache) = self.inner.upgrade() {
483 let size = cache_entry_size(&*value);
484 let key = build_key(&self.prefix, &cache_key.key(), K::type_name());
485 cache.insert(&key, value, size, K::codec()).await;
486 true
487 } else {
488 log::warn!("WeakLanceCache: cache no longer available, unable to insert item");
489 false
490 }
491 }
492
493 pub async fn get_or_insert_with_key<K, F, Fut>(
497 &self,
498 cache_key: K,
499 loader: F,
500 ) -> Result<Arc<K::ValueType>>
501 where
502 K: CacheKey,
503 K::ValueType: DeepSizeOf + Send + Sync + 'static,
504 F: FnOnce() -> Fut + Send,
505 Fut: Future<Output = Result<K::ValueType>> + Send,
506 {
507 if let Some(cache) = self.inner.upgrade() {
508 let key = build_key(&self.prefix, &cache_key.key(), K::type_name());
509 let typed_loader = Box::pin(async move {
510 let value = loader().await?;
511 let arc = Arc::new(value);
512 let size = cache_entry_size(&*arc);
513 Ok((arc as CacheEntry, size))
514 });
515 let (entry, was_cached) = cache.get_or_insert(&key, typed_loader, K::codec()).await?;
516 if was_cached {
517 self.hits.fetch_add(1, Ordering::Relaxed);
518 } else {
519 self.misses.fetch_add(1, Ordering::Relaxed);
520 }
521 Ok(entry.downcast::<K::ValueType>().unwrap())
522 } else {
523 log::warn!("WeakLanceCache: cache no longer available, computing without caching");
524 loader().await.map(Arc::new)
525 }
526 }
527
528 pub async fn get_unsized_with_key<K>(&self, cache_key: &K) -> Option<Arc<K::ValueType>>
529 where
530 K: UnsizedCacheKey,
531 K::ValueType: DeepSizeOf + Send + Sync + 'static,
532 {
533 let cache = self.inner.upgrade()?;
534 let key = build_key(&self.prefix, &cache_key.key(), K::type_name());
535 if let Some(entry) = cache.get(&key, None).await {
536 entry
537 .downcast::<Arc<K::ValueType>>()
538 .ok()
539 .map(|arc| arc.as_ref().clone())
540 } else {
541 None
542 }
543 }
544
545 pub async fn insert_unsized_with_key<K>(&self, cache_key: &K, value: Arc<K::ValueType>)
546 where
547 K: UnsizedCacheKey,
548 K::ValueType: DeepSizeOf + Send + Sync + 'static,
549 {
550 if let Some(cache) = self.inner.upgrade() {
551 let wrapper = Arc::new(value);
552 let size = cache_entry_size(&*wrapper);
553 let key = build_key(&self.prefix, &cache_key.key(), K::type_name());
554 cache.insert(&key, wrapper, size, None).await;
555 } else {
556 log::warn!("WeakLanceCache: cache no longer available, unable to insert unsized item");
557 }
558 }
559}
560
561#[derive(Debug, Clone)]
566pub struct CacheStats {
567 pub hits: u64,
569 pub misses: u64,
571 pub num_entries: usize,
573 pub size_bytes: usize,
575}
576
577impl CacheStats {
578 pub fn hit_ratio(&self) -> f32 {
579 if self.hits + self.misses == 0 {
580 0.0
581 } else {
582 self.hits as f32 / (self.hits + self.misses) as f32
583 }
584 }
585
586 pub fn miss_ratio(&self) -> f32 {
587 if self.hits + self.misses == 0 {
588 0.0
589 } else {
590 self.misses as f32 / (self.hits + self.misses) as f32
591 }
592 }
593}
594
595#[cfg(test)]
596mod tests {
597 use super::*;
598 use crate::Error;
599 use std::collections::{BTreeSet, HashMap};
600 use std::marker::PhantomData;
601 use std::sync::atomic::AtomicUsize;
602
603 struct TestKey<T: 'static> {
604 key: String,
605 _phantom: PhantomData<T>,
606 }
607
608 impl<T: 'static> TestKey<T> {
609 fn new(key: &str) -> Self {
610 Self {
611 key: key.to_string(),
612 _phantom: PhantomData,
613 }
614 }
615 }
616
617 impl<T: 'static> CacheKey for TestKey<T> {
618 type ValueType = T;
619 fn key(&self) -> std::borrow::Cow<'_, str> {
620 std::borrow::Cow::Borrowed(&self.key)
621 }
622 fn type_name() -> &'static str {
623 std::any::type_name::<T>()
624 }
625 }
626
627 struct TestUnsizedKey<T: 'static + ?Sized> {
629 key: String,
630 _phantom: PhantomData<T>,
631 }
632
633 impl<T: 'static + ?Sized> TestUnsizedKey<T> {
634 fn new(key: &str) -> Self {
635 Self {
636 key: key.to_string(),
637 _phantom: PhantomData,
638 }
639 }
640 }
641
642 impl<T: 'static + ?Sized> UnsizedCacheKey for TestUnsizedKey<T> {
643 type ValueType = T;
644 fn key(&self) -> std::borrow::Cow<'_, str> {
645 std::borrow::Cow::Borrowed(&self.key)
646 }
647 fn type_name() -> &'static str {
648 std::any::type_name::<T>()
649 }
650 }
651
652 fn key_fields(keys: &[InternalCacheKey]) -> BTreeSet<(String, String, &'static str)> {
653 keys.iter()
654 .map(|key| {
655 (
656 key.prefix().to_string(),
657 key.key().to_string(),
658 key.type_name(),
659 )
660 })
661 .collect()
662 }
663
664 #[tokio::test]
665 async fn test_cache_bytes() {
666 let item = Arc::new(vec![1, 2, 3]);
667 let item_size = item.deep_size_of();
668 let capacity = 10 * item_size;
669 let cache = LanceCache::with_capacity(capacity);
670
671 cache
672 .insert_with_key(&TestKey::<Vec<i32>>::new("key"), item.clone())
673 .await;
674 assert_eq!(cache.size().await, 1);
675
676 let retrieved = cache
677 .get_with_key(&TestKey::<Vec<i32>>::new("key"))
678 .await
679 .unwrap();
680 assert_eq!(*retrieved, *item);
681
682 for i in 0..20 {
683 cache
684 .insert_with_key(
685 &TestKey::<Vec<i32>>::new(&format!("key_{}", i)),
686 Arc::new(vec![i, i, i]),
687 )
688 .await;
689 }
690 assert!(cache.size_bytes().await <= capacity);
691 }
692
693 #[tokio::test]
694 async fn test_cache_weighs_key_footprint() {
695 let cache = LanceCache::with_capacity(usize::MAX);
697 let key = "k".repeat(10_000);
698 let value = Arc::new(vec![1_i32]);
699 let expected =
700 std::mem::size_of::<InternalCacheKey>() + key.len() + cache_entry_size(&*value);
701 cache
702 .insert_with_key(&TestKey::<Vec<i32>>::new(&key), value)
703 .await;
704 assert_eq!(cache.size_bytes().await, expected);
705 }
706
707 #[tokio::test]
708 async fn test_cache_shared_prefix_not_charged_per_entry() {
709 let cache = LanceCache::with_capacity(usize::MAX).with_key_prefix(&"p".repeat(10_000));
712 for i in 0..100 {
713 cache
714 .insert_with_key(
715 &TestKey::<Vec<i32>>::new(&i.to_string()),
716 Arc::new(vec![1_i32]),
717 )
718 .await;
719 }
720 let value_cost = cache_entry_size(&vec![1_i32]);
721 let key_bytes: usize = (0..100).map(|i| i.to_string().len()).sum();
722 let expected = 100 * (std::mem::size_of::<InternalCacheKey>() + value_cost) + key_bytes;
723 assert_eq!(cache.size_bytes().await, expected);
724 }
725
726 #[tokio::test]
727 async fn test_cache_trait_objects() {
728 #[derive(Debug, DeepSizeOf)]
729 struct MyType(i32);
730
731 trait MyTrait: DeepSizeOf + Send + Sync + std::any::Any {
732 fn as_any(&self) -> &dyn std::any::Any;
733 }
734
735 impl MyTrait for MyType {
736 fn as_any(&self) -> &dyn std::any::Any {
737 self
738 }
739 }
740
741 let item: Arc<dyn MyTrait> = Arc::new(MyType(42));
742 let cache = LanceCache::with_capacity(1000);
743 cache
744 .insert_unsized_with_key(&TestUnsizedKey::<dyn MyTrait>::new("test"), item)
745 .await;
746
747 let retrieved = cache
748 .get_unsized_with_key(&TestUnsizedKey::<dyn MyTrait>::new("test"))
749 .await
750 .unwrap();
751 assert_eq!(retrieved.as_any().downcast_ref::<MyType>().unwrap().0, 42);
752 }
753
754 #[tokio::test]
755 async fn test_cache_stats_basic() {
756 let cache = LanceCache::with_capacity(1000);
757 assert_eq!(cache.stats().await.hits, 0);
758
759 assert!(
761 cache
762 .get_with_key(&TestKey::<Vec<i32>>::new("x"))
763 .await
764 .is_none()
765 );
766 assert_eq!(cache.stats().await.misses, 1);
767
768 cache
770 .insert_with_key(&TestKey::new("k"), Arc::new(vec![1, 2, 3]))
771 .await;
772 assert!(
773 cache
774 .get_with_key(&TestKey::<Vec<i32>>::new("k"))
775 .await
776 .is_some()
777 );
778 assert_eq!(cache.stats().await.hits, 1);
779 }
780
781 #[tokio::test]
782 async fn test_cache_stats_with_prefixes() {
783 let base = LanceCache::with_capacity(1000);
784 let prefixed = base.with_key_prefix("ns");
785
786 assert!(
787 prefixed
788 .get_with_key(&TestKey::<Vec<i32>>::new("k"))
789 .await
790 .is_none()
791 );
792 assert_eq!(base.stats().await.misses, 1);
793
794 prefixed
795 .insert_with_key(&TestKey::new("k"), Arc::new(vec![1]))
796 .await;
797 assert!(
798 prefixed
799 .get_with_key(&TestKey::<Vec<i32>>::new("k"))
800 .await
801 .is_some()
802 );
803 assert_eq!(base.stats().await.hits, 1);
804 }
805
806 #[tokio::test]
807 async fn test_cache_keys_with_prefixes() {
808 let base = LanceCache::with_capacity(1000);
809 let prefixed = base.with_key_prefix("ns");
810 let nested = prefixed.with_key_prefix("index");
811 let other = base.with_key_prefix("ns-other");
812
813 base.insert_with_key(&TestKey::new("root"), Arc::new(vec![0]))
814 .await;
815 prefixed
816 .insert_with_key(&TestKey::new("child"), Arc::new(vec![1]))
817 .await;
818 nested
819 .insert_with_key(&TestKey::new("nested"), Arc::new(vec![2]))
820 .await;
821 other
822 .insert_with_key(&TestKey::new("other"), Arc::new(vec![3]))
823 .await;
824
825 let base_keys = base.keys().await.unwrap().collect::<Vec<_>>();
826 assert_eq!(
827 key_fields(&base_keys),
828 BTreeSet::from([
829 (
830 "".to_string(),
831 "root".to_string(),
832 TestKey::<Vec<i32>>::type_name()
833 ),
834 (
835 "ns/".to_string(),
836 "child".to_string(),
837 TestKey::<Vec<i32>>::type_name()
838 ),
839 (
840 "ns/index/".to_string(),
841 "nested".to_string(),
842 TestKey::<Vec<i32>>::type_name()
843 ),
844 (
845 "ns-other/".to_string(),
846 "other".to_string(),
847 TestKey::<Vec<i32>>::type_name()
848 ),
849 ])
850 );
851
852 let prefixed_keys = prefixed.keys().await.unwrap().collect::<Vec<_>>();
853 assert_eq!(
854 key_fields(&prefixed_keys),
855 BTreeSet::from([
856 (
857 "ns/".to_string(),
858 "child".to_string(),
859 TestKey::<Vec<i32>>::type_name()
860 ),
861 (
862 "ns/index/".to_string(),
863 "nested".to_string(),
864 TestKey::<Vec<i32>>::type_name()
865 ),
866 ])
867 );
868 }
869
870 #[tokio::test]
871 async fn test_cache_keys_reflect_invalidation_and_clear() {
872 let base = LanceCache::with_capacity(1000);
873 let prefixed = base.with_key_prefix("ns");
874 let other = base.with_key_prefix("other");
875
876 prefixed
877 .insert_with_key(&TestKey::new("child"), Arc::new(vec![1]))
878 .await;
879 other
880 .insert_with_key(&TestKey::new("other"), Arc::new(vec![2]))
881 .await;
882 assert_eq!(base.keys().await.unwrap().count(), 2);
883
884 prefixed.invalidate_prefix("").await;
885 let keys = base.keys().await.unwrap().collect::<Vec<_>>();
886 assert_eq!(
887 key_fields(&keys),
888 BTreeSet::from([(
889 "other/".to_string(),
890 "other".to_string(),
891 TestKey::<Vec<i32>>::type_name()
892 )])
893 );
894
895 base.clear().await;
896 assert_eq!(base.keys().await.unwrap().count(), 0);
897 }
898
899 #[tokio::test]
900 async fn test_cache_get_or_insert() {
901 let cache = LanceCache::with_capacity(1000);
902
903 let v: Arc<Vec<i32>> = cache
904 .get_or_insert_with_key(TestKey::<Vec<i32>>::new("k"), || async {
905 Ok(vec![1, 2, 3])
906 })
907 .await
908 .unwrap();
909 assert_eq!(*v, vec![1, 2, 3]);
910 assert_eq!(cache.stats().await.misses, 1);
911 assert_eq!(cache.stats().await.hits, 0);
912
913 let v: Arc<Vec<i32>> = cache
915 .get_or_insert_with_key(TestKey::<Vec<i32>>::new("k"), || async {
916 panic!("should not be called")
917 })
918 .await
919 .unwrap();
920 assert_eq!(*v, vec![1, 2, 3]);
921 assert_eq!(cache.stats().await.hits, 1);
922 }
923
924 #[tokio::test]
925 async fn test_cache_coalesces_concurrent_loader_errors() {
926 let cache = LanceCache::with_capacity(1000);
927 let barrier = Arc::new(tokio::sync::Barrier::new(5));
928 let loader_calls = Arc::new(AtomicUsize::new(0));
929
930 let results = futures::future::join_all((0..5).map(|_| {
931 let cache = cache.clone();
932 let barrier = barrier.clone();
933 let loader_calls = loader_calls.clone();
934 async move {
935 barrier.wait().await;
936 cache
937 .get_or_insert_with_key(TestKey::<Vec<i32>>::new("error"), || async move {
938 loader_calls.fetch_add(1, Ordering::Relaxed);
939 tokio::time::sleep(std::time::Duration::from_millis(50)).await;
940 Err(Error::timeout("metadata read timed out"))
941 })
942 .await
943 }
944 }))
945 .await;
946
947 assert_eq!(loader_calls.load(Ordering::Relaxed), 1);
948 assert!(
949 results
950 .iter()
951 .all(|result| matches!(result, Err(Error::Timeout { .. })))
952 );
953 }
954
955 #[tokio::test]
956 async fn test_custom_backend() {
957 use async_trait::async_trait;
958 use tokio::sync::Mutex;
959
960 #[derive(Debug)]
961 struct HashMapBackend {
962 map: Mutex<HashMap<InternalCacheKey, (CacheEntry, usize)>>,
963 }
964
965 impl HashMapBackend {
966 fn new() -> Self {
967 Self {
968 map: Mutex::new(HashMap::new()),
969 }
970 }
971 }
972
973 #[async_trait]
974 impl CacheBackend for HashMapBackend {
975 async fn get(
976 &self,
977 key: &InternalCacheKey,
978 _codec: Option<CacheCodec>,
979 ) -> Option<CacheEntry> {
980 self.map.lock().await.get(key).map(|(e, _)| e.clone())
981 }
982 async fn insert(
983 &self,
984 key: &InternalCacheKey,
985 entry: CacheEntry,
986 size_bytes: usize,
987 _codec: Option<CacheCodec>,
988 ) {
989 self.map
990 .lock()
991 .await
992 .insert(key.clone(), (entry, size_bytes));
993 }
994 async fn get_or_insert<'a>(
995 &self,
996 key: &InternalCacheKey,
997 loader: std::pin::Pin<
998 Box<dyn futures::Future<Output = Result<(CacheEntry, usize)>> + Send + 'a>,
999 >,
1000 _codec: Option<CacheCodec>,
1001 ) -> Result<(CacheEntry, bool)> {
1002 if let Some((entry, _)) = self.map.lock().await.get(key) {
1003 Ok((entry.clone(), true))
1004 } else {
1005 let (entry, size) = loader.await?;
1006 self.map
1007 .lock()
1008 .await
1009 .insert(key.clone(), (entry.clone(), size));
1010 Ok((entry, false))
1011 }
1012 }
1013 async fn invalidate_prefix(&self, prefix: &str) {
1014 self.map.lock().await.retain(|k, _| !k.starts_with(prefix));
1015 }
1016 async fn clear(&self) {
1017 self.map.lock().await.clear();
1018 }
1019 async fn num_entries(&self) -> usize {
1020 self.map.lock().await.len()
1021 }
1022 async fn size_bytes(&self) -> usize {
1023 self.map.lock().await.values().map(|(_, s)| *s).sum()
1024 }
1025 }
1026
1027 let cache = LanceCache::with_backend(Arc::new(HashMapBackend::new()));
1028
1029 cache
1030 .insert_with_key(&TestKey::new("k"), Arc::new(vec![1, 2, 3]))
1031 .await;
1032 assert!(
1033 cache
1034 .get_with_key(&TestKey::<Vec<i32>>::new("k"))
1035 .await
1036 .is_some()
1037 );
1038 assert!(
1040 cache
1041 .get_with_key(&TestKey::<Vec<u8>>::new("k"))
1042 .await
1043 .is_none()
1044 );
1045 assert!(cache.keys().await.is_none());
1046 }
1047
1048 #[tokio::test]
1049 async fn test_get_or_insert_dedup() {
1050 use std::sync::atomic::AtomicUsize;
1051
1052 let load_count = Arc::new(AtomicUsize::new(0));
1053 let cache = LanceCache::with_capacity(10000);
1054
1055 let (barrier_tx, _) = tokio::sync::broadcast::channel::<()>(1);
1056 let mut handles = Vec::new();
1057 for _ in 0..5 {
1058 let cache = cache.clone();
1059 let load_count = load_count.clone();
1060 let mut barrier_rx = barrier_tx.subscribe();
1061 handles.push(tokio::spawn(async move {
1062 barrier_rx.recv().await.ok();
1063 cache
1064 .get_or_insert_with_key(TestKey::<Vec<i32>>::new("key"), || {
1065 let load_count = load_count.clone();
1066 async move {
1067 load_count.fetch_add(1, Ordering::SeqCst);
1068 tokio::task::yield_now().await;
1069 Ok(vec![1, 2, 3])
1070 }
1071 })
1072 .await
1073 }));
1074 }
1075 barrier_tx.send(()).unwrap();
1076 for h in handles {
1077 let result: Arc<Vec<i32>> = h.await.unwrap().unwrap();
1078 assert_eq!(*result, vec![1, 2, 3]);
1079 }
1080
1081 assert_eq!(load_count.load(Ordering::SeqCst), 1);
1082 }
1083}