1use super::{CacheEntryKey, CacheEntryKeyRef, EvictionManager};
18#[cfg(test)]
19use crate::key::CompactCacheKey;
20
21use async_trait::async_trait;
22use pingora_error::{ErrorType::*, OrErr, Result};
23use pingora_lru::{persistence, Lru};
24use serde::de::SeqAccess;
25use serde::{Deserialize, Serialize};
26use std::hash::{Hash, Hasher};
27use std::time::SystemTime;
28
29pub struct Manager<const N: usize>(Lru<CacheEntryKey, N>);
37
38#[derive(Debug, Serialize, Deserialize)]
39struct SerdeHelperNode(CacheEntryKey, usize);
40
41impl<const N: usize> Manager<N> {
42 pub fn with_capacity(limit: usize, capacity: usize) -> Self {
46 Manager(Lru::with_capacity(limit, capacity))
47 }
48
49 pub fn with_capacity_and_watermark(
54 limit: usize,
55 capacity: usize,
56 watermark: Option<usize>,
57 ) -> Self {
58 Manager(Lru::with_capacity_and_watermark(limit, capacity, watermark))
59 }
60
61 pub fn weight_limit(&self) -> usize {
63 self.0.weight_limit()
64 }
65
66 pub fn set_weight_limit(&self, limit: usize) {
68 self.0.set_weight_limit(limit);
69 }
70
71 pub fn shards(&self) -> usize {
73 self.0.shards()
74 }
75
76 pub fn shard_weight(&self, shard: usize) -> usize {
78 self.0.shard_weight(shard)
79 }
80
81 pub fn shard_len(&self, shard: usize) -> usize {
85 self.0.shard_len(shard)
86 }
87
88 pub fn get_shard_for_key(&self, key: &CacheEntryKey) -> usize {
93 (u64key(key) % N as u64) as usize
94 }
95
96 pub fn peek_lru(&self, shard: usize) -> Option<CacheEntryKey> {
102 self.0.peek_lru(shard).map(|(key, _weight)| key)
103 }
104
105 pub fn serialize_shard(&self, shard: usize) -> Result<Vec<u8>> {
107 use rmp_serde::encode::Serializer;
108 use serde::ser::SerializeSeq;
109 use serde::ser::Serializer as _;
110
111 assert!(shard < N);
112
113 let mut nodes = Vec::with_capacity(self.0.shard_len(shard));
116 self.0.iter_for_each(shard, |(node, size)| {
117 nodes.push(SerdeHelperNode(node.clone(), size));
118 });
119 let mut ser = Serializer::new(vec![]);
120 let mut seq = ser
124 .serialize_seq(Some(nodes.len()))
125 .or_err(InternalError, "fail to serialize node")?;
126 for node in nodes {
127 seq.serialize_element(&node).unwrap(); }
129
130 seq.end().or_err(InternalError, "when serializing LRU")?;
131 Ok(ser.into_inner())
132 }
133
134 pub fn deserialize_shard(&self, buf: &[u8]) -> Result<()> {
138 use rmp_serde::decode::Deserializer;
139 use serde::de::Deserializer as _;
140
141 let mut de = Deserializer::new(buf);
142 let visitor = InsertToManager { lru: self };
143 de.deserialize_seq(visitor)
144 .or_err(InternalError, "when deserializing LRU")?;
145 Ok(())
146 }
147
148 pub fn peek_weight(&self, item: &CacheEntryKey) -> Option<usize> {
150 let key = u64key(item);
151 self.0.peek_weight(key)
152 }
153}
154
155struct InsertToManager<'a, const N: usize> {
156 lru: &'a Manager<N>,
157}
158
159impl<'de, const N: usize> serde::de::Visitor<'de> for InsertToManager<'_, N> {
160 type Value = ();
161
162 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
163 formatter.write_str("array of lru nodes")
164 }
165
166 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
167 where
168 A: SeqAccess<'de>,
169 {
170 while let Some(node) = seq.next_element::<SerdeHelperNode>()? {
171 let key = u64key(&node.0);
172 self.lru.0.insert_tail(key, node.0, node.1); }
174 Ok(())
175 }
176}
177
178#[inline]
179fn u64key(key: &impl Hash) -> u64 {
180 let mut hasher = ahash::AHasher::default();
182 key.hash(&mut hasher);
183 hasher.finish()
184}
185
186const FILE_NAME: &str = "lru.data";
187
188#[async_trait]
189impl<const N: usize> EvictionManager for Manager<N> {
190 fn total_size(&self) -> usize {
191 self.0.weight()
192 }
193 fn total_items(&self) -> usize {
194 self.0.len()
195 }
196 fn evicted_size(&self) -> usize {
197 self.0.evicted_weight()
198 }
199 fn evicted_items(&self) -> usize {
200 self.0.evicted_len()
201 }
202
203 fn admit(
204 &self,
205 item: CacheEntryKey,
206 size: usize,
207 _fresh_until: SystemTime,
208 ) -> Vec<CacheEntryKey> {
209 let key = u64key(&item);
210 self.0.admit(key, item, size);
211 self.0
212 .evict_to_limit()
213 .into_iter()
214 .map(|(key, _weight)| key)
215 .collect()
216 }
217
218 fn increment_weight(
219 &self,
220 item: &CacheEntryKey,
221 delta: usize,
222 max_weight: Option<usize>,
223 ) -> Vec<CacheEntryKey> {
224 let key = u64key(item);
225 self.0
226 .increment_weight(key, || item.clone(), delta, max_weight);
227 self.0
228 .evict_to_limit()
229 .into_iter()
230 .map(|(key, _weight)| key)
231 .collect()
232 }
233
234 fn remove(&self, item: CacheEntryKeyRef<'_>) {
235 let key = u64key(&item);
236 self.0.remove(key);
237 }
238
239 fn access(&self, item: &CacheEntryKey, size: usize, _fresh_until: SystemTime) -> bool {
240 let key = u64key(item);
241 if !self.0.promote(key) {
242 self.0.admit(key, item.clone(), size);
243 false
244 } else {
245 true
246 }
247 }
248
249 fn peek(&self, item: &CacheEntryKey) -> bool {
250 let key = u64key(item);
251 self.0.peek(key)
252 }
253
254 async fn save(&self, dir_path: &str) -> Result<()> {
255 persistence::save_shards(dir_path, FILE_NAME, N, |i| self.serialize_shard(i))
256 .await
257 .or_err(InternalError, "failed to save LRU")?;
258 Ok(())
259 }
260
261 async fn load(&self, dir_path: &str) -> Result<()> {
262 persistence::load_shards(dir_path, FILE_NAME, N, |_i, data| {
263 self.deserialize_shard(data)
264 })
265 .await;
266 Ok(())
267 }
268}
269
270#[cfg(test)]
271impl<const N: usize> Manager<N> {
272 fn admit(
273 &self,
274 item: CompactCacheKey,
275 size: usize,
276 fresh_until: SystemTime,
277 ) -> Vec<CompactCacheKey> {
278 EvictionManager::admit(self, CacheEntryKey::key_only(item), size, fresh_until)
279 .into_iter()
280 .map(CacheEntryKey::into_key)
281 .collect()
282 }
283
284 fn increment_weight(
285 &self,
286 item: &CompactCacheKey,
287 delta: usize,
288 max_weight: Option<usize>,
289 ) -> Vec<CompactCacheKey> {
290 EvictionManager::increment_weight(
291 self,
292 &CacheEntryKey::key_only(item.clone()),
293 delta,
294 max_weight,
295 )
296 .into_iter()
297 .map(CacheEntryKey::into_key)
298 .collect()
299 }
300
301 fn remove(&self, item: &CompactCacheKey) {
302 EvictionManager::remove(self, CacheEntryKeyRef::from_entry_id(item, None));
303 }
304
305 fn access(&self, item: &CompactCacheKey, size: usize, fresh_until: SystemTime) -> bool {
306 EvictionManager::access(
307 self,
308 &CacheEntryKey::key_only(item.clone()),
309 size,
310 fresh_until,
311 )
312 }
313
314 fn peek(&self, item: &CompactCacheKey) -> bool {
315 EvictionManager::peek(self, &CacheEntryKey::key_only(item.clone()))
316 }
317}
318
319#[cfg(test)]
320mod test {
321 use super::*;
322 use crate::CacheKey;
323 use std::path::Path;
324
325 #[test]
328 fn test_admission() {
329 let lru = Manager::<1>::with_capacity(4, 10);
330 let key1 = CacheKey::new("a", "1").to_compact();
331 let until = SystemTime::now(); let v = lru.admit(key1.clone(), 1, until);
333 assert_eq!(v.len(), 0);
334 let key2 = CacheKey::new("b", "1").to_compact();
335 let v = lru.admit(key2.clone(), 2, until);
336 assert_eq!(v.len(), 0);
337 let key3 = CacheKey::new("c", "1").to_compact();
338 let v = lru.admit(key3, 1, until);
339 assert_eq!(v.len(), 0);
340
341 let key4 = CacheKey::new("d", "1").to_compact();
344 let v = lru.admit(key4, 2, until);
345 assert_eq!(v.len(), 2);
347 assert_eq!(v[0], key1);
348 assert_eq!(v[1], key2);
349 }
350
351 #[test]
352 fn test_identified_entries_are_distinct() {
353 let lru = Manager::<1>::with_capacity(1, 2);
354 let key = CacheKey::new("a", "1").to_compact();
355 let first = CacheEntryKey::identified(key.clone(), crate::CacheEntryId::new(1));
356 let second = CacheEntryKey::identified(key, crate::CacheEntryId::new(2));
357 let until = SystemTime::now();
358
359 assert!(EvictionManager::admit(&lru, first.clone(), 1, until).is_empty());
360 assert_eq!(
361 EvictionManager::admit(&lru, second.clone(), 1, until),
362 vec![first]
363 );
364 assert_eq!(lru.peek_lru(0), Some(second));
365 }
366
367 #[test]
368 fn test_set_weight_limit() {
369 let lru = Manager::<1>::with_capacity(4, 10);
370 let until = SystemTime::now();
371 let key1 = CacheKey::new("a", "1").to_compact();
372 let key2 = CacheKey::new("b", "1").to_compact();
373 let key3 = CacheKey::new("c", "1").to_compact();
374 assert_eq!(lru.weight_limit(), 4);
375 assert!(lru.admit(key1.clone(), 1, until).is_empty());
376 assert!(lru.admit(key2.clone(), 1, until).is_empty());
377
378 lru.set_weight_limit(1);
379 assert_eq!(lru.weight_limit(), 1);
380 let evicted = lru.admit(key3, 1, until);
381 assert_eq!(evicted, vec![key1, key2]);
382
383 lru.set_weight_limit(10);
384 assert_eq!(lru.weight_limit(), 10);
385 }
386
387 #[test]
388 fn test_access() {
389 let lru = Manager::<1>::with_capacity(4, 10);
390 let key1 = CacheKey::new("a", "1").to_compact();
391 let until = SystemTime::now(); let v = lru.admit(key1.clone(), 1, until);
393 assert_eq!(v.len(), 0);
394 let key2 = CacheKey::new("b", "1").to_compact();
395 let v = lru.admit(key2.clone(), 2, until);
396 assert_eq!(v.len(), 0);
397 let key3 = CacheKey::new("c", "1").to_compact();
398 let v = lru.admit(key3, 1, until);
399 assert_eq!(v.len(), 0);
400
401 lru.access(&key1, 1, until);
404 assert_eq!(v.len(), 0);
405
406 let key4 = CacheKey::new("d", "1").to_compact();
407 let v = lru.admit(key4, 2, until);
408 assert_eq!(v.len(), 1);
409 assert_eq!(v[0], key2);
410 }
411
412 #[test]
413 fn test_remove() {
414 let lru = Manager::<1>::with_capacity(4, 10);
415 let key1 = CacheKey::new("a", "1").to_compact();
416 let until = SystemTime::now(); let v = lru.admit(key1.clone(), 1, until);
418 assert_eq!(v.len(), 0);
419 let key2 = CacheKey::new("b", "1").to_compact();
420 let v = lru.admit(key2.clone(), 2, until);
421 assert_eq!(v.len(), 0);
422 let key3 = CacheKey::new("c", "1").to_compact();
423 let v = lru.admit(key3, 1, until);
424 assert_eq!(v.len(), 0);
425
426 lru.remove(&key1);
429
430 let key4 = CacheKey::new("d", "1").to_compact();
432 let v = lru.admit(key4, 2, until);
433 assert_eq!(v.len(), 1);
434 assert_eq!(v[0], key2);
435 }
436
437 #[test]
438 fn test_access_add() {
439 let lru = Manager::<1>::with_capacity(4, 10);
440 let until = SystemTime::now(); let key1 = CacheKey::new("a", "1").to_compact();
443 lru.access(&key1, 1, until);
444 let key2 = CacheKey::new("b", "1").to_compact();
445 lru.access(&key2, 2, until);
446 let key3 = CacheKey::new("c", "1").to_compact();
447 lru.access(&key3, 2, until);
448
449 let key4 = CacheKey::new("d", "1").to_compact();
450 let v = lru.admit(key4, 2, until);
451 assert_eq!(v.len(), 2);
453 assert_eq!(v[0], key1);
454 assert_eq!(v[1], key2);
455 }
456
457 #[test]
458 fn test_increment_weight_adds_missing_item() {
459 let lru = Manager::<1>::with_capacity(4, 10);
460
461 let key1 = CacheKey::new("a", "1").to_compact();
462 assert!(lru.increment_weight(&key1, 2, None).is_empty());
463 assert!(lru.peek(&key1));
464 assert_eq!(lru.total_size(), 2);
465 assert_eq!(lru.total_items(), 1);
466
467 let key2 = CacheKey::new("b", "1").to_compact();
468 let evicted = lru.increment_weight(&key2, 100, Some(3));
469 assert_eq!(evicted, vec![key1]);
470 assert!(lru.peek(&key2));
471 assert_eq!(lru.total_size(), 3);
472 assert_eq!(lru.total_items(), 1);
473 }
474
475 #[test]
476 fn test_increment_weight_admits_zero_and_does_not_shrink() {
477 let lru = Manager::<1>::with_capacity(10, 10);
478
479 let key1 = CacheKey::new("a", "1").to_compact();
480 assert!(lru.increment_weight(&key1, 0, None).is_empty());
481 assert!(lru.peek(&key1));
482 assert_eq!(lru.total_size(), 1);
483
484 let key2 = CacheKey::new("b", "1").to_compact();
485 assert!(lru.increment_weight(&key2, 3, None).is_empty());
486 assert!(lru.increment_weight(&key2, 100, Some(2)).is_empty());
487 assert_eq!(lru.total_size(), 4);
488 assert_eq!(lru.total_items(), 2);
489 }
490
491 #[test]
492 fn test_admit_update() {
493 let lru = Manager::<1>::with_capacity(4, 10);
494 let key1 = CacheKey::new("a", "1").to_compact();
495 let until = SystemTime::now(); let v = lru.admit(key1.clone(), 1, until);
497 assert_eq!(v.len(), 0);
498 let key2 = CacheKey::new("b", "1").to_compact();
499 let v = lru.admit(key2.clone(), 2, until);
500 assert_eq!(v.len(), 0);
501 let key3 = CacheKey::new("c", "1").to_compact();
502 let v = lru.admit(key3, 1, until);
503 assert_eq!(v.len(), 0);
504
505 let v = lru.admit(key2, 1, until);
508 assert_eq!(v.len(), 0);
509
510 let key4 = CacheKey::new("d", "1").to_compact();
512 let v = lru.admit(key4.clone(), 1, until);
513 assert_eq!(v.len(), 0);
514
515 let v = lru.admit(key4, 2, until);
517 assert_eq!(v.len(), 1);
519 assert_eq!(v[0], key1);
520 }
521
522 #[test]
523 fn test_peek() {
524 let lru = Manager::<1>::with_capacity(4, 10);
525 let until = SystemTime::now(); let key1 = CacheKey::new("a", "1").to_compact();
528 lru.access(&key1, 1, until);
529 let key2 = CacheKey::new("b", "1").to_compact();
530 lru.access(&key2, 2, until);
531 assert!(lru.peek(&key1));
532 assert!(lru.peek(&key2));
533 }
534
535 #[test]
536 fn test_serde() {
537 let lru = Manager::<1>::with_capacity(4, 10);
538 let key1 = CacheKey::new("a", "1").to_compact();
539 let until = SystemTime::now(); let v = lru.admit(key1.clone(), 1, until);
541 assert_eq!(v.len(), 0);
542 let key2 = CacheKey::new("b", "1").to_compact();
543 let v = lru.admit(key2.clone(), 2, until);
544 assert_eq!(v.len(), 0);
545 let key3 = CacheKey::new("c", "1").to_compact();
546 let v = lru.admit(key3, 1, until);
547 assert_eq!(v.len(), 0);
548
549 lru.access(&key1, 1, until);
552 assert_eq!(v.len(), 0);
553
554 let ser = lru.serialize_shard(0).unwrap();
556 let lru2 = Manager::<1>::with_capacity(4, 10);
557 lru2.deserialize_shard(&ser).unwrap();
558
559 let key4 = CacheKey::new("d", "1").to_compact();
560 let v = lru2.admit(key4, 2, until);
561 assert_eq!(v.len(), 1);
562 assert_eq!(v[0], key2);
563 }
564
565 #[tokio::test]
566 async fn test_save_to_disk() {
567 let until = SystemTime::now(); let lru = Manager::<2>::with_capacity(10, 10);
569
570 lru.admit(CacheKey::new("a", "1").to_compact(), 1, until);
571 lru.admit(CacheKey::new("b", "1").to_compact(), 2, until);
572 lru.admit(CacheKey::new("c", "1").to_compact(), 1, until);
573 lru.admit(CacheKey::new("d", "1").to_compact(), 1, until);
574 lru.admit(CacheKey::new("e", "1").to_compact(), 2, until);
575 lru.admit(CacheKey::new("f", "1").to_compact(), 1, until);
576
577 lru.save("/tmp/test_lru_save").await.unwrap();
579 let lru2 = Manager::<2>::with_capacity(4, 10);
580 lru2.load("/tmp/test_lru_save").await.unwrap();
581
582 let ser0 = lru.serialize_shard(0).unwrap();
583 let ser1 = lru.serialize_shard(1).unwrap();
584
585 assert_eq!(ser0, lru2.serialize_shard(0).unwrap());
586 assert_eq!(ser1, lru2.serialize_shard(1).unwrap());
587 }
588
589 #[tokio::test]
590 async fn test_load_no_shards() {
591 let test_dir = "/tmp/test_lru_no_shards";
593 let _ = std::fs::remove_dir_all(test_dir);
594 std::fs::create_dir_all(test_dir).unwrap();
595
596 let lru = Manager::<4>::with_capacity(10, 10);
597 lru.load(test_dir).await.unwrap();
598 assert_eq!(lru.total_items(), 0);
599 assert_eq!(lru.total_size(), 0);
600
601 std::fs::remove_dir_all(test_dir).unwrap();
602 }
603
604 #[tokio::test]
605 async fn test_load_partial_shards() {
606 let test_dir = "/tmp/test_lru_partial_shards";
610 let _ = std::fs::remove_dir_all(test_dir);
611 std::fs::create_dir_all(test_dir).unwrap();
612
613 let until = SystemTime::now();
614 let src = Manager::<4>::with_capacity(100, 100);
615 for i in 0..16 {
616 src.admit(CacheKey::new(format!("k{i}"), "1").to_compact(), 1, until);
617 }
618 src.save(test_dir).await.unwrap();
619 let baseline = src.total_items();
620 assert!(baseline > 0);
621
622 std::fs::remove_file(format!("{test_dir}/lru.data.1")).unwrap();
624 std::fs::remove_file(format!("{test_dir}/lru.data.3")).unwrap();
625
626 let dst = Manager::<4>::with_capacity(100, 100);
627 dst.load(test_dir).await.unwrap();
628 assert!(dst.total_items() > 0);
630 assert!(dst.total_items() < baseline);
631
632 std::fs::remove_dir_all(test_dir).unwrap();
633 }
634
635 #[tokio::test]
636 async fn test_save_partial_failure_continues() {
637 let test_dir = "/tmp/test_lru_save_partial_fail";
641 let _ = std::fs::remove_dir_all(test_dir);
642 std::fs::create_dir_all(test_dir).unwrap();
643 std::fs::create_dir(format!("{test_dir}/lru.data.1")).unwrap();
644
645 let until = SystemTime::now();
646 let src = Manager::<4>::with_capacity(100, 100);
647 for i in 0..16 {
648 src.admit(CacheKey::new(format!("k{i}"), "1").to_compact(), 1, until);
649 }
650 src.save(test_dir).await.unwrap();
651
652 for i in [0, 2, 3] {
654 let p = format!("{test_dir}/lru.data.{i}");
655 let meta = std::fs::metadata(&p).unwrap();
656 assert!(meta.is_file(), "shard {i} should be a regular file");
657 assert!(meta.len() > 0, "shard {i} should be non-empty");
658 }
659 assert!(std::fs::metadata(format!("{test_dir}/lru.data.1"))
660 .unwrap()
661 .is_dir());
662
663 std::fs::remove_dir(format!("{test_dir}/lru.data.1")).unwrap();
664 std::fs::remove_dir_all(test_dir).unwrap();
665 }
666
667 #[tokio::test]
668 async fn test_save_total_failure_returns_err() {
669 let test_dir = "/tmp/test_lru_save_total_fail";
672 let _ = std::fs::remove_dir_all(test_dir);
673 std::fs::create_dir_all(test_dir).unwrap();
674 for i in 0..4 {
675 std::fs::create_dir(format!("{test_dir}/lru.data.{i}")).unwrap();
676 }
677
678 let until = SystemTime::now();
679 let src = Manager::<4>::with_capacity(100, 100);
680 src.admit(CacheKey::new("k", "1").to_compact(), 1, until);
681
682 let err = src.save(test_dir).await.unwrap_err();
683 assert!(
684 err.to_string().contains("All 4 shards failed to save"),
685 "unexpected error message: {err}"
686 );
687
688 for i in 0..4 {
689 std::fs::remove_dir(format!("{test_dir}/lru.data.{i}")).unwrap();
690 }
691 std::fs::remove_dir_all(test_dir).unwrap();
692 }
693
694 #[tokio::test]
695 async fn test_load_unreadable_shard_continues() {
696 let test_dir = "/tmp/test_lru_unreadable_shard";
699 let _ = std::fs::remove_dir_all(test_dir);
700 std::fs::create_dir_all(test_dir).unwrap();
701
702 let until = SystemTime::now();
703 let src = Manager::<4>::with_capacity(100, 100);
704 for i in 0..16 {
705 src.admit(CacheKey::new(format!("k{i}"), "1").to_compact(), 1, until);
706 }
707 src.save(test_dir).await.unwrap();
708
709 std::fs::remove_file(format!("{test_dir}/lru.data.2")).unwrap();
712 std::fs::create_dir(format!("{test_dir}/lru.data.2")).unwrap();
713
714 let dst = Manager::<4>::with_capacity(100, 100);
715 dst.load(test_dir).await.unwrap();
716 assert!(dst.total_items() > 0);
718
719 std::fs::remove_dir(format!("{test_dir}/lru.data.2")).unwrap();
720 std::fs::remove_dir_all(test_dir).unwrap();
721 }
722
723 #[tokio::test]
724 async fn test_load_corrupt_shard_continues() {
725 let test_dir = "/tmp/test_lru_corrupt_shard";
727 let _ = std::fs::remove_dir_all(test_dir);
728 std::fs::create_dir_all(test_dir).unwrap();
729
730 let until = SystemTime::now();
731 let src = Manager::<4>::with_capacity(100, 100);
732 for i in 0..16 {
733 src.admit(CacheKey::new(format!("k{i}"), "1").to_compact(), 1, until);
734 }
735 src.save(test_dir).await.unwrap();
736
737 std::fs::write(format!("{test_dir}/lru.data.2"), b"not valid msgpack").unwrap();
739
740 let dst = Manager::<4>::with_capacity(100, 100);
741 dst.load(test_dir).await.unwrap();
742 assert!(dst.total_items() > 0);
744
745 std::fs::remove_dir_all(test_dir).unwrap();
746 }
747
748 #[tokio::test]
749 async fn test_save_then_load_roundtrip_with_remove() {
750 let test_dir = "/tmp/test_lru_save_after_partial_load";
753 let _ = std::fs::remove_dir_all(test_dir);
754 std::fs::create_dir_all(test_dir).unwrap();
755
756 let until = SystemTime::now();
757 let src = Manager::<4>::with_capacity(100, 100);
758 for i in 0..16 {
759 src.admit(CacheKey::new(format!("k{i}"), "1").to_compact(), 1, until);
760 }
761 src.save(test_dir).await.unwrap();
762 std::fs::remove_file(format!("{test_dir}/lru.data.1")).unwrap();
763
764 let dst = Manager::<4>::with_capacity(100, 100);
765 dst.load(test_dir).await.unwrap();
766 dst.save(test_dir).await.unwrap();
767
768 for i in 0..4 {
769 assert!(
770 Path::new(&format!("{test_dir}/lru.data.{i}")).exists(),
771 "shard {i} should exist after save"
772 );
773 }
774
775 std::fs::remove_dir_all(test_dir).unwrap();
776 }
777
778 #[tokio::test]
779 async fn test_temp_file_cleanup() {
780 let test_dir = "/tmp/test_lru_cleanup";
781 let dir_path = Path::new(test_dir);
782
783 std::fs::create_dir_all(dir_path).unwrap();
785
786 let temp_files = [
788 "lru.data.0.12345678.tmp",
789 "lru.data.1.abcdef00.tmp",
790 "other_file.tmp", "lru.data.2", ];
793
794 for file in temp_files {
795 let file_path = dir_path.join(file);
796 std::fs::write(&file_path, b"test").unwrap();
797 }
798
799 let lru = Manager::<3>::with_capacity(10, 10);
800 lru.load(test_dir).await.unwrap();
801
802 assert!(!dir_path.join("lru.data.0.12345678.tmp").exists());
804 assert!(!dir_path.join("lru.data.1.abcdef00.tmp").exists());
805 assert!(dir_path.join("other_file.tmp").exists()); assert!(dir_path.join("lru.data.2").exists()); std::fs::remove_dir_all(dir_path).unwrap();
810 }
811
812 #[test]
813 fn test_peek_lru() {
814 let lru = Manager::<1>::with_capacity(20, 20);
815 let until = SystemTime::now();
816
817 assert!(lru.peek_lru(0).is_none());
819
820 let key1 = CacheKey::new("a", "1").to_compact();
821 lru.admit(key1.clone(), 1, until);
822 assert_eq!(
824 lru.peek_lru(0).unwrap(),
825 CacheEntryKey::key_only(key1.clone())
826 );
827
828 let key2 = CacheKey::new("b", "1").to_compact();
830 lru.admit(key2.clone(), 1, until);
831 for i in 0..5 {
832 lru.admit(CacheKey::new(format!("f{i}"), "1").to_compact(), 1, until);
833 }
834 assert_eq!(
836 lru.peek_lru(0).unwrap(),
837 CacheEntryKey::key_only(key1.clone())
838 );
839
840 lru.access(&key1, 1, until);
842 assert_eq!(
843 lru.peek_lru(0).unwrap(),
844 CacheEntryKey::key_only(key2.clone())
845 );
846
847 assert_eq!(
849 lru.peek_lru(0).unwrap(),
850 CacheEntryKey::key_only(key2.clone())
851 );
852 assert!(lru.peek(&key2));
853
854 assert!(lru.peek_lru(999).is_none());
856 }
857}