1use std::num::NonZeroUsize;
45use std::sync::atomic::{AtomicU64, Ordering};
46use std::time::{Duration, Instant};
47
48use lru::LruCache;
49use parking_lot::Mutex;
50use serde::{Deserialize, Serialize};
51use sha2::{Digest, Sha256};
52
53use ares_types::types::Result;
54
55#[derive(Debug, Clone, Default, Serialize, Deserialize)]
61pub struct CacheStats {
62 pub hits: u64,
64 pub misses: u64,
66 pub size_bytes: u64,
68 pub entry_count: usize,
70 pub evictions: u64,
72}
73
74impl CacheStats {
75 pub fn hit_rate(&self) -> f64 {
77 let total = self.hits + self.misses;
78 if total == 0 {
79 0.0
80 } else {
81 (self.hits as f64 / total as f64) * 100.0
82 }
83 }
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct CacheConfig {
89 #[serde(default = "default_max_size_bytes")]
91 pub max_size_bytes: u64,
92
93 #[serde(default)]
95 pub default_ttl: Option<Duration>,
96
97 #[serde(default = "default_enabled")]
99 pub enabled: bool,
100}
101
102fn default_max_size_bytes() -> u64 {
103 256 * 1024 * 1024 }
105
106fn default_enabled() -> bool {
107 true
108}
109
110impl Default for CacheConfig {
111 fn default() -> Self {
112 Self {
113 max_size_bytes: default_max_size_bytes(),
114 default_ttl: None,
115 enabled: default_enabled(),
116 }
117 }
118}
119
120pub trait EmbeddingCache: Send + Sync {
129 fn get(&self, key: &str) -> Option<Vec<f32>>;
131
132 fn set(&self, key: &str, embedding: Vec<f32>, ttl: Option<Duration>) -> Result<()>;
134
135 fn invalidate(&self, key: &str) -> Result<()>;
137
138 fn clear(&self) -> Result<()>;
140
141 fn stats(&self) -> CacheStats;
143
144 fn compute_key(&self, text: &str, model: &str) -> String {
146 let mut hasher = Sha256::new();
147 hasher.update(text.as_bytes());
148 hasher.update(b"|");
149 hasher.update(model.as_bytes());
150 format!("{:x}", hasher.finalize())
151 }
152
153 fn is_enabled(&self) -> bool;
155}
156
157#[derive(Debug, Clone)]
163struct CacheEntry {
164 embedding: Vec<f32>,
166 expires_at: Option<Instant>,
168 size_bytes: usize,
170}
171
172impl CacheEntry {
173 fn new(embedding: Vec<f32>, ttl: Option<Duration>) -> Self {
174 let now = Instant::now();
175 let size_bytes = embedding.len() * std::mem::size_of::<f32>();
176 Self {
177 embedding,
178 expires_at: ttl.map(|d| now + d),
179 size_bytes,
180 }
181 }
182
183 fn is_expired(&self) -> bool {
184 self.expires_at
185 .map(|exp| Instant::now() > exp)
186 .unwrap_or(false)
187 }
188}
189
190const DEFAULT_MAX_ENTRIES: usize = 10_000;
196
197pub struct LruEmbeddingCache {
208 cache: Mutex<LruCache<String, CacheEntry>>,
210 config: CacheConfig,
212 current_size: AtomicU64,
214 hits: AtomicU64,
216 misses: AtomicU64,
218 evictions: AtomicU64,
220}
221
222impl LruEmbeddingCache {
223 pub fn new(config: CacheConfig) -> Self {
225 let avg_entry_size = 384 * std::mem::size_of::<f32>(); let max_entries = (config.max_size_bytes as usize / avg_entry_size).max(100);
229 let capacity = NonZeroUsize::new(max_entries)
230 .unwrap_or(NonZeroUsize::new(DEFAULT_MAX_ENTRIES).unwrap());
231
232 Self {
233 cache: Mutex::new(LruCache::new(capacity)),
234 config,
235 current_size: AtomicU64::new(0),
236 hits: AtomicU64::new(0),
237 misses: AtomicU64::new(0),
238 evictions: AtomicU64::new(0),
239 }
240 }
241
242 pub fn with_defaults() -> Self {
244 Self::new(CacheConfig::default())
245 }
246
247 pub fn with_max_size(max_size_bytes: u64) -> Self {
249 Self::new(CacheConfig {
250 max_size_bytes,
251 ..Default::default()
252 })
253 }
254
255 pub fn with_max_entries(max_entries: usize) -> Self {
257 let capacity = NonZeroUsize::new(max_entries)
258 .unwrap_or(NonZeroUsize::new(DEFAULT_MAX_ENTRIES).unwrap());
259 Self {
260 cache: Mutex::new(LruCache::new(capacity)),
261 config: CacheConfig::default(),
262 current_size: AtomicU64::new(0),
263 hits: AtomicU64::new(0),
264 misses: AtomicU64::new(0),
265 evictions: AtomicU64::new(0),
266 }
267 }
268
269 pub fn cleanup_expired(&self) {
271 let mut cache = self.cache.lock();
272 let mut expired_keys = Vec::new();
273
274 for (key, entry) in cache.iter() {
276 if entry.is_expired() {
277 expired_keys.push(key.clone());
278 }
279 }
280
281 for key in expired_keys {
283 if let Some(entry) = cache.pop(&key) {
284 self.current_size
285 .fetch_sub(entry.size_bytes as u64, Ordering::Relaxed);
286 }
287 }
288 }
289
290 pub fn size_bytes(&self) -> u64 {
292 self.current_size.load(Ordering::Relaxed)
293 }
294
295 pub fn len(&self) -> usize {
297 self.cache.lock().len()
298 }
299
300 pub fn is_empty(&self) -> bool {
302 self.cache.lock().is_empty()
303 }
304}
305
306impl EmbeddingCache for LruEmbeddingCache {
307 fn get(&self, key: &str) -> Option<Vec<f32>> {
308 if !self.config.enabled {
309 return None;
310 }
311
312 let mut cache = self.cache.lock();
313
314 if let Some(entry) = cache.get(key) {
316 if entry.is_expired() {
317 let entry = cache.pop(key).unwrap();
319 self.current_size
320 .fetch_sub(entry.size_bytes as u64, Ordering::Relaxed);
321 self.misses.fetch_add(1, Ordering::Relaxed);
322 return None;
323 }
324 self.hits.fetch_add(1, Ordering::Relaxed);
325 Some(entry.embedding.clone())
326 } else {
327 self.misses.fetch_add(1, Ordering::Relaxed);
328 None
329 }
330 }
331
332 fn set(&self, key: &str, embedding: Vec<f32>, ttl: Option<Duration>) -> Result<()> {
333 if !self.config.enabled {
334 return Ok(());
335 }
336
337 let entry = CacheEntry::new(embedding, ttl.or(self.config.default_ttl));
338 let entry_size = entry.size_bytes;
339
340 let mut cache = self.cache.lock();
341
342 if let Some(old_entry) = cache.pop(key) {
344 self.current_size
345 .fetch_sub(old_entry.size_bytes as u64, Ordering::Relaxed);
346 }
347
348 let was_at_capacity = cache.len() == cache.cap().get();
350
351 if let Some((_, evicted)) = cache.push(key.to_string(), entry) {
353 self.current_size
355 .fetch_sub(evicted.size_bytes as u64, Ordering::Relaxed);
356 self.evictions.fetch_add(1, Ordering::Relaxed);
357 } else if was_at_capacity {
358 self.evictions.fetch_add(1, Ordering::Relaxed);
361 }
362
363 self.current_size
365 .fetch_add(entry_size as u64, Ordering::Relaxed);
366
367 Ok(())
368 }
369
370 fn invalidate(&self, key: &str) -> Result<()> {
371 let mut cache = self.cache.lock();
372 if let Some(entry) = cache.pop(key) {
373 self.current_size
374 .fetch_sub(entry.size_bytes as u64, Ordering::Relaxed);
375 }
376 Ok(())
377 }
378
379 fn clear(&self) -> Result<()> {
380 let mut cache = self.cache.lock();
381 cache.clear();
382 self.current_size.store(0, Ordering::Relaxed);
383 Ok(())
384 }
385
386 fn stats(&self) -> CacheStats {
387 CacheStats {
388 hits: self.hits.load(Ordering::Relaxed),
389 misses: self.misses.load(Ordering::Relaxed),
390 size_bytes: self.current_size.load(Ordering::Relaxed),
391 entry_count: self.cache.lock().len(),
392 evictions: self.evictions.load(Ordering::Relaxed),
393 }
394 }
395
396 fn is_enabled(&self) -> bool {
397 self.config.enabled
398 }
399}
400
401#[derive(Debug, Default)]
409pub struct NoOpCache;
410
411impl NoOpCache {
412 pub fn new() -> Self {
414 Self
415 }
416}
417
418impl EmbeddingCache for NoOpCache {
419 fn get(&self, _key: &str) -> Option<Vec<f32>> {
420 None
421 }
422
423 fn set(&self, _key: &str, _embedding: Vec<f32>, _ttl: Option<Duration>) -> Result<()> {
424 Ok(())
425 }
426
427 fn invalidate(&self, _key: &str) -> Result<()> {
428 Ok(())
429 }
430
431 fn clear(&self) -> Result<()> {
432 Ok(())
433 }
434
435 fn stats(&self) -> CacheStats {
436 CacheStats::default()
437 }
438
439 fn is_enabled(&self) -> bool {
440 false
441 }
442}
443
444#[cfg(test)]
449mod tests {
450 #[test]
451 fn test_cache_concurrent_reads_and_writes() {
452 use std::sync::Arc;
453 use std::thread;
454
455 let cache = Arc::new(LruEmbeddingCache::with_max_entries(100));
456 let mut handles = Vec::new();
457 for i in 0..8 {
458 let cache = Arc::clone(&cache);
459 handles.push(thread::spawn(move || {
460 let key = format!("key-{i}");
461 let embedding = vec![i as f32; 4];
462 cache.set(&key, embedding, None).unwrap();
463 for _ in 0..10 {
464 let _ = cache.get(&key);
465 }
466 }));
467 }
468 for h in handles {
469 h.join().unwrap();
470 }
471 assert!(!cache.is_empty());
472 }
473
474 use super::*;
475
476 #[test]
477 fn test_cache_key_computation() {
478 let cache = LruEmbeddingCache::with_defaults();
479
480 let key1 = cache.compute_key("hello world", "bge-small-en-v1.5");
481 let key2 = cache.compute_key("hello world", "bge-small-en-v1.5");
482 let key3 = cache.compute_key("hello world", "bge-base-en-v1.5");
483 let key4 = cache.compute_key("different text", "bge-small-en-v1.5");
484
485 assert_eq!(key1, key2);
487 assert_ne!(key1, key3);
489 assert_ne!(key1, key4);
491 }
492
493 #[test]
494 fn test_cache_set_and_get() {
495 let cache = LruEmbeddingCache::with_defaults();
496 let key = "test_key";
497 let embedding = vec![1.0, 2.0, 3.0, 4.0];
498
499 assert!(cache.get(key).is_none());
501 assert_eq!(cache.stats().misses, 1);
502
503 cache.set(key, embedding.clone(), None).unwrap();
505 let retrieved = cache.get(key);
506
507 assert!(retrieved.is_some());
508 assert_eq!(retrieved.unwrap(), embedding);
509 assert_eq!(cache.stats().hits, 1);
510 }
511
512 #[test]
513 fn test_cache_invalidate() {
514 let cache = LruEmbeddingCache::with_defaults();
515 let key = "test_key";
516 let embedding = vec![1.0, 2.0, 3.0];
517
518 cache.set(key, embedding, None).unwrap();
519 assert!(cache.get(key).is_some());
520
521 cache.invalidate(key).unwrap();
522 assert!(cache.get(key).is_none());
523 }
524
525 #[test]
526 fn test_cache_clear() {
527 let cache = LruEmbeddingCache::with_defaults();
528
529 cache.set("key1", vec![1.0, 2.0], None).unwrap();
530 cache.set("key2", vec![3.0, 4.0], None).unwrap();
531
532 assert_eq!(cache.len(), 2);
533 assert!(cache.size_bytes() > 0);
534
535 cache.clear().unwrap();
536
537 assert_eq!(cache.len(), 0);
538 assert_eq!(cache.size_bytes(), 0);
539 }
540
541 #[test]
542 fn test_cache_lru_eviction() {
543 let cache = LruEmbeddingCache::with_max_entries(2);
545
546 let embedding1 = vec![1.0, 2.0, 3.0, 4.0];
547 let embedding2 = vec![5.0, 6.0, 7.0, 8.0];
548 let embedding3 = vec![9.0, 10.0, 11.0, 12.0];
549
550 cache.set("key1", embedding1.clone(), None).unwrap();
551 cache.set("key2", embedding2.clone(), None).unwrap();
552
553 assert!(cache.get("key1").is_some());
555 assert!(cache.get("key2").is_some());
556
557 cache.set("key3", embedding3.clone(), None).unwrap();
559
560 assert!(cache.get("key1").is_none());
562 assert!(cache.get("key2").is_some());
564 assert!(cache.get("key3").is_some());
565
566 assert!(cache.stats().evictions > 0);
567 }
568
569 #[test]
570 fn test_cache_ttl_expiry() {
571 let cache = LruEmbeddingCache::with_defaults();
572 let key = "test_key";
573 let embedding = vec![1.0, 2.0, 3.0];
574
575 cache
577 .set(key, embedding, Some(Duration::from_nanos(1)))
578 .unwrap();
579
580 std::thread::sleep(Duration::from_millis(1));
582
583 assert!(cache.get(key).is_none());
585 }
586
587 #[test]
588 fn test_cache_stats() {
589 let cache = LruEmbeddingCache::with_defaults();
590
591 cache.set("key1", vec![1.0, 2.0], None).unwrap();
593 let _ = cache.get("key1"); let _ = cache.get("key2"); let _ = cache.get("key3"); let stats = cache.stats();
598 assert_eq!(stats.hits, 1);
599 assert_eq!(stats.misses, 2);
600 assert_eq!(stats.entry_count, 1);
601 assert!(stats.size_bytes > 0);
602 }
603
604 #[test]
605 fn test_cache_hit_rate() {
606 let stats = CacheStats {
607 hits: 75,
608 misses: 25,
609 size_bytes: 0,
610 entry_count: 0,
611 evictions: 0,
612 };
613
614 assert!((stats.hit_rate() - 75.0).abs() < 0.001);
615 }
616
617 #[test]
618 fn test_noop_cache() {
619 let cache = NoOpCache::new();
620
621 cache.set("key", vec![1.0, 2.0], None).unwrap();
623
624 assert!(cache.get("key").is_none());
626
627 let stats = cache.stats();
629 assert_eq!(stats.hits, 0);
630 assert_eq!(stats.misses, 0);
631 assert!(!cache.is_enabled());
632 }
633
634 #[test]
635 fn test_cache_disabled() {
636 let cache = LruEmbeddingCache::new(CacheConfig {
637 enabled: false,
638 ..Default::default()
639 });
640
641 cache.set("key", vec![1.0, 2.0], None).unwrap();
643
644 assert!(cache.get("key").is_none());
646 assert!(!cache.is_enabled());
647 }
648
649 #[test]
650 fn test_cache_update_existing() {
651 let cache = LruEmbeddingCache::with_defaults();
652 let key = "test_key";
653
654 cache.set(key, vec![1.0, 2.0], None).unwrap();
655 let size1 = cache.size_bytes();
656
657 cache.set(key, vec![3.0, 4.0, 5.0, 6.0], None).unwrap();
659 let size2 = cache.size_bytes();
660
661 assert!(size2 > size1);
663 assert_eq!(cache.len(), 1);
664
665 let retrieved = cache.get(key).unwrap();
667 assert_eq!(retrieved, vec![3.0, 4.0, 5.0, 6.0]);
668 }
669 #[test]
670 fn test_cache_hit_rate_zero_requests() {
671 let stats = CacheStats::default();
672 assert_eq!(stats.hit_rate(), 0.0);
673 }
674
675 #[test]
676 fn test_cache_cleanup_expired() {
677 let cache = LruEmbeddingCache::with_max_entries(10);
678 cache
679 .set("expired", vec![1.0], Some(Duration::from_nanos(1)))
680 .unwrap();
681 cache.set("fresh", vec![2.0], None).unwrap();
682 std::thread::sleep(Duration::from_millis(2));
683 cache.cleanup_expired();
684 assert!(cache.get("expired").is_none());
685 assert!(cache.get("fresh").is_some());
686 }
687
688 #[test]
689 fn test_cache_with_max_size_constructor() {
690 let cache = LruEmbeddingCache::with_max_size(10_000);
691 assert!(cache.is_empty());
692 cache.set("k", vec![1.0; 8], None).unwrap();
693 assert_eq!(cache.len(), 1);
694 }
695
696 #[test]
697 fn test_cache_is_empty() {
698 let cache = LruEmbeddingCache::with_defaults();
699 assert!(cache.is_empty());
700 cache.set("k", vec![1.0], None).unwrap();
701 assert!(!cache.is_empty());
702 }
703
704 #[test]
705 fn test_cache_config_serde_roundtrip() {
706 let config = CacheConfig {
707 max_size_bytes: 1024,
708 default_ttl: Some(Duration::from_secs(60)),
709 enabled: true,
710 };
711 let parsed: CacheConfig = serde_json::from_str(&serde_json::to_string(&config).unwrap())
712 .unwrap();
713 assert_eq!(parsed.max_size_bytes, 1024);
714 assert!(parsed.enabled);
715 assert_eq!(parsed.default_ttl, Some(Duration::from_secs(60)));
716 }
717
718 #[test]
719 fn test_cache_get_drops_expired_entry() {
720 let cache = LruEmbeddingCache::with_defaults();
721 cache
722 .set("k", vec![1.0, 2.0], Some(Duration::from_nanos(1)))
723 .unwrap();
724 std::thread::sleep(Duration::from_millis(2));
725 assert!(cache.get("k").is_none());
726 assert_eq!(cache.len(), 0);
727 }
728
729 #[test]
730 fn test_noop_cache_invalidate_and_clear() {
731 let cache = NoOpCache::new();
732 cache.invalidate("missing").unwrap();
733 cache.clear().unwrap();
734 }
735
736 #[test]
737 fn test_cache_stats_serde_roundtrip() {
738 let stats = CacheStats {
739 hits: 10,
740 misses: 5,
741 size_bytes: 4096,
742 entry_count: 3,
743 evictions: 2,
744 };
745 let parsed: CacheStats =
746 serde_json::from_str(&serde_json::to_string(&stats).unwrap()).unwrap();
747 assert_eq!(parsed.hits, 10);
748 assert_eq!(parsed.misses, 5);
749 assert_eq!(parsed.size_bytes, 4096);
750 assert_eq!(parsed.entry_count, 3);
751 assert_eq!(parsed.evictions, 2);
752 assert!((parsed.hit_rate() - stats.hit_rate()).abs() < f64::EPSILON);
753 }
754
755 #[test]
756 fn test_cache_config_serde_defaults() {
757 let parsed: CacheConfig = serde_json::from_str("{}").unwrap();
758 assert_eq!(parsed.max_size_bytes, 256 * 1024 * 1024);
759 assert!(parsed.enabled);
760 assert_eq!(parsed.default_ttl, None);
761
762 let partial: CacheConfig =
763 serde_json::from_str(r#"{"enabled":false,"max_size_bytes":512}"#).unwrap();
764 assert!(!partial.enabled);
765 assert_eq!(partial.max_size_bytes, 512);
766 assert_eq!(partial.default_ttl, None);
767 }
768
769 #[test]
770 fn test_cache_key_sha256_properties() {
771 let cache = LruEmbeddingCache::with_defaults();
772
773 let mut hasher = Sha256::new();
774 hasher.update(b"hello");
775 hasher.update(b"|");
776 hasher.update(b"model");
777 let expected = format!("{:x}", hasher.finalize());
778
779 let key = cache.compute_key("hello", "model");
780 assert_eq!(key, expected);
781 assert_eq!(key.len(), 64);
782 assert!(key.chars().all(|c| c.is_ascii_hexdigit()));
783
784 assert_eq!(cache.compute_key("", ""), cache.compute_key("", ""));
785 assert_ne!(cache.compute_key("", ""), cache.compute_key("", "x"));
786 assert_eq!(
788 cache.compute_key("a|b", "c"),
789 cache.compute_key("a", "b|c")
790 );
791 assert_ne!(
792 cache.compute_key("hello", "model-a"),
793 cache.compute_key("hello", "model-b")
794 );
795 assert_ne!(cache.compute_key("α", "m"), cache.compute_key("a", "m"));
796 }
797
798 #[test]
799 fn test_cache_uses_config_default_ttl() {
800 let cache = LruEmbeddingCache::new(CacheConfig {
801 default_ttl: Some(Duration::from_nanos(1)),
802 ..Default::default()
803 });
804
805 cache.set("k", vec![1.0, 2.0], None).unwrap();
806 std::thread::sleep(Duration::from_millis(2));
807
808 assert!(cache.get("k").is_none());
809 assert_eq!(cache.stats().misses, 1);
810 }
811
812 #[test]
813 fn test_cache_lru_get_promotes_entry() {
814 let cache = LruEmbeddingCache::with_max_entries(2);
815
816 cache.set("key1", vec![1.0], None).unwrap();
817 cache.set("key2", vec![2.0], None).unwrap();
818
819 assert!(cache.get("key1").is_some());
821
822 cache.set("key3", vec![3.0], None).unwrap();
823
824 assert!(cache.get("key1").is_some());
825 assert!(cache.get("key3").is_some());
826 assert!(cache.get("key2").is_none());
827 assert!(cache.stats().evictions >= 1);
828 }
829
830 #[test]
831 fn test_cache_invalidate_missing_key_is_noop() {
832 let cache = LruEmbeddingCache::with_defaults();
833 cache.set("present", vec![1.0, 2.0], None).unwrap();
834
835 let bytes_before = cache.size_bytes();
836 let len_before = cache.len();
837
838 cache.invalidate("absent").unwrap();
839
840 assert_eq!(cache.size_bytes(), bytes_before);
841 assert_eq!(cache.len(), len_before);
842 assert!(cache.get("present").is_some());
843 }
844
845 #[test]
846 fn test_cache_cleanup_expired_noop_when_fresh() {
847 let cache = LruEmbeddingCache::with_max_entries(4);
848 cache.set("fresh", vec![1.0, 2.0], None).unwrap();
849
850 cache.cleanup_expired();
851
852 assert_eq!(cache.len(), 1);
853 assert!(cache.get("fresh").is_some());
854 }
855
856 struct MockEmbeddingCache {
858 store: Mutex<std::collections::HashMap<String, Vec<f32>>>,
859 }
860
861 impl MockEmbeddingCache {
862 fn new() -> Self {
863 Self {
864 store: Mutex::new(std::collections::HashMap::new()),
865 }
866 }
867 }
868
869 impl EmbeddingCache for MockEmbeddingCache {
870 fn get(&self, key: &str) -> Option<Vec<f32>> {
871 self.store.lock().get(key).cloned()
872 }
873
874 fn set(&self, key: &str, embedding: Vec<f32>, _ttl: Option<Duration>) -> Result<()> {
875 self.store.lock().insert(key.to_string(), embedding);
876 Ok(())
877 }
878
879 fn invalidate(&self, key: &str) -> Result<()> {
880 self.store.lock().remove(key);
881 Ok(())
882 }
883
884 fn clear(&self) -> Result<()> {
885 self.store.lock().clear();
886 Ok(())
887 }
888
889 fn stats(&self) -> CacheStats {
890 CacheStats {
891 entry_count: self.store.lock().len(),
892 ..Default::default()
893 }
894 }
895
896 fn is_enabled(&self) -> bool {
897 true
898 }
899 }
900
901 fn exercise_embedding_cache(cache: &dyn EmbeddingCache) {
902 let key = cache.compute_key("integration text", "test-model");
903 assert!(cache.get(&key).is_none());
904
905 let embedding = vec![0.1, 0.2, 0.3];
906 cache.set(&key, embedding.clone(), None).unwrap();
907 assert_eq!(cache.get(&key), Some(embedding));
908
909 cache.invalidate(&key).unwrap();
910 assert!(cache.get(&key).is_none());
911
912 cache.set(&key, vec![9.0], None).unwrap();
913 cache.clear().unwrap();
914 assert!(cache.get(&key).is_none());
915 assert_eq!(cache.stats().entry_count, 0);
916 }
917
918 #[test]
919 fn test_mock_storage_backend_integration() {
920 exercise_embedding_cache(&MockEmbeddingCache::new());
921 }
922
923 #[test]
924 fn test_lru_cache_trait_object_integration() {
925 let cache: Box<dyn EmbeddingCache> = Box::new(LruEmbeddingCache::with_max_entries(8));
926 exercise_embedding_cache(cache.as_ref());
927 assert!(cache.is_enabled());
928 }
929
930
931}
932