Skip to main content

cachekit/policy/
slru.rs

1//! Segmented LRU (SLRU) cache replacement policy.
2//!
3//! Implements the SLRU algorithm, which separates recently inserted items from
4//! frequently accessed items using two LRU segments. This provides scan resistance
5//! by preventing one-time accesses from polluting the protected (frequently used) segment.
6//!
7//! ## Architecture
8//!
9//! ```text
10//! ┌─────────────────────────────────────────────────────────────────────────────┐
11//! │                           SlruCore<K, V> Layout                             │
12//! │                                                                             │
13//! │   ┌─────────────────────────────────────────────────────────────────────┐   │
14//! │   │  index: HashMap<K, NonNull<Node>>    nodes: Allocated on heap       │   │
15//! │   │                                                                     │   │
16//! │   │  ┌──────────┬───────────┐           ┌────────┬──────────────────┐   │   │
17//! │   │  │   Key    │  NodePtr  │           │ Node   │ key,val,segment  │   │   │
18//! │   │  ├──────────┼───────────┤           ├────────┼──────────────────┤   │   │
19//! │   │  │  "page1" │   ptr_0   │──────────►│ Node0  │ k,v,Probationary │   │   │
20//! │   │  │  "page2" │   ptr_1   │──────────►│ Node1  │ k,v,Protected    │   │   │
21//! │   │  │  "page3" │   ptr_2   │──────────►│ Node2  │ k,v,Probationary │   │   │
22//! │   │  └──────────┴───────────┘           └────────┴──────────────────┘   │   │
23//! │   └─────────────────────────────────────────────────────────────────────┘   │
24//! │                                                                             │
25//! │   ┌─────────────────────────────────────────────────────────────────────┐   │
26//! │   │                        Segment Organization                         │   │
27//! │   │                                                                     │   │
28//! │   │   PROBATIONARY (LRU)                     PROTECTED (LRU)            │   │
29//! │   │   ┌─────────────────────────┐            ┌─────────────────────────┐│   │
30//! │   │   │ MRU               LRU   │            │ MRU               LRU   ││   │
31//! │   │   │  ▼                  ▼   │            │  ▼                  ▼   ││   │
32//! │   │   │ [ptr_2] ◄──► [ptr_0] ◄┤ │            │ [ptr_1] ◄──► [...] ◄┤   ││   │
33//! │   │   │  new        older evict │            │ hot          cold  evict││   │
34//! │   │   └─────────────────────────┘            └─────────────────────────┘│   │
35//! │   │                                                                     │   │
36//! │   │   • New items enter probationary LRU                                │   │
37//! │   │   • Re-access promotes to protected LRU                             │   │
38//! │   │   • Eviction: probationary LRU first, then protected LRU            │   │
39//! │   └─────────────────────────────────────────────────────────────────────┘   │
40//! │                                                                             │
41//! └─────────────────────────────────────────────────────────────────────────────┘
42//!
43//! Insert Flow (new key)
44//! ──────────────────────
45//!
46//!   insert("new_key", value):
47//!     1. Check index - not found
48//!     2. Create Node with Segment::Probationary
49//!     3. Allocate on heap → get NonNull<Node>
50//!     4. Insert key→ptr into index
51//!     5. Attach ptr to probationary MRU
52//!     6. Evict if over capacity
53//!
54//! Access Flow (existing key)
55//! ──────────────────────────
56//!
57//!   get("existing_key"):
58//!     1. Lookup ptr in index
59//!     2. Check node's segment:
60//!        - If Probationary: promote to Protected (move to protected MRU)
61//!        - If Protected: move to MRU position
62//!     3. Return &value
63//!
64//! Eviction Flow
65//! ─────────────
66//!
67//!   evict_if_needed():
68//!     while len > capacity:
69//!       if probationary.len > probationary_cap:
70//!         evict from probationary LRU
71//!       else:
72//!         evict from protected LRU
73//! ```
74//!
75//! ## Key Components
76//!
77//! - [`SlruCore`]: Main SLRU cache implementation
78//!
79//! ## Operations
80//!
81//! | Operation   | Time   | Notes                                      |
82//! |-------------|--------|--------------------------------------------|
83//! | `get`       | O(1)   | May promote from probationary to protected |
84//! | `peek`      | O(1)   | Read without promotion                     |
85//! | `insert`    | O(1)*  | *Amortized, may trigger evictions          |
86//! | `contains`  | O(1)   | Index lookup only                          |
87//! | `len`       | O(1)   | Returns total entries                      |
88//! | `clear`     | O(n)   | Clears all structures                      |
89//!
90//! ## Algorithm Properties
91//!
92//! - **Scan Resistance**: One-time accesses stay in probationary, don't pollute protected
93//! - **Frequency Awareness**: Repeated access promotes to protected LRU
94//! - **Tunable**: `probationary_frac` controls probationary/protected size ratio
95//!
96//! ## Use Cases
97//!
98//! - Database buffer pools (scan-heavy workloads)
99//! - File system caches
100//! - Web caches with mixed access patterns
101//!
102//! ## Example Usage
103//!
104//! ```
105//! use cachekit::policy::slru::SlruCore;
106//!
107//! // Create SLRU cache: 100 total capacity, 25% for probationary
108//! let mut cache = SlruCore::new(100, 0.25);
109//!
110//! // Insert items (go to probationary)
111//! cache.insert("page1", "content1");
112//! cache.insert("page2", "content2");
113//!
114//! // First access promotes to protected
115//! assert_eq!(cache.get(&"page1"), Some(&"content1"));
116//!
117//! // Second access keeps in protected (MRU position)
118//! assert_eq!(cache.get(&"page1"), Some(&"content1"));
119//!
120//! assert_eq!(cache.len(), 2);
121//! ```
122//!
123//! ## Removal Policy
124//!
125//! `SlruCore` supports key removal via [`Cache::remove`].
126//! Removing an entry detaches it from its segment and adjusts the segment counters.
127//!
128//! ## Thread Safety
129//!
130//! - [`SlruCore`]: Not thread-safe, designed for single-threaded use
131//! - For concurrent access, wrap in external synchronization
132//!
133//! ## Implementation Notes
134//!
135//! - Probationary uses LRU ordering (MRU at front, evict from back)
136//! - Protected uses LRU ordering (MRU at front, evict from back)
137//! - Promotion from probationary to protected happens on re-access
138//! - Default `probationary_frac` of 0.25 means 25% of capacity for probationary
139//!
140//! ## References
141//!
142//! - Karedla et al., "Caching Strategies to Improve Disk System Performance", 1994
143//! - Wikipedia: Cache replacement policies
144
145#[cfg(feature = "metrics")]
146use crate::metrics::metrics_impl::SlruMetrics;
147#[cfg(feature = "metrics")]
148use crate::metrics::snapshot::SlruMetricsSnapshot;
149#[cfg(feature = "metrics")]
150use crate::metrics::traits::{CoreMetricsRecorder, MetricsSnapshotProvider, SlruMetricsRecorder};
151use crate::traits::Cache;
152use rustc_hash::FxHashMap;
153use std::hash::Hash;
154use std::iter::FusedIterator;
155use std::marker::PhantomData;
156use std::ptr::NonNull;
157
158/// Indicates which segment an entry resides in.
159#[derive(Copy, Clone, Debug, Eq, PartialEq)]
160enum Segment {
161    /// Entry is in the probationary segment (new/unproven entries).
162    Probationary,
163    /// Entry is in the protected segment (frequently accessed entries).
164    Protected,
165}
166
167/// Node in the SLRU linked list.
168///
169/// Cache-line optimized layout with pointers first.
170#[repr(C)]
171struct Node<K, V> {
172    prev: Option<NonNull<Node<K, V>>>,
173    next: Option<NonNull<Node<K, V>>>,
174    segment: Segment,
175    key: K,
176    value: V,
177}
178
179/// Core Segmented LRU (SLRU) cache implementation.
180///
181/// Implements the SLRU replacement algorithm with two segments:
182/// - **Probationary**: LRU queue for newly inserted items
183/// - **Protected**: LRU queue for frequently accessed items
184///
185/// New items enter probationary. Re-accessing an item in probationary promotes it
186/// to protected. This provides scan resistance by keeping one-time accesses
187/// from polluting the main cache.
188///
189/// `SlruCore` supports key removal via [`Cache::remove`].
190/// Removing an entry detaches it from its segment and adjusts the segment counters.
191///
192/// # Type Parameters
193///
194/// - `K`: Key type, must be `Clone + Eq + Hash`
195/// - `V`: Value type
196///
197/// # Example
198///
199/// ```
200/// use cachekit::policy::slru::SlruCore;
201///
202/// // 100 capacity, 25% probationary
203/// let mut cache = SlruCore::new(100, 0.25);
204///
205/// // Insert goes to probationary
206/// cache.insert("key1", "value1");
207/// assert!(cache.contains(&"key1"));
208///
209/// // First get promotes to protected
210/// cache.get(&"key1");
211///
212/// // Update existing key
213/// cache.insert("key1", "new_value");
214/// assert_eq!(cache.get(&"key1"), Some(&"new_value"));
215/// ```
216///
217/// # Eviction Behavior
218///
219/// When capacity is exceeded:
220/// 1. If probationary exceeds its cap, evict from probationary LRU
221/// 2. Otherwise, evict from protected LRU
222///
223/// # Implementation
224///
225/// Uses raw pointer linked lists for O(1) operations with minimal overhead.
226pub struct SlruCore<K, V> {
227    map: FxHashMap<K, NonNull<Node<K, V>>>,
228
229    /// Probationary segment (LRU): head=MRU, tail=LRU
230    probationary_head: Option<NonNull<Node<K, V>>>,
231    probationary_tail: Option<NonNull<Node<K, V>>>,
232    probationary_len: usize,
233
234    /// Protected segment (LRU): head=MRU, tail=LRU
235    protected_head: Option<NonNull<Node<K, V>>>,
236    protected_tail: Option<NonNull<Node<K, V>>>,
237    protected_len: usize,
238
239    probationary_cap: usize,
240    capacity: usize,
241
242    #[cfg(feature = "metrics")]
243    metrics: SlruMetrics,
244}
245
246// SAFETY: SlruCore exclusively owns all heap-allocated Node<K, V> via NonNull
247// pointers. No aliasing occurs — each node is reachable only through the map
248// or the linked lists, both owned by this struct. Sending the entire structure
249// is safe when K and V are Send because ownership transfers atomically.
250unsafe impl<K: Send, V: Send> Send for SlruCore<K, V> {}
251
252// SAFETY: A shared &SlruCore only permits read-only operations (len, capacity,
253// contains, peek, Debug). These access only the FxHashMap (Sync when K: Sync)
254// and primitive counters. The &mut self requirement on get/insert/clear prevents
255// data races on the linked-list pointers.
256unsafe impl<K: Sync, V: Sync> Sync for SlruCore<K, V> {}
257
258impl<K, V> SlruCore<K, V>
259where
260    K: Clone + Eq + Hash,
261{
262    /// Creates a new SLRU cache with the specified capacity and probationary fraction.
263    ///
264    /// A typical value for `probationary_frac` is 0.25 (25% for probationary).
265    ///
266    /// # Panics
267    ///
268    /// Panics if `probationary_frac` is not in `0.0..=1.0` or is NaN.
269    ///
270    /// # Example
271    ///
272    /// ```
273    /// use cachekit::policy::slru::SlruCore;
274    ///
275    /// // 100 capacity, 25% probationary (25 items max in probationary)
276    /// let cache: SlruCore<String, i32> = SlruCore::new(100, 0.25);
277    /// assert_eq!(cache.capacity(), 100);
278    /// assert!(cache.is_empty());
279    /// ```
280    #[inline]
281    #[must_use]
282    pub fn new(capacity: usize, probationary_frac: f64) -> Self {
283        assert!(
284            (0.0..=1.0).contains(&probationary_frac),
285            "probationary_frac must be in 0.0..=1.0, got {probationary_frac}"
286        );
287
288        let probationary_cap = (capacity as f64 * probationary_frac) as usize;
289        let total_cap = capacity + probationary_cap;
290
291        Self {
292            map: FxHashMap::with_capacity_and_hasher(total_cap, Default::default()),
293            probationary_head: None,
294            probationary_tail: None,
295            probationary_len: 0,
296            protected_head: None,
297            protected_tail: None,
298            protected_len: 0,
299            probationary_cap,
300            capacity,
301            #[cfg(feature = "metrics")]
302            metrics: SlruMetrics::default(),
303        }
304    }
305
306    /// Detach a node from its current segment.
307    #[inline(always)]
308    fn detach(&mut self, node_ptr: NonNull<Node<K, V>>) {
309        unsafe {
310            let node = node_ptr.as_ref();
311            let prev = node.prev;
312            let next = node.next;
313            let segment = node.segment;
314
315            let (head, tail, len) = match segment {
316                Segment::Probationary => (
317                    &mut self.probationary_head,
318                    &mut self.probationary_tail,
319                    &mut self.probationary_len,
320                ),
321                Segment::Protected => (
322                    &mut self.protected_head,
323                    &mut self.protected_tail,
324                    &mut self.protected_len,
325                ),
326            };
327
328            match prev {
329                Some(mut p) => p.as_mut().next = next,
330                None => *head = next,
331            }
332
333            match next {
334                Some(mut n) => n.as_mut().prev = prev,
335                None => *tail = prev,
336            }
337
338            *len -= 1;
339        }
340    }
341
342    /// Attach a node at the head of probationary segment (LRU: MRU at head).
343    #[inline(always)]
344    fn attach_probationary_head(&mut self, mut node_ptr: NonNull<Node<K, V>>) {
345        unsafe {
346            let node = node_ptr.as_mut();
347            node.prev = None;
348            node.next = self.probationary_head;
349            node.segment = Segment::Probationary;
350
351            match self.probationary_head {
352                Some(mut h) => h.as_mut().prev = Some(node_ptr),
353                None => self.probationary_tail = Some(node_ptr),
354            }
355
356            self.probationary_head = Some(node_ptr);
357            self.probationary_len += 1;
358        }
359    }
360
361    /// Attach a node at the head of protected segment (LRU: MRU at head).
362    #[inline(always)]
363    fn attach_protected_head(&mut self, mut node_ptr: NonNull<Node<K, V>>) {
364        unsafe {
365            let node = node_ptr.as_mut();
366            node.prev = None;
367            node.next = self.protected_head;
368            node.segment = Segment::Protected;
369
370            match self.protected_head {
371                Some(mut h) => h.as_mut().prev = Some(node_ptr),
372                None => self.protected_tail = Some(node_ptr),
373            }
374
375            self.protected_head = Some(node_ptr);
376            self.protected_len += 1;
377        }
378    }
379
380    /// Pop from probationary tail (LRU: LRU at tail).
381    #[inline(always)]
382    fn pop_probationary_tail(&mut self) -> Option<Box<Node<K, V>>> {
383        self.probationary_tail.map(|tail_ptr| unsafe {
384            let node = Box::from_raw(tail_ptr.as_ptr());
385
386            self.probationary_tail = node.prev;
387            match self.probationary_tail {
388                Some(mut t) => t.as_mut().next = None,
389                None => self.probationary_head = None,
390            }
391            self.probationary_len -= 1;
392
393            node
394        })
395    }
396
397    /// Pop from protected tail (LRU: LRU at tail).
398    #[inline(always)]
399    fn pop_protected_tail(&mut self) -> Option<Box<Node<K, V>>> {
400        self.protected_tail.map(|tail_ptr| unsafe {
401            let node = Box::from_raw(tail_ptr.as_ptr());
402
403            self.protected_tail = node.prev;
404            match self.protected_tail {
405                Some(mut t) => t.as_mut().next = None,
406                None => self.protected_head = None,
407            }
408            self.protected_len -= 1;
409
410            node
411        })
412    }
413
414    /// Returns a reference to the value without affecting segment position.
415    ///
416    /// Unlike [`get`](Self::get), this does not promote entries from probationary
417    /// to protected and does not update MRU position.
418    ///
419    /// # Example
420    ///
421    /// ```
422    /// use cachekit::policy::slru::SlruCore;
423    ///
424    /// let mut cache = SlruCore::new(100, 0.25);
425    /// cache.insert("key", 42);
426    ///
427    /// // Peek does not trigger promotion
428    /// assert_eq!(cache.peek(&"key"), Some(&42));
429    /// assert_eq!(cache.peek(&"missing"), None);
430    /// ```
431    #[inline]
432    pub fn peek(&self, key: &K) -> Option<&V> {
433        self.map.get(key).map(|&ptr| unsafe { &ptr.as_ref().value })
434    }
435
436    /// Retrieves a value by key, promoting from probationary to protected if needed.
437    ///
438    /// If the key is in probationary, accessing it promotes the entry to the
439    /// protected segment (demonstrating it's not a one-time access).
440    /// If already in protected, moves it to the MRU position.
441    ///
442    /// # Example
443    ///
444    /// ```
445    /// use cachekit::policy::slru::SlruCore;
446    ///
447    /// let mut cache = SlruCore::new(100, 0.25);
448    /// cache.insert("key", 42);
449    ///
450    /// // First access: in probationary, now promotes to protected
451    /// assert_eq!(cache.get(&"key"), Some(&42));
452    ///
453    /// // Second access: already in protected, moves to MRU
454    /// assert_eq!(cache.get(&"key"), Some(&42));
455    ///
456    /// // Missing key
457    /// assert_eq!(cache.get(&"missing"), None);
458    /// ```
459    #[inline]
460    pub fn get(&mut self, key: &K) -> Option<&V> {
461        let node_ptr = match self.map.get(key) {
462            Some(&ptr) => ptr,
463            None => {
464                #[cfg(feature = "metrics")]
465                self.metrics.record_get_miss();
466                return None;
467            },
468        };
469
470        #[cfg(feature = "metrics")]
471        self.metrics.record_get_hit();
472
473        let segment = unsafe { node_ptr.as_ref().segment };
474
475        match segment {
476            Segment::Probationary => {
477                self.detach(node_ptr);
478                self.attach_protected_head(node_ptr);
479
480                #[cfg(feature = "metrics")]
481                self.metrics.record_probationary_to_protected();
482            },
483            Segment::Protected => {
484                self.detach(node_ptr);
485                self.attach_protected_head(node_ptr);
486            },
487        }
488
489        unsafe { Some(&node_ptr.as_ref().value) }
490    }
491
492    /// Inserts or updates a key-value pair.
493    ///
494    /// - If the key exists, updates the value in place (no segment change)
495    /// - If the key is new, inserts into the probationary segment
496    /// - May trigger eviction if capacity is exceeded
497    ///
498    /// # Example
499    ///
500    /// ```
501    /// use cachekit::policy::slru::SlruCore;
502    ///
503    /// let mut cache = SlruCore::new(100, 0.25);
504    ///
505    /// // New insert goes to probationary
506    /// cache.insert("key", "initial");
507    /// assert_eq!(cache.len(), 1);
508    ///
509    /// // Update existing key
510    /// cache.insert("key", "updated");
511    /// assert_eq!(cache.get(&"key"), Some(&"updated"));
512    /// assert_eq!(cache.len(), 1);  // Still 1 entry
513    /// ```
514    #[inline]
515    pub fn insert(&mut self, key: K, value: V) -> Option<V> {
516        #[cfg(feature = "metrics")]
517        self.metrics.record_insert_call();
518
519        if self.capacity == 0 {
520            return None;
521        }
522
523        if let Some(&node_ptr) = self.map.get(&key) {
524            #[cfg(feature = "metrics")]
525            self.metrics.record_insert_update();
526
527            let old = unsafe { std::mem::replace(&mut (*node_ptr.as_ptr()).value, value) };
528            return Some(old);
529        }
530
531        #[cfg(feature = "metrics")]
532        self.metrics.record_insert_new();
533
534        self.evict_if_needed();
535
536        let node = Box::new(Node {
537            prev: None,
538            next: None,
539            segment: Segment::Probationary,
540            key: key.clone(),
541            value,
542        });
543        let node_ptr = NonNull::new(Box::into_raw(node)).unwrap();
544
545        self.map.insert(key, node_ptr);
546        self.attach_probationary_head(node_ptr);
547
548        #[cfg(debug_assertions)]
549        self.validate_invariants();
550        None
551    }
552
553    /// Evicts entries until there is room for a new entry.
554    #[inline]
555    fn evict_if_needed(&mut self) {
556        while self.len() >= self.capacity {
557            #[cfg(feature = "metrics")]
558            self.metrics.record_evict_call();
559
560            if self.probationary_len > self.probationary_cap {
561                if let Some(node) = self.pop_probationary_tail() {
562                    self.map.remove(&node.key);
563                    #[cfg(feature = "metrics")]
564                    self.metrics.record_evicted_entry();
565                    continue;
566                }
567            }
568            if let Some(node) = self.pop_protected_tail() {
569                self.map.remove(&node.key);
570                #[cfg(feature = "metrics")]
571                {
572                    self.metrics.record_evicted_entry();
573                    self.metrics.record_protected_eviction();
574                }
575                continue;
576            }
577            if let Some(node) = self.pop_probationary_tail() {
578                self.map.remove(&node.key);
579                #[cfg(feature = "metrics")]
580                self.metrics.record_evicted_entry();
581                continue;
582            }
583            break;
584        }
585    }
586
587    /// Returns the number of entries in the cache.
588    ///
589    /// # Example
590    ///
591    /// ```
592    /// use cachekit::policy::slru::SlruCore;
593    ///
594    /// let mut cache = SlruCore::new(100, 0.25);
595    /// assert_eq!(cache.len(), 0);
596    ///
597    /// cache.insert("a", 1);
598    /// cache.insert("b", 2);
599    /// assert_eq!(cache.len(), 2);
600    /// ```
601    #[inline]
602    pub fn len(&self) -> usize {
603        self.map.len()
604    }
605
606    /// Returns `true` if the cache is empty.
607    ///
608    /// # Example
609    ///
610    /// ```
611    /// use cachekit::policy::slru::SlruCore;
612    ///
613    /// let mut cache: SlruCore<&str, i32> = SlruCore::new(100, 0.25);
614    /// assert!(cache.is_empty());
615    ///
616    /// cache.insert("key", 42);
617    /// assert!(!cache.is_empty());
618    /// ```
619    #[inline]
620    pub fn is_empty(&self) -> bool {
621        self.map.is_empty()
622    }
623
624    /// Returns the total cache capacity.
625    ///
626    /// # Example
627    ///
628    /// ```
629    /// use cachekit::policy::slru::SlruCore;
630    ///
631    /// let cache: SlruCore<String, i32> = SlruCore::new(500, 0.25);
632    /// assert_eq!(cache.capacity(), 500);
633    /// ```
634    #[inline]
635    pub fn capacity(&self) -> usize {
636        self.capacity
637    }
638
639    /// Returns `true` if the key exists in the cache.
640    ///
641    /// Does not affect segment positions (no promotion on contains).
642    ///
643    /// # Example
644    ///
645    /// ```
646    /// use cachekit::policy::slru::SlruCore;
647    ///
648    /// let mut cache = SlruCore::new(100, 0.25);
649    /// cache.insert("key", 42);
650    ///
651    /// assert!(cache.contains(&"key"));
652    /// assert!(!cache.contains(&"missing"));
653    /// ```
654    #[inline]
655    pub fn contains(&self, key: &K) -> bool {
656        self.map.contains_key(key)
657    }
658
659    /// Returns the number of entries in the probationary segment.
660    #[inline]
661    pub fn probationary_len(&self) -> usize {
662        self.probationary_len
663    }
664
665    /// Returns the number of entries in the protected segment.
666    #[inline]
667    pub fn protected_len(&self) -> usize {
668        self.protected_len
669    }
670
671    /// Clears all entries from the cache.
672    ///
673    /// # Example
674    ///
675    /// ```
676    /// use cachekit::policy::slru::SlruCore;
677    ///
678    /// let mut cache = SlruCore::new(100, 0.25);
679    /// cache.insert("a", 1);
680    /// cache.insert("b", 2);
681    ///
682    /// cache.clear();
683    /// assert!(cache.is_empty());
684    /// assert!(!cache.contains(&"a"));
685    /// ```
686    pub fn clear(&mut self) {
687        #[cfg(feature = "metrics")]
688        self.metrics.record_clear();
689
690        while self.pop_probationary_tail().is_some() {}
691        while self.pop_protected_tail().is_some() {}
692        self.map.clear();
693
694        #[cfg(debug_assertions)]
695        self.validate_invariants();
696    }
697
698    /// Removes a specific key-value pair, returning the value if it existed.
699    ///
700    /// Detaches the entry from its segment (probationary or protected) and
701    /// adjusts the segment counters.
702    ///
703    /// # Example
704    ///
705    /// ```
706    /// use cachekit::policy::slru::SlruCore;
707    ///
708    /// let mut cache = SlruCore::new(100, 0.25);
709    /// cache.insert("key", 42);
710    /// assert_eq!(cache.remove(&"key"), Some(42));
711    /// assert!(!cache.contains(&"key"));
712    /// ```
713    pub fn remove(&mut self, key: &K) -> Option<V> {
714        let node_ptr = self.map.remove(key)?;
715        self.detach(node_ptr);
716        unsafe {
717            let node = Box::from_raw(node_ptr.as_ptr());
718            Some(node.value)
719        }
720    }
721
722    /// Returns an iterator over shared references to cached key-value pairs.
723    ///
724    /// Visits probationary entries (MRU to LRU) then protected entries.
725    pub fn iter(&self) -> Iter<'_, K, V> {
726        Iter {
727            current: self.probationary_head,
728            protected_head: self.protected_head,
729            in_protected: false,
730            remaining: self.probationary_len + self.protected_len,
731            _marker: PhantomData,
732        }
733    }
734
735    /// Returns a mutable iterator over cached key-value pairs.
736    ///
737    /// Keys are yielded as shared references; values as mutable references.
738    /// Modifying values does not affect eviction ordering.
739    pub fn iter_mut(&mut self) -> IterMut<'_, K, V> {
740        IterMut {
741            current: self.probationary_head,
742            protected_head: self.protected_head,
743            in_protected: false,
744            remaining: self.probationary_len + self.protected_len,
745            _marker: PhantomData,
746        }
747    }
748
749    /// Validates internal data structure invariants.
750    ///
751    /// This method checks that:
752    /// - All nodes in map are reachable from either probationary or protected lists
753    /// - List lengths match tracked counts
754    /// - No cycles exist in the lists
755    /// - All nodes have valid prev/next pointers
756    ///
757    /// Only runs when debug assertions are enabled.
758    #[cfg(debug_assertions)]
759    fn validate_invariants(&self) {
760        let mut prob_count = 0;
761        let mut current = self.probationary_head;
762        let mut visited = std::collections::HashSet::new();
763
764        while let Some(ptr) = current {
765            prob_count += 1;
766            assert!(visited.insert(ptr), "Cycle detected in probationary list");
767            assert!(
768                prob_count <= self.map.len(),
769                "Probationary count exceeds total entries"
770            );
771
772            unsafe {
773                let node = ptr.as_ref();
774                assert!(
775                    matches!(node.segment, Segment::Probationary),
776                    "Non-probationary node in probationary list"
777                );
778                current = node.next;
779            }
780        }
781
782        debug_assert_eq!(
783            prob_count, self.probationary_len,
784            "Probationary count mismatch"
785        );
786
787        let mut prot_count = 0;
788        let mut current = self.protected_head;
789        visited.clear();
790
791        while let Some(ptr) = current {
792            prot_count += 1;
793            assert!(visited.insert(ptr), "Cycle detected in protected list");
794            assert!(
795                prot_count <= self.map.len(),
796                "Protected count exceeds total entries"
797            );
798
799            unsafe {
800                let node = ptr.as_ref();
801                assert!(
802                    matches!(node.segment, Segment::Protected),
803                    "Non-protected node in protected list"
804                );
805                current = node.next;
806            }
807        }
808
809        debug_assert_eq!(prot_count, self.protected_len, "Protected count mismatch");
810
811        debug_assert_eq!(
812            prob_count + prot_count,
813            self.map.len(),
814            "List counts don't match map size"
815        );
816
817        for &node_ptr in self.map.values() {
818            unsafe {
819                let node = node_ptr.as_ref();
820                match node.segment {
821                    Segment::Probationary => {
822                        debug_assert!(prob_count > 0, "Node marked probationary but list empty");
823                    },
824                    Segment::Protected => {
825                        debug_assert!(prot_count > 0, "Node marked protected but list empty");
826                    },
827                }
828            }
829        }
830    }
831}
832
833#[cfg(feature = "metrics")]
834impl<K, V> SlruCore<K, V>
835where
836    K: Clone + Eq + Hash,
837{
838    pub fn metrics_snapshot(&self) -> SlruMetricsSnapshot {
839        SlruMetricsSnapshot {
840            get_calls: self.metrics.get_calls,
841            get_hits: self.metrics.get_hits,
842            get_misses: self.metrics.get_misses,
843            insert_calls: self.metrics.insert_calls,
844            insert_updates: self.metrics.insert_updates,
845            insert_new: self.metrics.insert_new,
846            evict_calls: self.metrics.evict_calls,
847            evicted_entries: self.metrics.evicted_entries,
848            probationary_to_protected: self.metrics.probationary_to_protected,
849            protected_evictions: self.metrics.protected_evictions,
850            cache_len: self.len(),
851            capacity: self.capacity(),
852        }
853    }
854}
855
856#[cfg(feature = "metrics")]
857impl<K, V> MetricsSnapshotProvider<SlruMetricsSnapshot> for SlruCore<K, V>
858where
859    K: Clone + Eq + Hash,
860{
861    fn snapshot(&self) -> SlruMetricsSnapshot {
862        self.metrics_snapshot()
863    }
864}
865
866impl<K, V> Drop for SlruCore<K, V> {
867    fn drop(&mut self) {
868        unsafe {
869            let mut current = self.probationary_head.take();
870            while let Some(ptr) = current {
871                current = ptr.as_ref().next;
872                drop(Box::from_raw(ptr.as_ptr()));
873            }
874            let mut current = self.protected_head.take();
875            while let Some(ptr) = current {
876                current = ptr.as_ref().next;
877                drop(Box::from_raw(ptr.as_ptr()));
878            }
879        }
880    }
881}
882
883impl<K, V> std::fmt::Debug for SlruCore<K, V>
884where
885    K: std::fmt::Debug,
886{
887    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
888        f.debug_struct("SlruCore")
889            .field("capacity", &self.capacity)
890            .field("probationary_cap", &self.probationary_cap)
891            .field("len", &self.map.len())
892            .field("probationary_len", &self.probationary_len)
893            .field("protected_len", &self.protected_len)
894            .finish_non_exhaustive()
895    }
896}
897
898impl<K, V> Default for SlruCore<K, V>
899where
900    K: Clone + Eq + Hash,
901{
902    /// Returns an empty `SlruCore` with capacity 0.
903    ///
904    /// Use [`SlruCore::new`] to specify a capacity.
905    fn default() -> Self {
906        Self::new(0, 0.25)
907    }
908}
909
910impl<K, V> Clone for SlruCore<K, V>
911where
912    K: Clone + Eq + Hash,
913    V: Clone,
914{
915    fn clone(&self) -> Self {
916        let mut new_cache = SlruCore::new(
917            self.capacity,
918            if self.capacity > 0 {
919                self.probationary_cap as f64 / self.capacity as f64
920            } else {
921                0.25
922            },
923        );
924
925        // Rebuild probationary from tail (LRU) to head (MRU) so attach_head
926        // reproduces the original ordering.
927        let mut prob_entries = Vec::with_capacity(self.probationary_len);
928        let mut current = self.probationary_tail;
929        while let Some(ptr) = current {
930            unsafe {
931                let node = ptr.as_ref();
932                prob_entries.push((node.key.clone(), node.value.clone()));
933                current = node.prev;
934            }
935        }
936        for (key, value) in prob_entries {
937            let node = Box::new(Node {
938                prev: None,
939                next: None,
940                segment: Segment::Probationary,
941                key: key.clone(),
942                value,
943            });
944            let ptr = NonNull::new(Box::into_raw(node)).unwrap();
945            new_cache.map.insert(key, ptr);
946            new_cache.attach_probationary_head(ptr);
947        }
948
949        let mut prot_entries = Vec::with_capacity(self.protected_len);
950        let mut current = self.protected_tail;
951        while let Some(ptr) = current {
952            unsafe {
953                let node = ptr.as_ref();
954                prot_entries.push((node.key.clone(), node.value.clone()));
955                current = node.prev;
956            }
957        }
958        for (key, value) in prot_entries {
959            let node = Box::new(Node {
960                prev: None,
961                next: None,
962                segment: Segment::Protected,
963                key: key.clone(),
964                value,
965            });
966            let ptr = NonNull::new(Box::into_raw(node)).unwrap();
967            new_cache.map.insert(key, ptr);
968            new_cache.attach_protected_head(ptr);
969        }
970
971        #[cfg(feature = "metrics")]
972        {
973            new_cache.metrics = self.metrics;
974        }
975
976        new_cache
977    }
978}
979
980/// Implementation of the [`Cache`] trait for SLRU.
981///
982/// Allows `SlruCore` to be used through the unified cache interface.
983///
984/// # Example
985///
986/// ```
987/// use cachekit::traits::Cache;
988/// use cachekit::policy::slru::SlruCore;
989///
990/// let mut cache: SlruCore<&str, i32> = SlruCore::new(100, 0.25);
991///
992/// // Use via Cache trait
993/// cache.insert("key", 42);
994/// assert_eq!(cache.get(&"key"), Some(&42));
995/// assert!(cache.contains(&"key"));
996/// ```
997impl<K, V> Cache<K, V> for SlruCore<K, V>
998where
999    K: Clone + Eq + Hash,
1000{
1001    #[inline]
1002    fn contains(&self, key: &K) -> bool {
1003        SlruCore::contains(self, key)
1004    }
1005
1006    #[inline]
1007    fn len(&self) -> usize {
1008        SlruCore::len(self)
1009    }
1010
1011    #[inline]
1012    fn capacity(&self) -> usize {
1013        SlruCore::capacity(self)
1014    }
1015
1016    #[inline]
1017    fn peek(&self, key: &K) -> Option<&V> {
1018        SlruCore::peek(self, key)
1019    }
1020
1021    #[inline]
1022    fn get(&mut self, key: &K) -> Option<&V> {
1023        SlruCore::get(self, key)
1024    }
1025
1026    #[inline]
1027    fn insert(&mut self, key: K, value: V) -> Option<V> {
1028        SlruCore::insert(self, key, value)
1029    }
1030
1031    #[inline]
1032    fn remove(&mut self, key: &K) -> Option<V> {
1033        SlruCore::remove(self, key)
1034    }
1035
1036    fn clear(&mut self) {
1037        SlruCore::clear(self);
1038    }
1039}
1040
1041// ---------------------------------------------------------------------------
1042// Iterators
1043// ---------------------------------------------------------------------------
1044
1045/// Iterator over shared references to cached key-value pairs.
1046///
1047/// Created by [`SlruCore::iter`]. Visits probationary entries (MRU to LRU)
1048/// then protected entries.
1049pub struct Iter<'a, K, V> {
1050    current: Option<NonNull<Node<K, V>>>,
1051    protected_head: Option<NonNull<Node<K, V>>>,
1052    in_protected: bool,
1053    remaining: usize,
1054    _marker: PhantomData<&'a (K, V)>,
1055}
1056
1057impl<'a, K, V> Iterator for Iter<'a, K, V> {
1058    type Item = (&'a K, &'a V);
1059
1060    fn next(&mut self) -> Option<Self::Item> {
1061        loop {
1062            if let Some(node_ptr) = self.current {
1063                unsafe {
1064                    let node = node_ptr.as_ref();
1065                    self.current = node.next;
1066                    self.remaining -= 1;
1067                    return Some((&node.key, &node.value));
1068                }
1069            } else if !self.in_protected {
1070                self.in_protected = true;
1071                self.current = self.protected_head;
1072            } else {
1073                return None;
1074            }
1075        }
1076    }
1077
1078    fn size_hint(&self) -> (usize, Option<usize>) {
1079        (self.remaining, Some(self.remaining))
1080    }
1081}
1082
1083impl<K, V> ExactSizeIterator for Iter<'_, K, V> {}
1084impl<K, V> FusedIterator for Iter<'_, K, V> {}
1085
1086/// Iterator over mutable references to cached values.
1087///
1088/// Created by [`SlruCore::iter_mut`]. Keys are shared; values are mutable.
1089pub struct IterMut<'a, K, V> {
1090    current: Option<NonNull<Node<K, V>>>,
1091    protected_head: Option<NonNull<Node<K, V>>>,
1092    in_protected: bool,
1093    remaining: usize,
1094    _marker: PhantomData<&'a mut (K, V)>,
1095}
1096
1097impl<'a, K, V> Iterator for IterMut<'a, K, V> {
1098    type Item = (&'a K, &'a mut V);
1099
1100    fn next(&mut self) -> Option<Self::Item> {
1101        loop {
1102            if let Some(mut node_ptr) = self.current {
1103                unsafe {
1104                    let node = node_ptr.as_mut();
1105                    self.current = node.next;
1106                    self.remaining -= 1;
1107                    return Some((&node.key, &mut node.value));
1108                }
1109            } else if !self.in_protected {
1110                self.in_protected = true;
1111                self.current = self.protected_head;
1112            } else {
1113                return None;
1114            }
1115        }
1116    }
1117
1118    fn size_hint(&self) -> (usize, Option<usize>) {
1119        (self.remaining, Some(self.remaining))
1120    }
1121}
1122
1123impl<K, V> ExactSizeIterator for IterMut<'_, K, V> {}
1124impl<K, V> FusedIterator for IterMut<'_, K, V> {}
1125
1126/// Owning iterator over cached key-value pairs.
1127///
1128/// Created by calling [`IntoIterator`] on an `SlruCore`.
1129pub struct IntoIter<K, V> {
1130    current: Option<NonNull<Node<K, V>>>,
1131    protected_head: Option<NonNull<Node<K, V>>>,
1132    in_protected: bool,
1133    remaining: usize,
1134}
1135
1136impl<K, V> Iterator for IntoIter<K, V> {
1137    type Item = (K, V);
1138
1139    fn next(&mut self) -> Option<Self::Item> {
1140        loop {
1141            if let Some(node_ptr) = self.current {
1142                unsafe {
1143                    let node = Box::from_raw(node_ptr.as_ptr());
1144                    self.current = node.next;
1145                    self.remaining -= 1;
1146                    return Some((node.key, node.value));
1147                }
1148            } else if !self.in_protected {
1149                self.in_protected = true;
1150                self.current = self.protected_head;
1151            } else {
1152                return None;
1153            }
1154        }
1155    }
1156
1157    fn size_hint(&self) -> (usize, Option<usize>) {
1158        (self.remaining, Some(self.remaining))
1159    }
1160}
1161
1162impl<K, V> ExactSizeIterator for IntoIter<K, V> {}
1163impl<K, V> FusedIterator for IntoIter<K, V> {}
1164
1165impl<K, V> Drop for IntoIter<K, V> {
1166    fn drop(&mut self) {
1167        while self.next().is_some() {}
1168    }
1169}
1170
1171// SAFETY: IntoIter exclusively owns its nodes (moved out of SlruCore).
1172unsafe impl<K: Send, V: Send> Send for IntoIter<K, V> {}
1173unsafe impl<K: Sync, V: Sync> Sync for IntoIter<K, V> {}
1174
1175impl<K, V> IntoIterator for SlruCore<K, V> {
1176    type Item = (K, V);
1177    type IntoIter = IntoIter<K, V>;
1178
1179    fn into_iter(mut self) -> IntoIter<K, V> {
1180        let iter = IntoIter {
1181            current: self.probationary_head,
1182            protected_head: self.protected_head,
1183            in_protected: false,
1184            remaining: self.probationary_len + self.protected_len,
1185        };
1186        // Prevent Drop from double-freeing nodes the iterator now owns.
1187        self.probationary_head = None;
1188        self.probationary_tail = None;
1189        self.probationary_len = 0;
1190        self.protected_head = None;
1191        self.protected_tail = None;
1192        self.protected_len = 0;
1193        iter
1194    }
1195}
1196
1197impl<'a, K, V> IntoIterator for &'a SlruCore<K, V>
1198where
1199    K: Clone + Eq + Hash,
1200{
1201    type Item = (&'a K, &'a V);
1202    type IntoIter = Iter<'a, K, V>;
1203
1204    fn into_iter(self) -> Iter<'a, K, V> {
1205        self.iter()
1206    }
1207}
1208
1209impl<'a, K, V> IntoIterator for &'a mut SlruCore<K, V>
1210where
1211    K: Clone + Eq + Hash,
1212{
1213    type Item = (&'a K, &'a mut V);
1214    type IntoIter = IterMut<'a, K, V>;
1215
1216    fn into_iter(self) -> IterMut<'a, K, V> {
1217        self.iter_mut()
1218    }
1219}
1220
1221impl<K, V> FromIterator<(K, V)> for SlruCore<K, V>
1222where
1223    K: Clone + Eq + Hash,
1224{
1225    fn from_iter<I: IntoIterator<Item = (K, V)>>(iter: I) -> Self {
1226        let iter = iter.into_iter();
1227        let (lower, _) = iter.size_hint();
1228        let mut cache = SlruCore::new(lower.max(16), 0.25);
1229        cache.extend(iter);
1230        cache
1231    }
1232}
1233
1234impl<K, V> Extend<(K, V)> for SlruCore<K, V>
1235where
1236    K: Clone + Eq + Hash,
1237{
1238    fn extend<I: IntoIterator<Item = (K, V)>>(&mut self, iter: I) {
1239        for (k, v) in iter {
1240            self.insert(k, v);
1241        }
1242    }
1243}
1244
1245#[cfg(test)]
1246mod tests {
1247    use super::*;
1248
1249    // ==============================================
1250    // SlruCore Basic Operations
1251    // ==============================================
1252
1253    mod basic_operations {
1254        use super::*;
1255
1256        #[test]
1257        fn new_cache_is_empty() {
1258            let cache: SlruCore<&str, i32> = SlruCore::new(100, 0.25);
1259            assert!(cache.is_empty());
1260            assert_eq!(cache.len(), 0);
1261            assert_eq!(cache.capacity(), 100);
1262        }
1263
1264        #[test]
1265        fn insert_and_get() {
1266            let mut cache = SlruCore::new(100, 0.25);
1267            cache.insert("key1", "value1");
1268
1269            assert_eq!(cache.len(), 1);
1270            assert_eq!(cache.get(&"key1"), Some(&"value1"));
1271        }
1272
1273        #[test]
1274        fn insert_multiple_items() {
1275            let mut cache = SlruCore::new(100, 0.25);
1276            cache.insert("a", 1);
1277            cache.insert("b", 2);
1278            cache.insert("c", 3);
1279
1280            assert_eq!(cache.len(), 3);
1281            assert_eq!(cache.get(&"a"), Some(&1));
1282            assert_eq!(cache.get(&"b"), Some(&2));
1283            assert_eq!(cache.get(&"c"), Some(&3));
1284        }
1285
1286        #[test]
1287        fn get_missing_key_returns_none() {
1288            let mut cache: SlruCore<&str, i32> = SlruCore::new(100, 0.25);
1289            cache.insert("exists", 42);
1290
1291            assert_eq!(cache.get(&"missing"), None);
1292        }
1293
1294        #[test]
1295        fn update_existing_key() {
1296            let mut cache = SlruCore::new(100, 0.25);
1297            cache.insert("key", "initial");
1298            cache.insert("key", "updated");
1299
1300            assert_eq!(cache.len(), 1);
1301            assert_eq!(cache.get(&"key"), Some(&"updated"));
1302        }
1303
1304        #[test]
1305        fn contains_returns_correct_result() {
1306            let mut cache = SlruCore::new(100, 0.25);
1307            cache.insert("exists", 1);
1308
1309            assert!(cache.contains(&"exists"));
1310            assert!(!cache.contains(&"missing"));
1311        }
1312
1313        #[test]
1314        fn contains_does_not_promote() {
1315            let mut cache: SlruCore<String, i32> = SlruCore::new(10, 0.3);
1316            cache.insert("a".to_string(), 1);
1317            cache.insert("b".to_string(), 2);
1318            cache.insert("c".to_string(), 3);
1319
1320            assert!(cache.contains(&"a".to_string()));
1321            assert!(cache.contains(&"b".to_string()));
1322            assert!(cache.contains(&"c".to_string()));
1323
1324            for i in 0..10 {
1325                cache.insert(format!("new{}", i), i);
1326            }
1327
1328            assert!(!cache.contains(&"a".to_string()));
1329            assert!(!cache.contains(&"b".to_string()));
1330            assert!(!cache.contains(&"c".to_string()));
1331        }
1332
1333        #[test]
1334        fn clear_removes_all_entries() {
1335            let mut cache = SlruCore::new(100, 0.25);
1336            cache.insert("a", 1);
1337            cache.insert("b", 2);
1338            cache.get(&"a");
1339
1340            cache.clear();
1341
1342            assert!(cache.is_empty());
1343            assert_eq!(cache.len(), 0);
1344            assert!(!cache.contains(&"a"));
1345            assert!(!cache.contains(&"b"));
1346        }
1347
1348        #[test]
1349        fn capacity_returns_correct_value() {
1350            let cache: SlruCore<i32, i32> = SlruCore::new(500, 0.25);
1351            assert_eq!(cache.capacity(), 500);
1352        }
1353    }
1354
1355    // ==============================================
1356    // Peek
1357    // ==============================================
1358
1359    mod peek_tests {
1360        use super::*;
1361
1362        #[test]
1363        fn peek_returns_value_without_promotion() {
1364            let mut cache = SlruCore::new(100, 0.25);
1365            cache.insert("key", 42);
1366
1367            assert_eq!(cache.peek(&"key"), Some(&42));
1368            assert_eq!(cache.peek(&"missing"), None);
1369        }
1370
1371        #[test]
1372        fn peek_does_not_promote_to_protected() {
1373            let mut cache: SlruCore<String, i32> = SlruCore::new(10, 0.3);
1374            cache.insert("key".to_string(), 1);
1375
1376            // Peek many times — should NOT promote
1377            for _ in 0..10 {
1378                assert_eq!(cache.peek(&"key".to_string()), Some(&1));
1379            }
1380
1381            // Fill up to trigger eviction — "key" should be evicted from probationary
1382            for i in 0..12 {
1383                cache.insert(format!("filler{}", i), i);
1384            }
1385
1386            assert!(!cache.contains(&"key".to_string()));
1387        }
1388    }
1389
1390    // ==============================================
1391    // Segment Behavior (Probationary vs Protected)
1392    // ==============================================
1393
1394    mod segment_behavior {
1395        use super::*;
1396
1397        #[test]
1398        fn new_insert_goes_to_probationary() {
1399            let mut cache = SlruCore::new(10, 0.3);
1400            cache.insert("key", "value");
1401
1402            assert!(cache.contains(&"key"));
1403            assert_eq!(cache.len(), 1);
1404            assert_eq!(cache.probationary_len(), 1);
1405            assert_eq!(cache.protected_len(), 0);
1406        }
1407
1408        #[test]
1409        fn get_promotes_from_probationary_to_protected() {
1410            let mut cache: SlruCore<String, i32> = SlruCore::new(10, 0.3);
1411            cache.insert("key".to_string(), 0);
1412
1413            let _ = cache.get(&"key".to_string());
1414
1415            for i in 0..12 {
1416                cache.insert(format!("new{}", i), i);
1417            }
1418
1419            assert!(cache.contains(&"key".to_string()));
1420        }
1421
1422        #[test]
1423        fn item_in_protected_stays_in_protected() {
1424            let mut cache = SlruCore::new(10, 0.3);
1425            cache.insert("key", "value");
1426
1427            cache.get(&"key");
1428            cache.get(&"key");
1429            cache.get(&"key");
1430
1431            assert_eq!(cache.get(&"key"), Some(&"value"));
1432        }
1433
1434        #[test]
1435        fn multiple_accesses_keep_item_alive() {
1436            let mut cache: SlruCore<String, i32> = SlruCore::new(10, 0.3);
1437
1438            cache.insert("hot".to_string(), 0);
1439            cache.get(&"hot".to_string());
1440
1441            for i in 0..15 {
1442                cache.insert(format!("cold{}", i), i);
1443                cache.get(&"hot".to_string());
1444            }
1445
1446            assert!(cache.contains(&"hot".to_string()));
1447        }
1448
1449        #[test]
1450        fn segment_len_accessors() {
1451            let mut cache = SlruCore::new(100, 0.25);
1452            cache.insert("a", 1);
1453            cache.insert("b", 2);
1454            assert_eq!(cache.probationary_len(), 2);
1455            assert_eq!(cache.protected_len(), 0);
1456
1457            cache.get(&"a");
1458            assert_eq!(cache.probationary_len(), 1);
1459            assert_eq!(cache.protected_len(), 1);
1460        }
1461    }
1462
1463    // ==============================================
1464    // Eviction Behavior
1465    // ==============================================
1466
1467    mod eviction_behavior {
1468        use super::*;
1469
1470        #[test]
1471        fn eviction_occurs_when_over_capacity() {
1472            let mut cache = SlruCore::new(5, 0.2);
1473
1474            for i in 0..10 {
1475                cache.insert(i, i * 10);
1476            }
1477
1478            assert_eq!(cache.len(), 5);
1479        }
1480
1481        #[test]
1482        fn probationary_evicts_lru_order() {
1483            let mut cache = SlruCore::new(5, 0.4);
1484
1485            cache.insert("first", 1);
1486            cache.insert("second", 2);
1487            cache.insert("third", 3);
1488            cache.insert("fourth", 4);
1489            cache.insert("fifth", 5);
1490            cache.insert("sixth", 6);
1491
1492            assert!(!cache.contains(&"first"));
1493            assert_eq!(cache.len(), 5);
1494        }
1495
1496        #[test]
1497        fn protected_evicts_lru_when_probationary_under_cap() {
1498            let mut cache = SlruCore::new(5, 0.4);
1499
1500            cache.insert("p1", 1);
1501            cache.get(&"p1");
1502            cache.insert("p2", 2);
1503            cache.get(&"p2");
1504            cache.insert("p3", 3);
1505            cache.get(&"p3");
1506
1507            cache.insert("new1", 10);
1508            cache.insert("new2", 20);
1509            cache.insert("new3", 30);
1510
1511            assert!(!cache.contains(&"p1"));
1512            assert_eq!(cache.len(), 5);
1513        }
1514
1515        #[test]
1516        fn scan_items_evicted_before_hot_items() {
1517            let mut cache: SlruCore<String, i32> = SlruCore::new(10, 0.3);
1518
1519            cache.insert("hot1".to_string(), 1);
1520            cache.get(&"hot1".to_string());
1521            cache.insert("hot2".to_string(), 2);
1522            cache.get(&"hot2".to_string());
1523
1524            for i in 0..20 {
1525                cache.insert(format!("scan{}", i), i);
1526            }
1527
1528            assert!(cache.contains(&"hot1".to_string()));
1529            assert!(cache.contains(&"hot2".to_string()));
1530            assert_eq!(cache.len(), 10);
1531        }
1532
1533        #[test]
1534        fn eviction_removes_from_index() {
1535            let mut cache = SlruCore::new(3, 0.33);
1536
1537            cache.insert("a", 1);
1538            cache.insert("b", 2);
1539            cache.insert("c", 3);
1540
1541            assert!(cache.contains(&"a"));
1542
1543            cache.insert("d", 4);
1544
1545            assert!(!cache.contains(&"a"));
1546            assert_eq!(cache.get(&"a"), None);
1547        }
1548    }
1549
1550    // ==============================================
1551    // Scan Resistance
1552    // ==============================================
1553
1554    mod scan_resistance {
1555        use super::*;
1556
1557        #[test]
1558        fn scan_does_not_pollute_protected() {
1559            let mut cache = SlruCore::new(100, 0.25);
1560
1561            for i in 0..50 {
1562                let key = format!("working{}", i);
1563                cache.insert(key.clone(), i);
1564                cache.get(&key);
1565            }
1566
1567            for i in 0..200 {
1568                cache.insert(format!("scan{}", i), i);
1569            }
1570
1571            let mut working_set_hits = 0;
1572            for i in 0..50 {
1573                if cache.contains(&format!("working{}", i)) {
1574                    working_set_hits += 1;
1575                }
1576            }
1577
1578            assert!(
1579                working_set_hits >= 40,
1580                "Working set should survive scan, but only {} items remained",
1581                working_set_hits
1582            );
1583        }
1584
1585        #[test]
1586        fn one_time_access_stays_in_probationary() {
1587            let mut cache: SlruCore<String, i32> = SlruCore::new(10, 0.3);
1588
1589            cache.insert("once".to_string(), 1);
1590
1591            for i in 0..5 {
1592                cache.insert(format!("other{}", i), i);
1593            }
1594
1595            cache.get(&"once".to_string());
1596
1597            for i in 0..10 {
1598                cache.insert(format!("new{}", i), i);
1599            }
1600
1601            assert!(cache.contains(&"once".to_string()));
1602        }
1603
1604        #[test]
1605        fn repeated_scans_dont_evict_hot_items() {
1606            let mut cache = SlruCore::new(20, 0.25);
1607
1608            for i in 0..10 {
1609                let key = format!("hot{}", i);
1610                cache.insert(key.clone(), i);
1611                cache.get(&key);
1612                cache.get(&key);
1613                cache.get(&key);
1614            }
1615
1616            for scan in 0..3 {
1617                for i in 0..30 {
1618                    cache.insert(format!("scan{}_{}", scan, i), i);
1619                }
1620            }
1621
1622            let mut hot_survivors = 0;
1623            for i in 0..10 {
1624                if cache.contains(&format!("hot{}", i)) {
1625                    hot_survivors += 1;
1626                }
1627            }
1628
1629            assert!(
1630                hot_survivors >= 8,
1631                "Hot items should survive scans, but only {} survived",
1632                hot_survivors
1633            );
1634        }
1635    }
1636
1637    // ==============================================
1638    // Edge Cases
1639    // ==============================================
1640
1641    mod edge_cases {
1642        use super::*;
1643
1644        #[test]
1645        fn single_capacity_cache() {
1646            let mut cache = SlruCore::new(1, 0.5);
1647
1648            cache.insert("a", 1);
1649            assert_eq!(cache.get(&"a"), Some(&1));
1650
1651            cache.insert("b", 2);
1652            assert!(!cache.contains(&"a"));
1653            assert_eq!(cache.get(&"b"), Some(&2));
1654        }
1655
1656        #[test]
1657        fn zero_probationary_fraction() {
1658            let mut cache = SlruCore::new(10, 0.0);
1659
1660            for i in 0..10 {
1661                cache.insert(i, i * 10);
1662            }
1663
1664            assert_eq!(cache.len(), 10);
1665
1666            cache.insert(100, 1000);
1667            assert_eq!(cache.len(), 10);
1668        }
1669
1670        #[test]
1671        fn one_hundred_percent_probationary() {
1672            let mut cache = SlruCore::new(10, 1.0);
1673
1674            for i in 0..10 {
1675                cache.insert(i, i * 10);
1676            }
1677
1678            for i in 0..10 {
1679                cache.get(&i);
1680            }
1681
1682            assert_eq!(cache.len(), 10);
1683        }
1684
1685        #[test]
1686        fn get_after_update() {
1687            let mut cache = SlruCore::new(100, 0.25);
1688
1689            cache.insert("key", "v1");
1690            assert_eq!(cache.get(&"key"), Some(&"v1"));
1691
1692            cache.insert("key", "v2");
1693            assert_eq!(cache.get(&"key"), Some(&"v2"));
1694
1695            cache.insert("key", "v3");
1696            cache.insert("key", "v4");
1697            assert_eq!(cache.get(&"key"), Some(&"v4"));
1698        }
1699
1700        #[test]
1701        fn large_capacity() {
1702            let mut cache = SlruCore::new(10000, 0.25);
1703
1704            for i in 0..10000 {
1705                cache.insert(i, i * 2);
1706            }
1707
1708            assert_eq!(cache.len(), 10000);
1709
1710            assert_eq!(cache.get(&5000), Some(&10000));
1711            assert_eq!(cache.get(&9999), Some(&19998));
1712        }
1713
1714        #[test]
1715        fn empty_cache_operations() {
1716            let mut cache: SlruCore<i32, i32> = SlruCore::new(100, 0.25);
1717
1718            assert!(cache.is_empty());
1719            assert_eq!(cache.get(&1), None);
1720            assert!(!cache.contains(&1));
1721
1722            cache.clear();
1723            assert!(cache.is_empty());
1724        }
1725
1726        #[test]
1727        fn small_fractions() {
1728            let mut cache = SlruCore::new(100, 0.01);
1729
1730            for i in 0..10 {
1731                cache.insert(i, i);
1732            }
1733
1734            assert_eq!(cache.len(), 10);
1735        }
1736
1737        #[test]
1738        fn string_keys_and_values() {
1739            let mut cache = SlruCore::new(100, 0.25);
1740
1741            cache.insert(String::from("hello"), String::from("world"));
1742            cache.insert(String::from("foo"), String::from("bar"));
1743
1744            assert_eq!(
1745                cache.get(&String::from("hello")),
1746                Some(&String::from("world"))
1747            );
1748            assert_eq!(cache.get(&String::from("foo")), Some(&String::from("bar")));
1749        }
1750
1751        #[test]
1752        fn integer_keys() {
1753            let mut cache = SlruCore::new(100, 0.25);
1754
1755            for i in 0..50 {
1756                cache.insert(i, format!("value_{}", i));
1757            }
1758
1759            assert_eq!(cache.get(&25), Some(&String::from("value_25")));
1760            assert_eq!(cache.get(&49), Some(&String::from("value_49")));
1761        }
1762
1763        #[test]
1764        #[should_panic(expected = "probationary_frac must be in 0.0..=1.0")]
1765        fn negative_fraction_panics() {
1766            let _cache: SlruCore<i32, i32> = SlruCore::new(100, -0.5);
1767        }
1768
1769        #[test]
1770        #[should_panic(expected = "probationary_frac must be in 0.0..=1.0")]
1771        fn fraction_above_one_panics() {
1772            let _cache: SlruCore<i32, i32> = SlruCore::new(100, 1.5);
1773        }
1774
1775        #[test]
1776        #[should_panic(expected = "probationary_frac must be in 0.0..=1.0")]
1777        fn nan_fraction_panics() {
1778            let _cache: SlruCore<i32, i32> = SlruCore::new(100, f64::NAN);
1779        }
1780    }
1781
1782    // ==============================================
1783    // Boundary Tests
1784    // ==============================================
1785
1786    mod boundary_tests {
1787        use super::*;
1788
1789        #[test]
1790        fn exact_capacity_no_eviction() {
1791            let mut cache = SlruCore::new(10, 0.3);
1792
1793            for i in 0..10 {
1794                cache.insert(i, i);
1795            }
1796
1797            assert_eq!(cache.len(), 10);
1798            for i in 0..10 {
1799                assert!(cache.contains(&i));
1800            }
1801        }
1802
1803        #[test]
1804        fn one_over_capacity_triggers_eviction() {
1805            let mut cache = SlruCore::new(10, 0.3);
1806
1807            for i in 0..10 {
1808                cache.insert(i, i);
1809            }
1810
1811            cache.insert(10, 10);
1812
1813            assert_eq!(cache.len(), 10);
1814            assert!(!cache.contains(&0));
1815            assert!(cache.contains(&10));
1816        }
1817
1818        #[test]
1819        fn probationary_cap_boundary() {
1820            let mut cache = SlruCore::new(10, 0.3);
1821
1822            cache.insert("a", 1);
1823            cache.insert("b", 2);
1824            cache.insert("c", 3);
1825
1826            assert_eq!(cache.len(), 3);
1827
1828            cache.insert("d", 4);
1829            assert_eq!(cache.len(), 4);
1830
1831            for key in &["a", "b", "c", "d"] {
1832                assert!(cache.contains(key));
1833            }
1834        }
1835
1836        #[test]
1837        fn promotion_fills_protected() {
1838            let mut cache = SlruCore::new(10, 0.3);
1839
1840            for i in 0..5 {
1841                cache.insert(i, i);
1842            }
1843
1844            for i in 0..5 {
1845                cache.get(&i);
1846            }
1847
1848            for i in 5..10 {
1849                cache.insert(i, i);
1850            }
1851
1852            assert_eq!(cache.len(), 10);
1853
1854            cache.insert(10, 10);
1855            assert_eq!(cache.len(), 10);
1856        }
1857    }
1858
1859    // ==============================================
1860    // Regression Tests
1861    // ==============================================
1862
1863    mod regression_tests {
1864        use super::*;
1865
1866        #[test]
1867        fn promotion_actually_moves_to_protected_segment() {
1868            let mut cache: SlruCore<String, i32> = SlruCore::new(5, 0.4);
1869
1870            cache.insert("key".to_string(), 0);
1871            cache.get(&"key".to_string());
1872
1873            cache.insert("p1".to_string(), 1);
1874            cache.insert("p2".to_string(), 2);
1875            cache.insert("p3".to_string(), 3);
1876            cache.insert("p4".to_string(), 4);
1877
1878            assert!(
1879                cache.contains(&"key".to_string()),
1880                "Promoted item should be in protected segment and survive probationary eviction"
1881            );
1882        }
1883
1884        #[test]
1885        fn update_preserves_segment_position() {
1886            let mut cache: SlruCore<String, i32> = SlruCore::new(10, 0.3);
1887
1888            cache.insert("key".to_string(), 1);
1889            cache.get(&"key".to_string());
1890
1891            cache.insert("key".to_string(), 2);
1892
1893            assert_eq!(cache.get(&"key".to_string()), Some(&2));
1894
1895            for i in 0..15 {
1896                cache.insert(format!("other{}", i), i);
1897            }
1898
1899            assert!(cache.contains(&"key".to_string()));
1900        }
1901
1902        #[test]
1903        fn eviction_order_consistency() {
1904            for _ in 0..10 {
1905                let mut cache = SlruCore::new(5, 0.4);
1906
1907                cache.insert("a", 1);
1908                cache.insert("b", 2);
1909                cache.insert("c", 3);
1910                cache.insert("d", 4);
1911                cache.insert("e", 5);
1912                cache.insert("f", 6);
1913
1914                assert!(!cache.contains(&"a"), "First item should be evicted");
1915                assert!(cache.contains(&"f"), "New item should exist");
1916            }
1917        }
1918    }
1919
1920    // ==============================================
1921    // Workload Simulation
1922    // ==============================================
1923
1924    mod workload_simulation {
1925        use super::*;
1926
1927        #[test]
1928        fn database_buffer_pool_workload() {
1929            let mut cache = SlruCore::new(100, 0.25);
1930
1931            for i in 0..10 {
1932                let key = format!("index_page_{}", i);
1933                cache.insert(key.clone(), format!("index_data_{}", i));
1934                cache.get(&key);
1935                cache.get(&key);
1936            }
1937
1938            for i in 0..200 {
1939                cache.insert(format!("table_page_{}", i), format!("row_data_{}", i));
1940            }
1941
1942            let mut index_hits = 0;
1943            for i in 0..10 {
1944                if cache.contains(&format!("index_page_{}", i)) {
1945                    index_hits += 1;
1946                }
1947            }
1948
1949            assert!(
1950                index_hits >= 8,
1951                "Index pages should survive table scan, got {} hits",
1952                index_hits
1953            );
1954        }
1955
1956        #[test]
1957        fn web_cache_simulation() {
1958            let mut cache: SlruCore<String, String> = SlruCore::new(50, 0.3);
1959
1960            let popular = vec!["home", "about", "products", "contact"];
1961            for page in &popular {
1962                cache.insert(page.to_string(), format!("{}_content", page));
1963                cache.get(&page.to_string());
1964                cache.get(&page.to_string());
1965            }
1966
1967            for i in 0..100 {
1968                cache.insert(format!("blog_post_{}", i), format!("content_{}", i));
1969            }
1970
1971            for page in &popular {
1972                assert!(
1973                    cache.contains(&page.to_string()),
1974                    "Popular page '{}' should survive",
1975                    page
1976                );
1977            }
1978        }
1979
1980        #[test]
1981        fn mixed_workload() {
1982            let mut cache = SlruCore::new(100, 0.25);
1983
1984            for i in 0..30 {
1985                let key = format!("working_{}", i);
1986                cache.insert(key.clone(), i);
1987                cache.get(&key);
1988            }
1989
1990            for round in 0..5 {
1991                for i in (0..30).step_by(3) {
1992                    cache.get(&format!("working_{}", i));
1993                }
1994
1995                for i in 0..20 {
1996                    cache.insert(format!("round_{}_{}", round, i), i);
1997                }
1998            }
1999
2000            let mut working_set_hits = 0;
2001            for i in (0..30).step_by(3) {
2002                if cache.contains(&format!("working_{}", i)) {
2003                    working_set_hits += 1;
2004                }
2005            }
2006
2007            assert!(
2008                working_set_hits >= 8,
2009                "Frequently accessed working set should survive, got {} hits",
2010                working_set_hits
2011            );
2012        }
2013    }
2014
2015    // ==============================================
2016    // Validation Tests
2017    // ==============================================
2018
2019    #[test]
2020    #[cfg(debug_assertions)]
2021    fn validate_invariants_after_operations() {
2022        let mut cache = SlruCore::new(10, 0.3);
2023
2024        for i in 1..=10 {
2025            cache.insert(i, i * 100);
2026        }
2027        cache.validate_invariants();
2028
2029        for _ in 0..3 {
2030            cache.get(&1);
2031            cache.get(&2);
2032        }
2033        cache.validate_invariants();
2034
2035        cache.insert(11, 1100);
2036        cache.validate_invariants();
2037
2038        cache.insert(12, 1200);
2039        cache.validate_invariants();
2040
2041        cache.clear();
2042        cache.validate_invariants();
2043
2044        assert_eq!(cache.len(), 0);
2045    }
2046
2047    #[test]
2048    #[cfg(debug_assertions)]
2049    fn validate_invariants_with_segment_transitions() {
2050        let mut cache = SlruCore::new(5, 0.4);
2051        cache.insert(1, 100);
2052        cache.insert(2, 200);
2053        cache.insert(3, 300);
2054
2055        cache.get(&1);
2056        cache.validate_invariants();
2057
2058        cache.get(&2);
2059        cache.validate_invariants();
2060
2061        cache.insert(4, 400);
2062        cache.insert(5, 500);
2063        cache.validate_invariants();
2064
2065        cache.insert(6, 600);
2066        cache.validate_invariants();
2067
2068        assert_eq!(cache.len(), 5);
2069    }
2070
2071    // ==============================================
2072    // Standard Trait Tests
2073    // ==============================================
2074
2075    #[test]
2076    fn zero_capacity_rejects_inserts() {
2077        let mut cache: SlruCore<&str, i32> = SlruCore::new(0, 0.25);
2078        assert_eq!(cache.capacity(), 0);
2079
2080        cache.insert("key", 42);
2081
2082        assert_eq!(
2083            cache.len(),
2084            0,
2085            "SlruCore with capacity=0 should reject inserts"
2086        );
2087    }
2088
2089    #[test]
2090    fn trait_insert_returns_old_value() {
2091        let mut cache: SlruCore<&str, i32> = SlruCore::new(10, 0.25);
2092
2093        let first = Cache::insert(&mut cache, "key", 1);
2094        assert_eq!(first, None);
2095
2096        let second = Cache::insert(&mut cache, "key", 2);
2097        assert_eq!(
2098            second,
2099            Some(1),
2100            "Second insert via trait should return old value"
2101        );
2102    }
2103
2104    #[test]
2105    fn inherent_insert_updates_value() {
2106        let mut cache: SlruCore<&str, i32> = SlruCore::new(10, 0.25);
2107
2108        cache.insert("key", 1);
2109        cache.insert("key", 2);
2110
2111        assert_eq!(cache.get(&"key"), Some(&2));
2112    }
2113
2114    #[test]
2115    fn default_creates_zero_capacity() {
2116        let cache: SlruCore<String, i32> = SlruCore::default();
2117        assert_eq!(cache.capacity(), 0);
2118        assert!(cache.is_empty());
2119    }
2120
2121    #[test]
2122    fn clone_preserves_entries() {
2123        let mut cache = SlruCore::new(100, 0.25);
2124        cache.insert("a", 1);
2125        cache.insert("b", 2);
2126        cache.get(&"a");
2127
2128        let cloned = cache.clone();
2129        assert_eq!(cloned.len(), 2);
2130        assert_eq!(cloned.peek(&"a"), Some(&1));
2131        assert_eq!(cloned.peek(&"b"), Some(&2));
2132        assert_eq!(cloned.capacity(), 100);
2133    }
2134
2135    #[test]
2136    fn clone_is_independent() {
2137        let mut cache = SlruCore::new(100, 0.25);
2138        cache.insert("a", 1);
2139
2140        let mut cloned = cache.clone();
2141        cloned.insert("a", 999);
2142        cloned.insert("b", 2);
2143
2144        assert_eq!(cache.peek(&"a"), Some(&1));
2145        assert!(!cache.contains(&"b"));
2146    }
2147
2148    #[test]
2149    fn from_iterator() {
2150        let data = vec![("a", 1), ("b", 2), ("c", 3)];
2151        let cache: SlruCore<&str, i32> = data.into_iter().collect();
2152
2153        assert_eq!(cache.len(), 3);
2154        assert_eq!(cache.peek(&"a"), Some(&1));
2155        assert_eq!(cache.peek(&"b"), Some(&2));
2156        assert_eq!(cache.peek(&"c"), Some(&3));
2157    }
2158
2159    #[test]
2160    fn into_iterator_owned() {
2161        let mut cache = SlruCore::new(100, 0.25);
2162        cache.insert("a", 1);
2163        cache.insert("b", 2);
2164        cache.insert("c", 3);
2165
2166        let mut items: Vec<_> = cache.into_iter().collect();
2167        items.sort_by_key(|(k, _)| *k);
2168        assert_eq!(items, vec![("a", 1), ("b", 2), ("c", 3)]);
2169    }
2170
2171    #[test]
2172    fn into_iterator_ref() {
2173        let mut cache = SlruCore::new(100, 0.25);
2174        cache.insert("a", 1);
2175        cache.insert("b", 2);
2176
2177        let mut items: Vec<_> = (&cache).into_iter().collect();
2178        items.sort_by_key(|(k, _)| **k);
2179        assert_eq!(items, vec![(&"a", &1), (&"b", &2)]);
2180    }
2181
2182    #[test]
2183    fn iter_exact_size() {
2184        let mut cache = SlruCore::new(100, 0.25);
2185        cache.insert("a", 1);
2186        cache.insert("b", 2);
2187        cache.get(&"a");
2188
2189        let iter = cache.iter();
2190        assert_eq!(iter.len(), 2);
2191    }
2192
2193    #[test]
2194    fn extend_adds_entries() {
2195        let mut cache = SlruCore::new(100, 0.25);
2196        cache.insert("a", 1);
2197
2198        cache.extend(vec![("b", 2), ("c", 3)]);
2199
2200        assert_eq!(cache.len(), 3);
2201        assert_eq!(cache.peek(&"b"), Some(&2));
2202    }
2203
2204    #[test]
2205    fn debug_impl_works() {
2206        let mut cache = SlruCore::new(100, 0.25);
2207        cache.insert("a", 1);
2208        let debug = format!("{:?}", cache);
2209        assert!(debug.contains("SlruCore"));
2210        assert!(debug.contains("capacity"));
2211    }
2212}