1pub mod backend;
49mod backend_uri;
50pub mod codec;
51mod entry_io;
52mod key;
53mod moka;
54mod quick;
55mod registry;
56
57pub use backend::{CacheBackend, CacheEntry};
58pub use backend_uri::{build_from_uri, parse_backend_uri};
59pub use codec::{
60 CacheCodec, CacheCodecImpl, CacheDecode, CacheMissReason, MAGIC, has_cache_envelope,
61};
62pub use entry_io::{CacheEntryReader, CacheEntryWriter};
63pub use key::{CACHE_KEY_FORMAT, CacheKeySchema, CacheNamespace, InternalCacheKey, KeyBuilder};
64pub use moka::MokaCacheBackend;
65pub use quick::{QuickCacheBackend, QuickCacheShardPolicy, recommended_cache_shards};
66pub use registry::{BackendBuildFn, BackendConfig, build_from_config, register_backend};
67
68use std::any::TypeId;
69use std::borrow::Cow;
70use std::collections::HashMap;
71use std::sync::{
72 Arc, RwLock, Weak,
73 atomic::{AtomicU64, Ordering},
74};
75
76use futures::Future;
77
78use crate::{Error, Result};
79
80pub use crate::deepsize::{Context, DeepSizeOf};
81
82pub trait CacheKey {
105 type ValueType: 'static;
106
107 fn key(&self) -> Cow<'_, str>;
108
109 fn type_name() -> &'static str;
118
119 fn stable_type_id() -> &'static str {
124 Self::type_name()
125 }
126
127 fn schema() -> CacheKeySchema {
129 CacheKeySchema::LEGACY_TEXT
130 }
131
132 fn write_key(&self, builder: &mut KeyBuilder) {
137 builder.write_str(self.key().as_ref());
138 }
139
140 fn codec() -> Option<CacheCodec> {
149 None
150 }
151}
152
153pub trait UnsizedCacheKey {
161 type ValueType: 'static + ?Sized;
162
163 fn key(&self) -> Cow<'_, str>;
164
165 fn type_name() -> &'static str;
168
169 fn stable_type_id() -> &'static str {
171 Self::type_name()
172 }
173
174 fn schema() -> CacheKeySchema {
176 CacheKeySchema::LEGACY_TEXT
177 }
178
179 fn write_key(&self, builder: &mut KeyBuilder) {
181 builder.write_str(self.key().as_ref());
182 }
183}
184
185fn cache_entry_size<T: DeepSizeOf + ?Sized>(value: &T) -> usize {
191 value.deep_size_of() + std::mem::size_of::<std::sync::atomic::AtomicUsize>() * 2
192}
193
194type CacheEntrySizeAccessor = fn(&CacheEntry, &mut Context) -> Option<usize>;
195
196fn cache_entry_size_with_context<T>(entry: &CacheEntry, context: &mut Context) -> Option<usize>
197where
198 T: DeepSizeOf + Send + Sync + 'static,
199{
200 let value = entry.downcast_ref::<T>()?;
201 let entry_ptr = Arc::as_ptr(entry) as *const () as usize;
202 if !context.mark_seen(entry_ptr) {
203 return Some(0);
204 }
205 Some(
206 std::mem::size_of_val(value)
207 + value.deep_size_of_children(context)
208 + std::mem::size_of::<std::sync::atomic::AtomicUsize>() * 2,
209 )
210}
211
212#[derive(Debug)]
213struct CacheState {
214 backend: Arc<dyn CacheBackend>,
215 hits: AtomicU64,
216 misses: AtomicU64,
217 entry_size_accessors: RwLock<HashMap<TypeId, CacheEntrySizeAccessor>>,
218}
219
220impl CacheState {
221 fn new(backend: Arc<dyn CacheBackend>) -> Self {
222 Self {
223 backend,
224 hits: AtomicU64::new(0),
225 misses: AtomicU64::new(0),
226 entry_size_accessors: RwLock::new(HashMap::new()),
227 }
228 }
229
230 fn entry_size<T>(&self, value: &T) -> usize
231 where
232 T: DeepSizeOf + Send + Sync + 'static,
233 {
234 let type_id = TypeId::of::<T>();
235 let is_registered = self
236 .entry_size_accessors
237 .read()
238 .unwrap_or_else(|poisoned| poisoned.into_inner())
239 .contains_key(&type_id);
240 if !is_registered {
241 self.entry_size_accessors
242 .write()
243 .unwrap_or_else(|poisoned| poisoned.into_inner())
244 .entry(type_id)
245 .or_insert(cache_entry_size_with_context::<T>);
246 }
247 cache_entry_size(value)
248 }
249}
250
251#[derive(Clone)]
260pub struct LanceCache {
261 state: Arc<CacheState>,
262 namespace: key::CacheNamespace,
263}
264
265impl std::fmt::Debug for LanceCache {
266 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
267 f.debug_struct("LanceCache")
268 .field("backend", &self.state.backend)
269 .finish_non_exhaustive()
270 }
271}
272
273impl DeepSizeOf for LanceCache {
274 fn deep_size_of_children(&self, context: &mut Context) -> usize {
275 let state_ptr = Arc::as_ptr(&self.state) as usize;
276 if !context.mark_seen(state_ptr) {
277 return 0;
278 }
279
280 let accessors = self
281 .state
282 .entry_size_accessors
283 .read()
284 .unwrap_or_else(|poisoned| poisoned.into_inner())
285 .clone();
286 self.state
287 .backend
288 .deep_size_of_entries(context, &|entry, context| {
289 accessors
290 .get(&entry.as_ref().type_id())
291 .and_then(|size_of_entry| size_of_entry(entry, context))
292 })
293 .unwrap_or_else(|| self.state.backend.approx_size_bytes())
294 }
295}
296
297impl LanceCache {
298 pub fn with_capacity(capacity: usize) -> Self {
299 Self::with_backend(Arc::new(MokaCacheBackend::with_capacity(capacity)))
300 }
301
302 pub fn with_backend(backend: Arc<dyn CacheBackend>) -> Self {
304 Self {
305 state: Arc::new(CacheState::new(backend)),
306 namespace: key::CacheNamespace::root(),
307 }
308 }
309
310 pub fn no_cache() -> Self {
311 Self::with_backend(Arc::new(MokaCacheBackend::no_cache()))
312 }
313
314 pub fn with_key_prefix(&self, prefix: &str) -> Self {
320 Self {
321 state: self.state.clone(),
322 namespace: self.namespace.child(prefix),
323 }
324 }
325
326 pub async fn size(&self) -> usize {
327 self.state.backend.num_entries().await
328 }
329
330 pub fn approx_size(&self) -> usize {
331 self.state.backend.approx_num_entries()
332 }
333
334 pub async fn size_bytes(&self) -> usize {
335 self.state.backend.size_bytes().await
336 }
337
338 pub fn capacity_bytes(&self) -> Option<usize> {
340 self.state.backend.capacity_bytes()
341 }
342
343 pub async fn stats(&self) -> CacheStats {
346 CacheStats {
347 hits: self.state.hits.load(Ordering::Relaxed),
348 misses: self.state.misses.load(Ordering::Relaxed),
349 num_entries: self.state.backend.num_entries().await,
350 size_bytes: self.state.backend.size_bytes().await,
351 }
352 }
353
354 pub async fn clear(&self) {
355 self.state.backend.clear().await;
356 self.state.hits.store(0, Ordering::Relaxed);
357 self.state.misses.store(0, Ordering::Relaxed);
358 }
359
360 pub async fn insert_with_key<K>(&self, cache_key: &K, metadata: Arc<K::ValueType>)
363 where
364 K: CacheKey,
365 K::ValueType: DeepSizeOf + Send + Sync + 'static,
366 {
367 let size = self.state.entry_size(metadata.as_ref());
368 let key = self.sized_key(cache_key);
369 self.state
370 .backend
371 .insert(&key, metadata, size, K::codec())
372 .await;
373 }
374
375 pub async fn get_with_key<K>(&self, cache_key: &K) -> Option<Arc<K::ValueType>>
376 where
377 K: CacheKey,
378 K::ValueType: DeepSizeOf + Send + Sync + 'static,
379 {
380 let key = self.sized_key(cache_key);
381 let Some(entry) = self.state.backend.get(&key, K::codec()).await else {
382 self.state.misses.fetch_add(1, Ordering::Relaxed);
383 return None;
384 };
385 match entry.downcast::<K::ValueType>() {
386 Ok(value) => {
387 self.state.hits.fetch_add(1, Ordering::Relaxed);
388 Some(value)
389 }
390 Err(_) => {
391 log::warn!(
395 "cache backend returned a value with the wrong concrete type for key type {:?}",
396 K::stable_type_id()
397 );
398 self.state.misses.fetch_add(1, Ordering::Relaxed);
399 None
400 }
401 }
402 }
403
404 pub async fn get_or_insert_with_key<K, F, Fut>(
405 &self,
406 cache_key: K,
407 loader: F,
408 ) -> Result<Arc<K::ValueType>>
409 where
410 K: CacheKey,
411 K::ValueType: DeepSizeOf + Send + Sync + 'static,
412 F: FnOnce() -> Fut + Send,
413 Fut: Future<Output = Result<K::ValueType>> + Send,
414 {
415 self.get_or_insert_with_key_hit(cache_key, loader)
416 .await
417 .map(|(value, _)| value)
418 }
419
420 pub async fn get_or_insert_with_key_hit<K, F, Fut>(
436 &self,
437 cache_key: K,
438 loader: F,
439 ) -> Result<(Arc<K::ValueType>, bool)>
440 where
441 K: CacheKey,
442 K::ValueType: DeepSizeOf + Send + Sync + 'static,
443 F: FnOnce() -> Fut + Send,
444 Fut: Future<Output = Result<K::ValueType>> + Send,
445 {
446 let key = self.sized_key(&cache_key);
447 let state = self.state.clone();
448 let typed_loader = Box::pin(async move {
449 let value = Arc::new(loader().await?);
450 let size = state.entry_size(value.as_ref());
451 Ok((value as CacheEntry, size))
452 });
453
454 let (entry, was_cached) = self
455 .state
456 .backend
457 .get_or_insert(&key, typed_loader, K::codec())
458 .await?;
459 let entry = entry.downcast::<K::ValueType>().map_err(|_| {
460 self.state.misses.fetch_add(1, Ordering::Relaxed);
461 Error::io(format!(
462 "cache backend returned a value with the wrong concrete type for key type {:?}",
463 K::stable_type_id()
464 ))
465 })?;
466 if was_cached {
467 self.state.hits.fetch_add(1, Ordering::Relaxed);
468 } else {
469 self.state.misses.fetch_add(1, Ordering::Relaxed);
470 }
471 Ok((entry, was_cached))
472 }
473
474 pub async fn insert_unsized_with_key<K>(&self, cache_key: &K, metadata: Arc<K::ValueType>)
475 where
476 K: UnsizedCacheKey,
477 K::ValueType: DeepSizeOf + Send + Sync + 'static,
478 {
479 let metadata = Arc::new(metadata);
480 let size = self.state.entry_size(metadata.as_ref());
481 let key = self.unsized_key(cache_key);
482 self.state.backend.insert(&key, metadata, size, None).await;
483 }
484
485 pub async fn get_or_insert_unsized_with_key<K, F, Fut>(
486 &self,
487 cache_key: K,
488 loader: F,
489 ) -> Result<Arc<K::ValueType>>
490 where
491 K: UnsizedCacheKey,
492 K::ValueType: DeepSizeOf + Send + Sync + 'static,
493 F: FnOnce() -> Fut + Send,
494 Fut: Future<Output = Result<Arc<K::ValueType>>> + Send,
495 {
496 let key = self.unsized_key(&cache_key);
497 let state = self.state.clone();
498 let typed_loader = Box::pin(async move {
499 let value = loader().await?;
500 let size = state.entry_size(&value);
501 Ok((Arc::new(value) as CacheEntry, size))
502 });
503
504 let (entry, was_cached) = self
505 .state
506 .backend
507 .get_or_insert(&key, typed_loader, None)
508 .await?;
509 let entry = entry.downcast::<Arc<K::ValueType>>().map_err(|_| {
510 self.state.misses.fetch_add(1, Ordering::Relaxed);
511 Error::io(format!(
512 "cache backend returned a value with the wrong concrete type for unsized key type {:?}",
513 K::stable_type_id()
514 ))
515 })?;
516 if was_cached {
517 self.state.hits.fetch_add(1, Ordering::Relaxed);
518 } else {
519 self.state.misses.fetch_add(1, Ordering::Relaxed);
520 }
521 Ok(entry.as_ref().clone())
522 }
523
524 pub async fn get_unsized_with_key<K>(&self, cache_key: &K) -> Option<Arc<K::ValueType>>
525 where
526 K: UnsizedCacheKey,
527 K::ValueType: DeepSizeOf + Send + Sync + 'static,
528 {
529 let key = self.unsized_key(cache_key);
530 let Some(entry) = self.state.backend.get(&key, None).await else {
531 self.state.misses.fetch_add(1, Ordering::Relaxed);
532 return None;
533 };
534 match entry.downcast::<Arc<K::ValueType>>() {
535 Ok(value) => {
536 self.state.hits.fetch_add(1, Ordering::Relaxed);
537 Some(value.as_ref().clone())
538 }
539 Err(_) => {
540 log::warn!(
544 "cache backend returned a value with the wrong concrete type for unsized key type {:?}",
545 K::stable_type_id()
546 );
547 self.state.misses.fetch_add(1, Ordering::Relaxed);
548 None
549 }
550 }
551 }
552
553 fn sized_key<K: CacheKey>(&self, cache_key: &K) -> InternalCacheKey {
554 let mut builder = KeyBuilder::new(self.namespace, K::stable_type_id(), K::schema());
555 cache_key.write_key(&mut builder);
556 builder.finish()
557 }
558
559 fn unsized_key<K: UnsizedCacheKey>(&self, cache_key: &K) -> InternalCacheKey {
560 let mut builder = KeyBuilder::new(self.namespace, K::stable_type_id(), K::schema());
561 cache_key.write_key(&mut builder);
562 builder.finish()
563 }
564}
565
566#[derive(Clone, Debug)]
573pub struct WeakLanceCache {
574 state: Weak<CacheState>,
575 namespace: key::CacheNamespace,
576}
577
578impl WeakLanceCache {
579 pub fn from(cache: &LanceCache) -> Self {
580 Self {
581 state: Arc::downgrade(&cache.state),
582 namespace: cache.namespace,
583 }
584 }
585
586 pub fn with_key_prefix(&self, prefix: &str) -> Self {
587 Self {
588 state: self.state.clone(),
589 namespace: self.namespace.child(prefix),
590 }
591 }
592
593 pub fn capacity_bytes(&self) -> Option<usize> {
596 self.upgrade()?.capacity_bytes()
597 }
598
599 pub async fn get_with_key<K>(&self, cache_key: &K) -> Option<Arc<K::ValueType>>
600 where
601 K: CacheKey,
602 K::ValueType: DeepSizeOf + Send + Sync + 'static,
603 {
604 self.upgrade()?.get_with_key(cache_key).await
605 }
606
607 pub async fn insert_with_key<K>(&self, cache_key: &K, value: Arc<K::ValueType>) -> bool
608 where
609 K: CacheKey,
610 K::ValueType: DeepSizeOf + Send + Sync + 'static,
611 {
612 let Some(cache) = self.upgrade() else {
613 log::warn!("WeakLanceCache: cache no longer available, unable to insert item");
614 return false;
615 };
616 cache.insert_with_key(cache_key, value).await;
617 true
618 }
619
620 pub async fn get_or_insert_with_key<K, F, Fut>(
624 &self,
625 cache_key: K,
626 loader: F,
627 ) -> Result<Arc<K::ValueType>>
628 where
629 K: CacheKey,
630 K::ValueType: DeepSizeOf + Send + Sync + 'static,
631 F: FnOnce() -> Fut + Send,
632 Fut: Future<Output = Result<K::ValueType>> + Send,
633 {
634 self.get_or_insert_with_key_hit(cache_key, loader)
635 .await
636 .map(|(value, _)| value)
637 }
638
639 pub async fn get_or_insert_with_key_hit<K, F, Fut>(
644 &self,
645 cache_key: K,
646 loader: F,
647 ) -> Result<(Arc<K::ValueType>, bool)>
648 where
649 K: CacheKey,
650 K::ValueType: DeepSizeOf + Send + Sync + 'static,
651 F: FnOnce() -> Fut + Send,
652 Fut: Future<Output = Result<K::ValueType>> + Send,
653 {
654 let Some(cache) = self.upgrade() else {
655 log::warn!("WeakLanceCache: cache no longer available, computing without caching");
656 return loader().await.map(|value| (Arc::new(value), false));
657 };
658 cache.get_or_insert_with_key_hit(cache_key, loader).await
659 }
660
661 pub async fn get_unsized_with_key<K>(&self, cache_key: &K) -> Option<Arc<K::ValueType>>
662 where
663 K: UnsizedCacheKey,
664 K::ValueType: DeepSizeOf + Send + Sync + 'static,
665 {
666 self.upgrade()?.get_unsized_with_key(cache_key).await
667 }
668
669 pub async fn insert_unsized_with_key<K>(&self, cache_key: &K, value: Arc<K::ValueType>)
670 where
671 K: UnsizedCacheKey,
672 K::ValueType: DeepSizeOf + Send + Sync + 'static,
673 {
674 let Some(cache) = self.upgrade() else {
675 log::warn!("WeakLanceCache: cache no longer available, unable to insert unsized item");
676 return;
677 };
678 cache.insert_unsized_with_key(cache_key, value).await;
679 }
680
681 fn upgrade(&self) -> Option<LanceCache> {
682 Some(LanceCache {
683 state: self.state.upgrade()?,
684 namespace: self.namespace,
685 })
686 }
687}
688
689#[derive(Debug, Clone)]
694pub struct CacheStats {
695 pub hits: u64,
697 pub misses: u64,
699 pub num_entries: usize,
701 pub size_bytes: usize,
703}
704
705impl CacheStats {
706 pub fn hit_ratio(&self) -> f32 {
707 if self.hits + self.misses == 0 {
708 0.0
709 } else {
710 self.hits as f32 / (self.hits + self.misses) as f32
711 }
712 }
713
714 pub fn miss_ratio(&self) -> f32 {
715 if self.hits + self.misses == 0 {
716 0.0
717 } else {
718 self.misses as f32 / (self.hits + self.misses) as f32
719 }
720 }
721}
722
723#[cfg(test)]
724mod tests {
725 use std::collections::HashMap;
726 use std::pin::Pin;
727 use std::sync::{
728 atomic::{AtomicUsize, Ordering},
729 mpsc,
730 };
731 use std::task::Poll;
732 use std::thread;
733 use std::time::Duration;
734
735 use super::*;
736
737 async fn report_first_pending<F>(
738 future: F,
739 parked: tokio::sync::oneshot::Sender<()>,
740 ) -> F::Output
741 where
742 F: Future,
743 {
744 tokio::pin!(future);
745 let mut parked = Some(parked);
746 futures::future::poll_fn(|cx| match future.as_mut().poll(cx) {
747 Poll::Pending => {
748 if let Some(parked) = parked.take() {
749 let _ = parked.send(());
750 }
751 Poll::Pending
752 }
753 Poll::Ready(output) => Poll::Ready(output),
754 })
755 .await
756 }
757
758 #[derive(Clone)]
759 struct VersionedTestKey<const SCHEMA_VERSION: u32> {
760 id: u64,
761 }
762
763 type TestKey = VersionedTestKey<1>;
764 type TestKeyV2 = VersionedTestKey<2>;
765
766 impl<const SCHEMA_VERSION: u32> VersionedTestKey<SCHEMA_VERSION> {
767 fn new(id: u64) -> Self {
768 Self { id }
769 }
770 }
771
772 impl<const SCHEMA_VERSION: u32> CacheKey for VersionedTestKey<SCHEMA_VERSION> {
773 type ValueType = Vec<u32>;
774
775 fn key(&self) -> Cow<'_, str> {
776 self.id.to_string().into()
777 }
778
779 fn type_name() -> &'static str {
780 "test.VecU32"
781 }
782
783 fn schema() -> CacheKeySchema {
784 CacheKeySchema::new("test.vec-u32-key", SCHEMA_VERSION)
785 }
786
787 fn write_key(&self, builder: &mut KeyBuilder) {
788 builder.write_u64(self.id);
789 }
790 }
791
792 struct SharedTestValue {
793 data: Arc<Vec<u8>>,
794 }
795
796 impl DeepSizeOf for SharedTestValue {
797 fn deep_size_of_children(&self, context: &mut Context) -> usize {
798 self.data.deep_size_of_children(context)
799 }
800 }
801
802 struct SharedTestKey(u64);
803
804 impl CacheKey for SharedTestKey {
805 type ValueType = SharedTestValue;
806
807 fn key(&self) -> Cow<'_, str> {
808 self.0.to_string().into()
809 }
810
811 fn type_name() -> &'static str {
812 "test.SharedValue"
813 }
814
815 fn schema() -> CacheKeySchema {
816 CacheKeySchema::new("test.shared-value-key", 1)
817 }
818
819 fn write_key(&self, builder: &mut KeyBuilder) {
820 builder.write_u64(self.0);
821 }
822 }
823
824 struct ReentrantValue(LanceCache);
825
826 impl DeepSizeOf for ReentrantValue {
827 fn deep_size_of_children(&self, context: &mut Context) -> usize {
828 self.0.deep_size_of_children(context)
829 }
830 }
831
832 struct ReentrantKey;
833
834 impl CacheKey for ReentrantKey {
835 type ValueType = ReentrantValue;
836
837 fn key(&self) -> Cow<'_, str> {
838 Cow::Borrowed("reentrant")
839 }
840
841 fn type_name() -> &'static str {
842 "test.ReentrantValue"
843 }
844 }
845
846 #[derive(Clone, Copy, Debug)]
847 enum TestBackendKind {
848 Moka,
849 Quick,
850 }
851
852 impl TestBackendKind {
853 fn cache(self, capacity: usize) -> LanceCache {
854 match self {
855 Self::Moka => LanceCache::with_capacity(capacity),
856 Self::Quick => {
857 LanceCache::with_backend(Arc::new(QuickCacheBackend::with_capacity(capacity)))
858 }
859 }
860 }
861 }
862
863 struct LegacyBridgeKey(&'static str);
864
865 impl CacheKey for LegacyBridgeKey {
866 type ValueType = Vec<u32>;
867
868 fn key(&self) -> Cow<'_, str> {
869 Cow::Borrowed(self.0)
870 }
871
872 fn type_name() -> &'static str {
873 "test.LegacyBridge"
874 }
875 }
876
877 struct ExplicitBridgeKey(&'static str);
878
879 impl CacheKey for ExplicitBridgeKey {
880 type ValueType = Vec<u32>;
881
882 fn key(&self) -> Cow<'_, str> {
883 Cow::Borrowed(self.0)
884 }
885
886 fn type_name() -> &'static str {
887 "test.LegacyBridge"
888 }
889
890 fn write_key(&self, builder: &mut KeyBuilder) {
891 builder.write_str(self.0);
892 }
893 }
894
895 trait TestDynValue: DeepSizeOf + Send + Sync {
896 fn values(&self) -> &[u32];
897 }
898
899 impl TestDynValue for Vec<u32> {
900 fn values(&self) -> &[u32] {
901 self
902 }
903 }
904
905 struct LegacyUnsizedBridgeKey(&'static str);
906
907 impl UnsizedCacheKey for LegacyUnsizedBridgeKey {
908 type ValueType = dyn TestDynValue;
909
910 fn key(&self) -> Cow<'_, str> {
911 Cow::Borrowed(self.0)
912 }
913
914 fn type_name() -> &'static str {
915 "test.LegacyUnsizedBridge"
916 }
917 }
918
919 struct ExplicitUnsizedBridgeKey(&'static str);
920
921 impl UnsizedCacheKey for ExplicitUnsizedBridgeKey {
922 type ValueType = dyn TestDynValue;
923
924 fn key(&self) -> Cow<'_, str> {
925 Cow::Borrowed(self.0)
926 }
927
928 fn type_name() -> &'static str {
929 "test.LegacyUnsizedBridge"
930 }
931
932 fn write_key(&self, builder: &mut KeyBuilder) {
933 builder.write_str(self.0);
934 }
935 }
936
937 #[derive(Debug, Default)]
938 struct HashMapBackend {
939 entries: tokio::sync::Mutex<HashMap<InternalCacheKey, (CacheEntry, usize)>>,
940 }
941
942 #[async_trait::async_trait]
943 impl CacheBackend for HashMapBackend {
944 async fn get(
945 &self,
946 key: &InternalCacheKey,
947 _codec: Option<CacheCodec>,
948 ) -> Option<CacheEntry> {
949 self.entries
950 .lock()
951 .await
952 .get(key)
953 .map(|(entry, _)| entry.clone())
954 }
955
956 async fn insert(
957 &self,
958 key: &InternalCacheKey,
959 entry: CacheEntry,
960 size_bytes: usize,
961 _codec: Option<CacheCodec>,
962 ) {
963 self.entries.lock().await.insert(*key, (entry, size_bytes));
964 }
965
966 async fn get_or_insert<'a>(
967 &self,
968 key: &InternalCacheKey,
969 loader: Pin<Box<dyn Future<Output = Result<(CacheEntry, usize)>> + Send + 'a>>,
970 codec: Option<CacheCodec>,
971 ) -> Result<(CacheEntry, bool)> {
972 if let Some(entry) = self.get(key, codec).await {
973 return Ok((entry, true));
974 }
975 let (entry, size_bytes) = loader.await?;
976 self.insert(key, entry.clone(), size_bytes, codec).await;
977 Ok((entry, false))
978 }
979
980 async fn clear(&self) {
981 self.entries.lock().await.clear();
982 }
983
984 async fn num_entries(&self) -> usize {
985 self.entries.lock().await.len()
986 }
987
988 async fn size_bytes(&self) -> usize {
989 self.entries
990 .lock()
991 .await
992 .values()
993 .map(|(_, size_bytes)| size_bytes)
994 .sum()
995 }
996 }
997
998 #[derive(Debug)]
999 struct WrongTypeBackend;
1000
1001 #[async_trait::async_trait]
1002 impl CacheBackend for WrongTypeBackend {
1003 async fn get(
1004 &self,
1005 _key: &InternalCacheKey,
1006 _codec: Option<CacheCodec>,
1007 ) -> Option<CacheEntry> {
1008 Some(Arc::new(String::from("wrong type")))
1009 }
1010
1011 async fn insert(
1012 &self,
1013 _key: &InternalCacheKey,
1014 _entry: CacheEntry,
1015 _size_bytes: usize,
1016 _codec: Option<CacheCodec>,
1017 ) {
1018 }
1019
1020 async fn get_or_insert<'a>(
1021 &self,
1022 _key: &InternalCacheKey,
1023 _loader: Pin<Box<dyn Future<Output = Result<(CacheEntry, usize)>> + Send + 'a>>,
1024 _codec: Option<CacheCodec>,
1025 ) -> Result<(CacheEntry, bool)> {
1026 Ok((Arc::new(String::from("wrong type")), true))
1027 }
1028
1029 async fn clear(&self) {}
1030
1031 async fn num_entries(&self) -> usize {
1032 0
1033 }
1034
1035 async fn size_bytes(&self) -> usize {
1036 0
1037 }
1038 }
1039
1040 #[tokio::test]
1041 async fn typed_roundtrip_stats_clear_and_namespace_isolation() {
1042 let cache = LanceCache::with_capacity(4096);
1043 let left = cache.with_key_prefix("left");
1044 let right = cache.with_key_prefix("right");
1045 left.insert_with_key(&TestKey::new(7), Arc::new(vec![1, 2, 3]))
1046 .await;
1047
1048 assert_eq!(
1049 left.get_with_key(&TestKey::new(7)).await.as_deref(),
1050 Some(&vec![1, 2, 3])
1051 );
1052 assert!(right.get_with_key(&TestKey::new(7)).await.is_none());
1053 let stats = cache.stats().await;
1054 assert_eq!((stats.hits, stats.misses, stats.num_entries), (1, 1, 1));
1055
1056 cache.clear().await;
1057 let stats = left.stats().await;
1058 assert_eq!((stats.hits, stats.misses, stats.num_entries), (0, 0, 0));
1059 }
1060
1061 #[tokio::test]
1062 async fn strong_and_weak_handles_share_state_and_namespace() {
1063 let cache = LanceCache::with_capacity(4096);
1064 let child = cache.with_key_prefix("child");
1065 let weak = WeakLanceCache::from(&child);
1066
1067 assert!(
1068 weak.insert_with_key(&TestKey::new(1), Arc::new(vec![1]))
1069 .await
1070 );
1071 assert_eq!(
1072 child.get_with_key(&TestKey::new(1)).await.as_deref(),
1073 Some(&vec![1])
1074 );
1075 child
1076 .insert_with_key(&TestKey::new(2), Arc::new(vec![2]))
1077 .await;
1078 assert_eq!(
1079 weak.get_with_key(&TestKey::new(2)).await.as_deref(),
1080 Some(&vec![2])
1081 );
1082 assert_eq!((cache.stats().await.hits, cache.size().await), (2, 2));
1083 }
1084
1085 #[tokio::test]
1086 async fn nested_namespace_segments_do_not_alias_combined_segments() {
1087 let cache = LanceCache::with_capacity(4096);
1088 let nested = cache.with_key_prefix("a").with_key_prefix("b");
1089 let combined = cache.with_key_prefix("a/b");
1090 nested
1091 .insert_with_key(&TestKey::new(1), Arc::new(vec![10]))
1092 .await;
1093 assert!(combined.get_with_key(&TestKey::new(1)).await.is_none());
1094 }
1095
1096 #[tokio::test]
1097 async fn schema_change_produces_a_cold_miss() {
1098 let cache = LanceCache::with_capacity(4096);
1099 cache
1100 .insert_with_key(&TestKey::new(1), Arc::new(vec![10]))
1101 .await;
1102 assert!(cache.get_with_key(&TestKeyV2::new(1)).await.is_none());
1103 }
1104
1105 #[tokio::test]
1106 async fn get_or_insert_with_key_hit_reports_loader_execution() {
1107 let cache = LanceCache::with_capacity(4096);
1108
1109 let (value, was_cached) = cache
1111 .get_or_insert_with_key_hit(TestKey::new(1), || async { Ok(vec![1, 2, 3]) })
1112 .await
1113 .unwrap();
1114 assert_eq!(*value, vec![1, 2, 3]);
1115 assert!(!was_cached);
1116
1117 let (value, was_cached) = cache
1119 .get_or_insert_with_key_hit(TestKey::new(1), || async {
1120 panic!("should not be called")
1121 })
1122 .await
1123 .unwrap();
1124 assert_eq!(*value, vec![1, 2, 3]);
1125 assert!(was_cached);
1126 }
1127
1128 #[tokio::test]
1129 async fn default_string_bridge_matches_explicit_legacy_encoding() {
1130 let cache = LanceCache::with_capacity(4096);
1131 cache
1132 .insert_with_key(&LegacyBridgeKey("same"), Arc::new(vec![10]))
1133 .await;
1134 assert_eq!(
1135 cache
1136 .get_with_key(&ExplicitBridgeKey("same"))
1137 .await
1138 .as_deref(),
1139 Some(&vec![10])
1140 );
1141 }
1142
1143 #[tokio::test]
1144 async fn unsized_default_string_bridge_matches_explicit_legacy_encoding() {
1145 let cache = LanceCache::with_capacity(4096);
1146 let value: Arc<dyn TestDynValue> = Arc::new(vec![10, 20]);
1147 cache
1148 .insert_unsized_with_key(&LegacyUnsizedBridgeKey("same"), value)
1149 .await;
1150
1151 let cached = cache
1152 .get_unsized_with_key(&ExplicitUnsizedBridgeKey("same"))
1153 .await
1154 .unwrap();
1155 assert_eq!(cached.values(), &[10, 20]);
1156 }
1157
1158 #[tokio::test]
1159 async fn custom_backend_receives_opaque_keys_and_shared_clear() {
1160 let backend = Arc::new(HashMapBackend::default());
1161 let cache = LanceCache::with_backend(backend.clone());
1162 let child = cache.with_key_prefix("child");
1163 let value = Arc::new(vec![1, 2, 3]);
1164 let value_size = cache_entry_size(value.as_ref());
1165
1166 child.insert_with_key(&TestKey::new(7), value).await;
1167 assert_eq!(
1168 child.get_with_key(&TestKey::new(7)).await.as_deref(),
1169 Some(&vec![1, 2, 3])
1170 );
1171 assert_eq!(backend.entries.lock().await.len(), 1);
1172 assert_eq!(cache.size_bytes().await, value_size);
1173
1174 cache.clear().await;
1175 assert!(backend.entries.lock().await.is_empty());
1176 assert_eq!(child.stats().await.hits, 0);
1177 }
1178
1179 #[tokio::test]
1180 async fn backend_type_collisions_are_contextual_misses_or_errors() {
1181 let cache = LanceCache::with_backend(Arc::new(WrongTypeBackend));
1182
1183 assert!(cache.get_with_key(&TestKey::new(1)).await.is_none());
1184 let error = cache
1185 .get_or_insert_with_key(TestKey::new(2), || async { Ok(vec![2]) })
1186 .await
1187 .unwrap_err();
1188 assert!(error.to_string().contains("test.VecU32"));
1189 let stats = cache.stats().await;
1190 assert_eq!((stats.hits, stats.misses), (0, 2));
1191 }
1192
1193 #[tokio::test]
1194 async fn moka_weight_includes_the_fixed_physical_key() {
1195 let value = Arc::new(vec![0_u32; 3]);
1196 let expected = cache_entry_size(value.as_ref())
1197 .checked_add(std::mem::size_of::<InternalCacheKey>())
1198 .unwrap();
1199 let cache = LanceCache::with_capacity(expected * 2);
1200 cache.insert_with_key(&TestKey::new(1), value).await;
1201 assert_eq!(cache.size_bytes().await, expected);
1202 }
1203
1204 #[rstest::rstest]
1205 #[case::moka(TestBackendKind::Moka)]
1206 #[case::quick(TestBackendKind::Quick)]
1207 #[tokio::test]
1208 async fn deep_size_deduplicates_shared_entry_allocations(
1209 #[case] backend_kind: TestBackendKind,
1210 ) {
1211 let cache = backend_kind.cache(1 << 20);
1212 let shared_data = Arc::new(vec![0_u8; 1024]);
1213
1214 for id in 0..2 {
1215 let data = shared_data.clone();
1216 cache
1217 .get_or_insert_with_key(SharedTestKey(id), || async move {
1218 Ok(SharedTestValue { data })
1219 })
1220 .await
1221 .unwrap();
1222 }
1223
1224 let arc_overhead = std::mem::size_of::<AtomicUsize>() * 2;
1225 let shared_allocation = std::mem::size_of::<Vec<u8>>() + shared_data.capacity();
1226 let expected_entries = 2 * std::mem::size_of::<InternalCacheKey>()
1227 + 2 * (std::mem::size_of::<SharedTestValue>() + arc_overhead)
1228 + shared_allocation;
1229
1230 let weighted_size = cache.size_bytes().await;
1231 assert_eq!(weighted_size, expected_entries + shared_allocation);
1232 assert_eq!(
1233 cache.deep_size_of(),
1234 std::mem::size_of::<LanceCache>() + expected_entries
1235 );
1236
1237 let mut context = Context::new();
1238 assert_eq!(cache.deep_size_of_children(&mut context), expected_entries);
1239 assert_eq!(
1240 cache
1241 .with_key_prefix("another-handle")
1242 .deep_size_of_children(&mut context),
1243 0
1244 );
1245 }
1246
1247 #[test]
1248 fn sizing_can_reenter_the_same_cache() {
1249 let (done_tx, done_rx) = mpsc::channel();
1250 let worker = thread::spawn(move || {
1251 let runtime = tokio::runtime::Builder::new_current_thread()
1252 .enable_all()
1253 .build()
1254 .unwrap();
1255 runtime.block_on(async move {
1256 let cache = LanceCache::with_capacity(4096);
1257 cache
1258 .insert_with_key(&ReentrantKey, Arc::new(ReentrantValue(cache.clone())))
1259 .await;
1260 done_tx.send(()).unwrap();
1261 });
1262 });
1263
1264 done_rx
1265 .recv_timeout(Duration::from_secs(1))
1266 .expect("cache insertion deadlocked during sizing");
1267 worker.join().unwrap();
1268 }
1269
1270 #[tokio::test]
1271 async fn no_cache_computes_each_time() {
1272 let cache = LanceCache::no_cache();
1273 let loads = Arc::new(AtomicUsize::new(0));
1274 for _ in 0..2 {
1275 let loads = loads.clone();
1276 let value = cache
1277 .get_or_insert_with_key(TestKey::new(1), move || async move {
1278 loads.fetch_add(1, Ordering::SeqCst);
1279 Ok(vec![42])
1280 })
1281 .await
1282 .unwrap();
1283 assert_eq!(value.as_slice(), &[42]);
1284 }
1285 assert_eq!(loads.load(Ordering::SeqCst), 2);
1286 assert_eq!(cache.size().await, 0);
1287 }
1288
1289 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1290 async fn single_flight_coalesces_success_after_contenders_are_parked() {
1291 const CONTENDERS: usize = 4;
1292
1293 let cache = Arc::new(LanceCache::with_capacity(4096));
1294 let loader_calls = Arc::new(AtomicUsize::new(0));
1295 let release = Arc::new(tokio::sync::Notify::new());
1296 let (started_tx, started_rx) = tokio::sync::oneshot::channel();
1297
1298 let owner = {
1299 let cache = cache.clone();
1300 let loader_calls = loader_calls.clone();
1301 let release = release.clone();
1302 tokio::spawn(async move {
1303 cache
1304 .get_or_insert_with_key(TestKey::new(10), move || async move {
1305 loader_calls.fetch_add(1, Ordering::SeqCst);
1306 let _ = started_tx.send(());
1307 release.notified().await;
1308 Ok(vec![10])
1309 })
1310 .await
1311 })
1312 };
1313 started_rx.await.unwrap();
1314
1315 let mut contenders = Vec::new();
1316 let mut parked = Vec::new();
1317 for _ in 0..CONTENDERS {
1318 let cache = cache.clone();
1319 let loader_calls = loader_calls.clone();
1320 let (parked_tx, parked_rx) = tokio::sync::oneshot::channel();
1321 parked.push(parked_rx);
1322 contenders.push(tokio::spawn(async move {
1323 report_first_pending(
1324 cache.get_or_insert_with_key(TestKey::new(10), move || async move {
1325 loader_calls.fetch_add(1, Ordering::SeqCst);
1326 Ok(vec![99])
1327 }),
1328 parked_tx,
1329 )
1330 .await
1331 }));
1332 }
1333 for parked in parked {
1334 parked
1335 .await
1336 .expect("contender completed instead of parking behind owner");
1337 }
1338 assert_eq!(loader_calls.load(Ordering::SeqCst), 1);
1339 assert!(contenders.iter().all(|handle| !handle.is_finished()));
1340
1341 release.notify_one();
1342 assert_eq!(owner.await.unwrap().unwrap().as_slice(), &[10]);
1343 for contender in contenders {
1344 assert_eq!(contender.await.unwrap().unwrap().as_slice(), &[10]);
1345 }
1346 let stats = cache.stats().await;
1347 assert_eq!((stats.hits, stats.misses), (CONTENDERS as u64, 1),);
1348 }
1349
1350 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1351 async fn single_flight_coalesces_errors_after_contenders_are_parked() {
1352 const CONTENDERS: usize = 4;
1353
1354 let cache = Arc::new(LanceCache::with_capacity(4096));
1355 let loader_calls = Arc::new(AtomicUsize::new(0));
1356 let release = Arc::new(tokio::sync::Notify::new());
1357 let (started_tx, started_rx) = tokio::sync::oneshot::channel();
1358
1359 let owner = {
1360 let cache = cache.clone();
1361 let loader_calls = loader_calls.clone();
1362 let release = release.clone();
1363 tokio::spawn(async move {
1364 cache
1365 .get_or_insert_with_key(TestKey::new(20), move || async move {
1366 loader_calls.fetch_add(1, Ordering::SeqCst);
1367 let _ = started_tx.send(());
1368 release.notified().await;
1369 Err(Error::timeout("owner loader timed out"))
1370 })
1371 .await
1372 })
1373 };
1374 started_rx.await.unwrap();
1375
1376 let mut contenders = Vec::new();
1377 let mut parked = Vec::new();
1378 for _ in 0..CONTENDERS {
1379 let cache = cache.clone();
1380 let loader_calls = loader_calls.clone();
1381 let (parked_tx, parked_rx) = tokio::sync::oneshot::channel();
1382 parked.push(parked_rx);
1383 contenders.push(tokio::spawn(async move {
1384 report_first_pending(
1385 cache.get_or_insert_with_key(TestKey::new(20), move || async move {
1386 loader_calls.fetch_add(1, Ordering::SeqCst);
1387 Err(Error::timeout("contender loader timed out"))
1388 }),
1389 parked_tx,
1390 )
1391 .await
1392 }));
1393 }
1394 for parked in parked {
1395 parked
1396 .await
1397 .expect("contender completed instead of parking behind owner");
1398 }
1399 assert_eq!(loader_calls.load(Ordering::SeqCst), 1);
1400 assert!(contenders.iter().all(|handle| !handle.is_finished()));
1401
1402 release.notify_one();
1403 assert!(matches!(owner.await.unwrap(), Err(Error::Timeout { .. })));
1404 for contender in contenders {
1405 assert!(matches!(
1406 contender.await.unwrap(),
1407 Err(Error::Timeout { .. })
1408 ));
1409 }
1410 assert_eq!(loader_calls.load(Ordering::SeqCst), 1);
1411 }
1412
1413 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
1414 async fn single_flight_retries_after_the_owner_is_cancelled() {
1415 let cache = Arc::new(LanceCache::with_capacity(4096));
1416 let loader_calls = Arc::new(AtomicUsize::new(0));
1417 let (started_tx, started_rx) = tokio::sync::oneshot::channel();
1418
1419 let owner = {
1420 let cache = cache.clone();
1421 let loader_calls = loader_calls.clone();
1422 tokio::spawn(async move {
1423 cache
1424 .get_or_insert_with_key(TestKey::new(30), move || async move {
1425 loader_calls.fetch_add(1, Ordering::SeqCst);
1426 let _ = started_tx.send(());
1427 std::future::pending::<()>().await;
1428 Ok(vec![30])
1429 })
1430 .await
1431 })
1432 };
1433 started_rx.await.unwrap();
1434
1435 let (parked_tx, parked_rx) = tokio::sync::oneshot::channel();
1436 let contender = {
1437 let cache = cache.clone();
1438 let loader_calls = loader_calls.clone();
1439 tokio::spawn(async move {
1440 report_first_pending(
1441 cache.get_or_insert_with_key(TestKey::new(30), move || async move {
1442 loader_calls.fetch_add(1, Ordering::SeqCst);
1443 Ok(vec![31])
1444 }),
1445 parked_tx,
1446 )
1447 .await
1448 })
1449 };
1450 parked_rx
1451 .await
1452 .expect("contender completed instead of parking behind owner");
1453 assert_eq!(loader_calls.load(Ordering::SeqCst), 1);
1454 assert!(!contender.is_finished());
1455
1456 owner.abort();
1457 assert!(owner.await.unwrap_err().is_cancelled());
1458 let value = tokio::time::timeout(std::time::Duration::from_secs(5), contender)
1459 .await
1460 .expect("contender remained parked after owner cancellation")
1461 .unwrap()
1462 .unwrap();
1463 assert_eq!(value.as_slice(), &[31]);
1464 assert_eq!(loader_calls.load(Ordering::SeqCst), 2);
1465 }
1466
1467 #[tokio::test]
1468 async fn expired_weak_cache_degrades_without_retaining_state() {
1469 let cache = LanceCache::with_capacity(4096);
1470 let weak = WeakLanceCache::from(&cache);
1471 drop(cache);
1472
1473 assert!(weak.get_with_key(&TestKey::new(1)).await.is_none());
1474 assert!(
1475 !weak
1476 .insert_with_key(&TestKey::new(1), Arc::new(vec![1]))
1477 .await
1478 );
1479 let value = weak
1480 .get_or_insert_with_key(TestKey::new(1), || async { Ok(vec![7]) })
1481 .await
1482 .unwrap();
1483 assert_eq!(value.as_slice(), &[7]);
1484 }
1485}