Skip to main content

cache_rs/
gdsf.rs

1//! Greedy Dual-Size Frequency (GDSF) Cache Implementation
2//!
3//! GDSF is a sophisticated cache replacement algorithm designed for **variable-sized objects**.
4//! It combines object size, access frequency, and aging into a unified priority formula,
5//! making it ideal for CDN caches, image caches, and any scenario where cached objects
6//! have different sizes.
7//!
8//! # How the Algorithm Works
9//!
10//! GDSF calculates priority for each cached item using the formula:
11//!
12//! ```text
13//! Priority = (Frequency / Size) + GlobalAge
14//! ```
15//!
16//! This formula cleverly balances multiple factors:
17//! - **Frequency**: More frequently accessed items get higher priority
18//! - **Size**: Smaller items are favored (more items fit in cache)
19//! - **Aging**: Prevents old popular items from staying forever
20//!
21//! ## Mathematical Formulation
22//!
23//! ```text
24//! For each cache entry i:
25//!   - F_i = access frequency of item i
26//!   - S_i = size of item i (in bytes)
27//!   - GlobalAge = increases on eviction (set to evicted item's priority)
28//!   - Priority_i = (F_i / S_i) + GlobalAge
29//!
30//! On eviction: select item j where Priority_j = min{Priority_i for all i}
31//! ```
32//!
33//! ## Why Size Matters
34//!
35//! Consider a cache with 10KB capacity:
36//! - Option A: Cache one 10KB file accessed 10 times → Priority = 10/10000 = 0.001
37//! - Option B: Cache ten 1KB files accessed once each → Each Priority = 1/1000 = 0.001
38//!
39//! GDSF recognizes that caching many small frequently-accessed items often yields
40//! better hit rates than caching fewer large items.
41//!
42//! ## Data Structure
43//!
44//! ```text
45//! ┌─────────────────────────────────────────────────────────────────────────────┐
46//! │                    GDSF Cache (global_age=1.5, max_size=10MB)               │
47//! │                                                                              │
48//! │  HashMap<K, *Node>              BTreeMap<Priority, List>                     │
49//! │  ┌──────────────┐              ┌─────────────────────────────────────────┐   │
50//! │  │ "icon.png" ─────────────────│ pri=3.5: [icon.png]   (f=2, s=1KB)      │   │
51//! │  │ "thumb.jpg" ────────────────│ pri=2.5: [thumb.jpg]  (f=1, s=1KB)      │   │
52//! │  │ "video.mp4" ────────────────│ pri=1.5: [video.mp4]  (f=10, s=100MB)←E │   │
53//! │  └──────────────┘              └─────────────────────────────────────────┘   │
54//! │                                        ▲                                     │
55//! │                                        │                                     │
56//! │                                   min_priority=1.5                           │
57//! │                                                                              │
58//! │  Note: video.mp4 has high frequency (10) but large size (100MB),            │
59//! │        so its priority = 10/100000000 + 1.5 ≈ 1.5 (eviction candidate)      │
60//! └─────────────────────────────────────────────────────────────────────────────┘
61//! ```
62//!
63//! ## Operations
64//!
65//! | Operation | Action | Time |
66//! |-----------|--------|------|
67//! | `get(key)` | Increment frequency, recalculate priority | O(log P) |
68//! | `put(key, value, size)` | Insert with priority=(1/size)+age | O(log P) |
69//! | `remove(key)` | Remove from priority list, update min_priority | O(log P) |
70//!
71//! ## Size-Aware Example
72//!
73//! ```text
74//! Cache: max_size=5KB, global_age=0
75//!
76//! put("small.txt", data, 1KB)   →  pri=1.0, total_size=1KB
77//! put("medium.txt", data, 2KB)  →  pri=0.5, total_size=3KB
78//! put("large.txt", data, 3KB)   →  pri=0.33, total_size=6KB  OVERFLOW!
79//!
80//! Eviction needed: evict "large.txt" (lowest priority=0.33)
81//! global_age = 0.33
82//!
83//! put("large.txt", data, 3KB)   →  pri=0.33+0.33=0.66, total_size=6KB  OVERFLOW!
84//!
85//! Evict again: now "medium.txt" has lowest priority (0.5 < 0.66)
86//! Result: small.txt + large.txt fit in 4KB
87//! ```
88//!
89//! # Dual-Limit Capacity
90//!
91//! GDSF naturally works with size-based limits:
92//!
93//! - **`max_entries`**: Maximum number of items (prevents too many tiny items)
94//! - **`max_size`**: Maximum total size (primary constraint for GDSF)
95//!
96//! # Performance Characteristics
97//!
98//! | Metric | Value |
99//! |--------|-------|
100//! | Get | O(log P) |
101//! | Put | O(log P) amortized |
102//! | Remove | O(log P) |
103//! | Memory per entry | ~120 bytes overhead + key×2 + value |
104//!
105//! Where P = number of distinct priority buckets. Priority = (frequency/size) + age,
106//! quantized to integer keys. BTreeMap provides O(log P) lookups.
107//!
108//! Higher overhead than simpler algorithms due to priority calculation and
109//! BTreeMap-based priority lists.
110//!
111//! # When to Use GDSF
112//!
113//! **Good for:**
114//! - CDN and proxy caches with variable object sizes
115//! - Image thumbnail caches
116//! - API response caches with varying payload sizes
117//! - File system caches
118//! - Any size-constrained cache with heterogeneous objects
119//!
120//! **Not ideal for:**
121//! - Uniform-size objects (simpler algorithms work equally well)
122//! - Entry-count-constrained caches (LRU/LFU are simpler)
123//! - Very small caches (overhead not justified)
124//!
125//! # GDSF vs Other Algorithms
126//!
127//! | Aspect | LRU | LFU | GDSF |
128//! |--------|-----|-----|------|
129//! | Size-aware | No | No | **Yes** |
130//! | Frequency-aware | No | Yes | Yes |
131//! | Aging | Implicit | No | Yes |
132//! | Best for | Recency | Frequency | Variable-size objects |
133//!
134//! # Thread Safety
135//!
136//! `GdsfCache` is **not thread-safe**. For concurrent access, either:
137//! - Wrap with `Mutex` or `RwLock`
138//! - Use `ConcurrentGdsfCache` (requires `concurrent` feature)
139//!
140//! # Examples
141//!
142//! ## Basic Usage
143//!
144//! ```
145//! use cache_rs::GdsfCache;
146//! use cache_rs::config::GdsfCacheConfig;
147//! use core::num::NonZeroUsize;
148//!
149//! // Create cache with max 1000 entries and 10MB size limit
150//! let config = GdsfCacheConfig {
151//!     capacity: NonZeroUsize::new(1000).unwrap(),
152//!     initial_age: 0.0,
153//!     max_size: 10 * 1024 * 1024,  // 10MB
154//! };
155//! let mut cache: GdsfCache<String, Vec<u8>> = GdsfCache::init(config, None);
156//!
157//! // Insert with explicit size tracking
158//! let small_data = vec![0u8; 1024];  // 1KB
159//! cache.put("small.txt".to_string(), small_data, 1024);
160//!
161//! let large_data = vec![0u8; 1024 * 1024];  // 1MB
162//! cache.put("large.bin".to_string(), large_data, 1024 * 1024);
163//!
164//! // Small items get higher priority per byte
165//! assert!(cache.get(&"small.txt".to_string()).is_some());
166//! ```
167//!
168//! ## CDN-Style Caching
169//!
170//! ```
171//! use cache_rs::GdsfCache;
172//! use cache_rs::config::GdsfCacheConfig;
173//! use core::num::NonZeroUsize;
174//!
175//! // 100MB cache for web assets
176//! let config = GdsfCacheConfig {
177//!     capacity: NonZeroUsize::new(10000).unwrap(),
178//!     initial_age: 0.0,
179//!     max_size: 100 * 1024 * 1024,
180//! };
181//! let mut cache: GdsfCache<String, Vec<u8>> = GdsfCache::init(config, None);
182//!
183//! // Cache various asset types with their sizes
184//! fn cache_asset(cache: &mut GdsfCache<String, Vec<u8>>, url: &str, data: Vec<u8>) {
185//!     let size = data.len() as u64;
186//!     cache.put(url.to_string(), data, size);
187//! }
188//!
189//! // Small, frequently-accessed assets get priority over large, rarely-used ones
190//! ```
191
192extern crate alloc;
193
194use crate::config::GdsfCacheConfig;
195use crate::entry::CacheEntry;
196use crate::list::{List, ListEntry};
197use crate::meta::GdsfMeta;
198use crate::metrics::{CacheMetrics, GdsfCacheMetrics};
199use alloc::boxed::Box;
200use alloc::collections::BTreeMap;
201use alloc::string::String;
202use core::borrow::Borrow;
203use core::hash::{BuildHasher, Hash};
204use core::mem;
205use core::num::NonZeroUsize;
206
207#[cfg(feature = "hashbrown")]
208use hashbrown::DefaultHashBuilder;
209#[cfg(feature = "hashbrown")]
210use hashbrown::HashMap;
211
212#[cfg(not(feature = "hashbrown"))]
213use std::collections::hash_map::RandomState as DefaultHashBuilder;
214#[cfg(not(feature = "hashbrown"))]
215use std::collections::HashMap;
216
217/// Internal GDSF segment containing the actual cache algorithm.
218///
219/// Uses `CacheEntry<K, V, GdsfMeta>` as the unified entry type. The map stores
220/// raw pointers to list nodes, and all entry data (key, value, size, metadata)
221/// is stored in the `CacheEntry`.
222pub(crate) struct GdsfSegment<K, V, S = DefaultHashBuilder> {
223    config: GdsfCacheConfig,
224    global_age: f64,
225    min_priority: f64,
226    /// Maps keys to node pointers. The node contains CacheEntry with all data.
227    map: HashMap<K, *mut ListEntry<CacheEntry<K, V, GdsfMeta>>, S>,
228    /// Priority lists: key is (priority * 1000) as u64 for BTreeMap ordering
229    priority_lists: BTreeMap<u64, List<CacheEntry<K, V, GdsfMeta>>>,
230    metrics: GdsfCacheMetrics,
231    /// Current total size of cached content (sum of entry sizes)
232    current_size: u64,
233}
234
235// SAFETY: GdsfSegment owns all data and raw pointers point only to nodes owned by
236// `priority_lists`. Concurrent access is safe when wrapped in proper synchronization primitives.
237unsafe impl<K: Send, V: Send, S: Send> Send for GdsfSegment<K, V, S> {}
238
239// SAFETY: All mutation requires &mut self; shared references cannot cause data races.
240unsafe impl<K: Send, V: Send, S: Sync> Sync for GdsfSegment<K, V, S> {}
241
242impl<K: Hash + Eq, V: Clone, S: BuildHasher> GdsfSegment<K, V, S> {
243    /// Creates a new GDSF segment from a configuration.
244    ///
245    /// This is the **only** way to create a GDSF segment. All configuration
246    /// is specified through the [`GdsfCacheConfig`] struct.
247    ///
248    /// # Arguments
249    ///
250    /// * `config` - Configuration specifying capacity, initial age, and optional size limit
251    /// * `hasher` - Hash builder for the internal HashMap
252    #[allow(dead_code)] // Used by concurrent module when feature is enabled
253    pub(crate) fn init(config: GdsfCacheConfig, hasher: S) -> Self {
254        let map_capacity = config.capacity.get().next_power_of_two();
255        GdsfSegment {
256            global_age: config.initial_age,
257            min_priority: 0.0,
258            map: HashMap::with_capacity_and_hasher(map_capacity, hasher),
259            priority_lists: BTreeMap::new(),
260            metrics: GdsfCacheMetrics::new(config.max_size),
261            current_size: 0,
262            config,
263        }
264    }
265
266    #[inline]
267    pub(crate) fn cap(&self) -> NonZeroUsize {
268        self.config.capacity
269    }
270
271    #[inline]
272    pub(crate) fn len(&self) -> usize {
273        self.map.len()
274    }
275
276    #[inline]
277    pub(crate) fn is_empty(&self) -> bool {
278        self.map.is_empty()
279    }
280
281    #[inline]
282    pub(crate) fn global_age(&self) -> f64 {
283        self.global_age
284    }
285
286    /// Returns the current total size of cached content.
287    #[inline]
288    pub(crate) fn current_size(&self) -> u64 {
289        self.current_size
290    }
291
292    /// Returns the maximum content size the cache can hold.
293    #[inline]
294    pub(crate) fn max_size(&self) -> u64 {
295        self.config.max_size
296    }
297
298    #[inline]
299    pub(crate) fn metrics(&self) -> &GdsfCacheMetrics {
300        &self.metrics
301    }
302
303    fn estimate_object_size(&self, _key: &K, _value: &V) -> u64 {
304        mem::size_of::<K>() as u64 + mem::size_of::<V>() as u64 + 64
305    }
306
307    #[inline]
308    pub(crate) fn record_miss(&mut self, object_size: u64) {
309        self.metrics.core.record_miss(object_size);
310    }
311
312    fn calculate_priority(&self, frequency: u64, size: u64) -> f64 {
313        if size == 0 {
314            return f64::INFINITY;
315        }
316        (frequency as f64 / size as f64) + self.global_age
317    }
318
319    unsafe fn update_priority_by_node(
320        &mut self,
321        node: *mut ListEntry<CacheEntry<K, V, GdsfMeta>>,
322    ) -> *mut ListEntry<CacheEntry<K, V, GdsfMeta>>
323    where
324        K: Clone + Hash + Eq,
325    {
326        // SAFETY: node is guaranteed valid by caller's contract
327        let entry = (*node).get_value_mut();
328        let key_cloned = entry.key.clone();
329        let size = entry.size;
330        let meta = entry.metadata_mut().unwrap();
331        let old_priority = meta.priority;
332
333        meta.increment();
334
335        let global_age = self.global_age;
336        let new_priority = meta.calculate_priority(size, global_age);
337
338        let old_priority_key = (old_priority * 1000.0) as u64;
339        let new_priority_key = (new_priority * 1000.0) as u64;
340
341        if old_priority_key == new_priority_key {
342            self.priority_lists
343                .get_mut(&new_priority_key)
344                .unwrap()
345                .move_to_front(node);
346            return node;
347        }
348
349        let boxed_entry = self
350            .priority_lists
351            .get_mut(&old_priority_key)
352            .unwrap()
353            .remove(node)
354            .unwrap();
355
356        if self
357            .priority_lists
358            .get(&old_priority_key)
359            .unwrap()
360            .is_empty()
361        {
362            self.priority_lists.remove(&old_priority_key);
363        }
364
365        let entry_ptr = Box::into_raw(boxed_entry);
366
367        let capacity = self.config.capacity;
368        self.priority_lists
369            .entry(new_priority_key)
370            .or_insert_with(|| List::new(capacity));
371
372        self.priority_lists
373            .get_mut(&new_priority_key)
374            .unwrap()
375            .attach_from_other_list(entry_ptr);
376
377        // Update map with new node pointer
378        self.map.insert(key_cloned, entry_ptr);
379        entry_ptr
380    }
381
382    pub(crate) fn get<Q>(&mut self, key: &Q) -> Option<V>
383    where
384        K: Borrow<Q> + Clone,
385        Q: ?Sized + Hash + Eq,
386    {
387        if let Some(&node) = self.map.get(key) {
388            unsafe {
389                // SAFETY: node comes from our map
390                let entry = (*node).get_value();
391                let object_size = self.estimate_object_size(&entry.key, &entry.value);
392                let meta = entry.metadata.as_ref().unwrap();
393                self.metrics.core.record_hit(object_size);
394                self.metrics
395                    .record_item_access(meta.frequency, entry.size, meta.priority);
396
397                let new_node = self.update_priority_by_node(node);
398                let value = (*new_node).get_value().value.clone();
399                Some(value)
400            }
401        } else {
402            None
403        }
404    }
405
406    pub(crate) fn get_mut<Q>(&mut self, key: &Q) -> Option<&mut V>
407    where
408        K: Borrow<Q> + Clone,
409        Q: ?Sized + Hash + Eq,
410    {
411        if let Some(&node) = self.map.get(key) {
412            unsafe {
413                // SAFETY: node comes from our map
414                let entry = (*node).get_value();
415                let object_size = self.estimate_object_size(&entry.key, &entry.value);
416                let meta = entry.metadata.as_ref().unwrap();
417                self.metrics.core.record_hit(object_size);
418                self.metrics
419                    .record_item_access(meta.frequency, entry.size, meta.priority);
420
421                let new_node = self.update_priority_by_node(node);
422                let entry_mut = (*new_node).get_value_mut();
423                Some(&mut entry_mut.value)
424            }
425        } else {
426            None
427        }
428    }
429
430    pub(crate) fn contains_key<Q>(&self, key: &Q) -> bool
431    where
432        K: Borrow<Q>,
433        Q: ?Sized + Hash + Eq,
434    {
435        self.map.contains_key(key)
436    }
437
438    pub(crate) fn put(&mut self, key: K, val: V, size: u64) -> Option<V>
439    where
440        K: Clone,
441    {
442        if size == 0 {
443            return None;
444        }
445
446        let object_size = self.estimate_object_size(&key, &val);
447
448        // Check if key exists - update existing entry
449        if let Some(&node) = self.map.get(&key) {
450            unsafe {
451                // SAFETY: node comes from our map
452                let entry = (*node).get_value_mut();
453                let old_size = entry.size;
454                let meta = entry.metadata_mut().unwrap();
455                let old_priority_key = (meta.priority * 1000.0) as u64;
456                let frequency = meta.frequency;
457
458                // Remove from old priority list
459                let list = self.priority_lists.get_mut(&old_priority_key).unwrap();
460                let boxed_entry = list.remove(node).unwrap();
461
462                if list.is_empty() {
463                    self.priority_lists.remove(&old_priority_key);
464                }
465
466                let entry_ptr = Box::into_raw(boxed_entry);
467                let old_value = (*entry_ptr).get_value().value.clone();
468                let _ = Box::from_raw(entry_ptr);
469
470                // Update size tracking
471                self.current_size = self.current_size.saturating_sub(old_size);
472                self.current_size += size;
473
474                // Create new entry with updated values but preserved frequency
475                let new_priority = self.calculate_priority(frequency, size);
476                let new_priority_key = (new_priority * 1000.0) as u64;
477
478                let new_entry = CacheEntry::with_metadata(
479                    key.clone(),
480                    val,
481                    size,
482                    GdsfMeta::new(frequency, new_priority),
483                );
484
485                let capacity = self.cap();
486                let list = self
487                    .priority_lists
488                    .entry(new_priority_key)
489                    .or_insert_with(|| List::new(capacity));
490
491                if let Some(new_node) = list.add(new_entry) {
492                    self.map.insert(key, new_node);
493                    self.metrics.core.record_insertion(object_size);
494                    return Some(old_value);
495                } else {
496                    self.map.remove(&key);
497                    return None;
498                }
499            }
500        }
501
502        // New entry - check capacity and size limits
503        let capacity = self.config.capacity.get();
504        let max_size = self.config.max_size;
505
506        while self.len() >= capacity
507            || (self.current_size + size > max_size && !self.map.is_empty())
508        {
509            self.evict_one();
510        }
511
512        let priority = self.calculate_priority(1, size);
513        let priority_key = (priority * 1000.0) as u64;
514
515        let cap = self.config.capacity;
516        let list = self
517            .priority_lists
518            .entry(priority_key)
519            .or_insert_with(|| List::new(cap));
520
521        let cache_entry =
522            CacheEntry::with_metadata(key.clone(), val, size, GdsfMeta::new(1, priority));
523
524        if let Some(node) = list.add(cache_entry) {
525            self.map.insert(key, node);
526            self.current_size += size;
527
528            if self.len() == 1 || priority < self.min_priority {
529                self.min_priority = priority;
530            }
531
532            self.metrics.core.record_insertion(size);
533            self.metrics
534                .record_item_cached(size, self.metrics.average_item_size());
535            self.metrics.record_item_access(1, size, priority);
536
537            None
538        } else {
539            None
540        }
541    }
542
543    fn evict_one(&mut self) {
544        if self.is_empty() {
545            return;
546        }
547
548        let min_priority_key = *self.priority_lists.keys().next().unwrap();
549        let list = self.priority_lists.get_mut(&min_priority_key).unwrap();
550
551        if let Some(boxed_entry) = list.remove_last() {
552            unsafe {
553                // SAFETY: entry comes from list.remove_last()
554                let entry_ptr = Box::into_raw(boxed_entry);
555                let entry = (*entry_ptr).get_value();
556                let evicted_size = entry.size;
557                let priority_to_update = entry
558                    .metadata
559                    .as_ref()
560                    .map(|m| m.priority)
561                    .unwrap_or(self.global_age);
562
563                self.current_size = self.current_size.saturating_sub(evicted_size);
564                self.metrics.core.record_eviction(evicted_size);
565                self.metrics.record_size_based_eviction();
566                self.metrics.record_aging_event(priority_to_update);
567
568                self.global_age = priority_to_update;
569                self.map.remove(&entry.key);
570
571                let _ = Box::from_raw(entry_ptr);
572            }
573        }
574
575        if list.is_empty() {
576            self.priority_lists.remove(&min_priority_key);
577        }
578    }
579
580    pub(crate) fn pop<Q>(&mut self, key: &Q) -> Option<V>
581    where
582        K: Borrow<Q>,
583        Q: ?Sized + Hash + Eq,
584    {
585        if let Some(node) = self.map.remove(key) {
586            unsafe {
587                // SAFETY: node comes from our map
588                let entry = (*node).get_value();
589                let removed_size = entry.size;
590                let priority = entry.metadata.as_ref().map(|m| m.priority).unwrap_or(0.0);
591                let priority_key = (priority * 1000.0) as u64;
592
593                let list = self.priority_lists.get_mut(&priority_key).unwrap();
594                let boxed_entry = list.remove(node).unwrap();
595
596                if list.is_empty() {
597                    self.priority_lists.remove(&priority_key);
598                }
599
600                let entry_ptr = Box::into_raw(boxed_entry);
601                let result = (*entry_ptr).get_value().value.clone();
602                self.current_size = self.current_size.saturating_sub(removed_size);
603                let _ = Box::from_raw(entry_ptr);
604
605                Some(result)
606            }
607        } else {
608            None
609        }
610    }
611
612    pub(crate) fn clear(&mut self) {
613        self.map.clear();
614        self.priority_lists.clear();
615        self.global_age = 0.0;
616        self.min_priority = 0.0;
617        self.current_size = 0;
618    }
619}
620
621impl<K, V, S> core::fmt::Debug for GdsfSegment<K, V, S> {
622    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
623        f.debug_struct("GdsfSegment")
624            .field("capacity", &self.config.capacity)
625            .field("len", &self.map.len())
626            .field("global_age", &self.global_age)
627            .finish()
628    }
629}
630
631/// An implementation of a Greedy Dual-Size Frequency (GDSF) cache.
632#[derive(Debug)]
633pub struct GdsfCache<K, V, S = DefaultHashBuilder> {
634    segment: GdsfSegment<K, V, S>,
635}
636
637impl<K: Hash + Eq, V: Clone, S: BuildHasher> GdsfCache<K, V, S> {
638    #[inline]
639    pub fn cap(&self) -> NonZeroUsize {
640        self.segment.cap()
641    }
642
643    #[inline]
644    pub fn len(&self) -> usize {
645        self.segment.len()
646    }
647
648    #[inline]
649    pub fn is_empty(&self) -> bool {
650        self.segment.is_empty()
651    }
652
653    /// Returns the current total size of cached content.
654    #[inline]
655    pub fn current_size(&self) -> u64 {
656        self.segment.current_size()
657    }
658
659    /// Returns the maximum content size the cache can hold.
660    #[inline]
661    pub fn max_size(&self) -> u64 {
662        self.segment.max_size()
663    }
664
665    #[inline]
666    pub fn global_age(&self) -> f64 {
667        self.segment.global_age()
668    }
669
670    #[inline]
671    pub fn record_miss(&mut self, object_size: u64) {
672        self.segment.record_miss(object_size);
673    }
674
675    #[inline]
676    pub fn get<Q>(&mut self, key: &Q) -> Option<V>
677    where
678        K: Borrow<Q> + Clone,
679        Q: ?Sized + Hash + Eq,
680    {
681        self.segment.get(key)
682    }
683
684    #[inline]
685    pub fn get_mut<Q>(&mut self, key: &Q) -> Option<&mut V>
686    where
687        K: Borrow<Q> + Clone,
688        Q: ?Sized + Hash + Eq,
689    {
690        self.segment.get_mut(key)
691    }
692
693    #[inline]
694    pub fn contains_key<Q>(&self, key: &Q) -> bool
695    where
696        K: Borrow<Q>,
697        Q: ?Sized + Hash + Eq,
698    {
699        self.segment.contains_key(key)
700    }
701
702    #[inline]
703    pub fn put(&mut self, key: K, val: V, size: u64) -> Option<V>
704    where
705        K: Clone,
706    {
707        self.segment.put(key, val, size)
708    }
709
710    #[inline]
711    pub fn pop<Q>(&mut self, key: &Q) -> Option<V>
712    where
713        K: Borrow<Q>,
714        Q: ?Sized + Hash + Eq,
715    {
716        self.segment.pop(key)
717    }
718
719    #[inline]
720    pub fn clear(&mut self) {
721        self.segment.clear()
722    }
723}
724
725impl<K: Hash + Eq, V: Clone, S: BuildHasher> CacheMetrics for GdsfCache<K, V, S> {
726    fn metrics(&self) -> BTreeMap<String, f64> {
727        self.segment.metrics().metrics()
728    }
729
730    fn algorithm_name(&self) -> &'static str {
731        self.segment.metrics().algorithm_name()
732    }
733}
734
735impl<K: Hash + Eq, V: Clone> GdsfCache<K, V, DefaultHashBuilder> {
736    /// Creates a new GDSF cache from a configuration.
737    ///
738    /// This is the **recommended** way to create a GDSF cache. All configuration
739    /// is specified through the [`GdsfCacheConfig`] struct.
740    ///
741    /// # Arguments
742    ///
743    /// * `config` - Configuration specifying capacity and optional size limit/initial age
744    /// * `hasher` - Optional custom hash builder. Pass `None` to use the default.
745    ///
746    /// # Example
747    ///
748    /// ```
749    /// use cache_rs::GdsfCache;
750    /// use cache_rs::config::GdsfCacheConfig;
751    /// use core::num::NonZeroUsize;
752    ///
753    /// // Simple capacity-only cache
754    /// let config = GdsfCacheConfig {
755    ///     capacity: NonZeroUsize::new(100).unwrap(),
756    ///     initial_age: 0.0,
757    ///     max_size: u64::MAX,
758    /// };
759    /// let mut cache: GdsfCache<&str, i32> = GdsfCache::init(config, None);
760    /// cache.put("key", 42, 1);
761    ///
762    /// // Cache with size limit (recommended for GDSF)
763    /// let config = GdsfCacheConfig {
764    ///     capacity: NonZeroUsize::new(1000).unwrap(),
765    ///     initial_age: 0.0,
766    ///     max_size: 10 * 1024 * 1024,  // 10MB
767    /// };
768    /// let cache: GdsfCache<String, Vec<u8>> = GdsfCache::init(config, None);
769    /// ```
770    pub fn init(config: GdsfCacheConfig, hasher: Option<DefaultHashBuilder>) -> Self {
771        GdsfCache {
772            segment: GdsfSegment::init(config, hasher.unwrap_or_default()),
773        }
774    }
775}
776
777#[cfg(test)]
778mod tests {
779    use super::*;
780    use crate::config::GdsfCacheConfig;
781    use core::num::NonZeroUsize;
782
783    /// Helper to create a GdsfCache with the given capacity
784    fn make_cache<K: Hash + Eq + Clone, V: Clone>(cap: usize) -> GdsfCache<K, V> {
785        let config = GdsfCacheConfig {
786            capacity: NonZeroUsize::new(cap).unwrap(),
787            initial_age: 0.0,
788            max_size: u64::MAX,
789        };
790        GdsfCache::init(config, None)
791    }
792
793    #[test]
794    fn test_gdsf_basic_operations() {
795        let mut cache = make_cache(3);
796
797        assert_eq!(cache.put("a", 1, 1), None);
798        assert_eq!(cache.put("b", 2, 2), None);
799        assert_eq!(cache.put("c", 3, 1), None);
800        assert_eq!(cache.len(), 3);
801
802        assert_eq!(cache.get(&"a"), Some(1));
803        assert_eq!(cache.get(&"b"), Some(2));
804        assert_eq!(cache.get(&"c"), Some(3));
805
806        assert!(cache.contains_key(&"a"));
807        assert!(!cache.contains_key(&"d"));
808    }
809
810    #[test]
811    fn test_gdsf_frequency_priority() {
812        let mut cache = make_cache(2);
813
814        cache.put("a", 1, 1);
815        cache.put("b", 2, 1);
816
817        cache.get(&"a");
818        cache.get(&"a");
819
820        cache.put("c", 3, 1);
821
822        assert!(cache.contains_key(&"a"));
823        assert!(!cache.contains_key(&"b"));
824        assert!(cache.contains_key(&"c"));
825    }
826
827    #[test]
828    fn test_gdsf_size_consideration() {
829        let mut cache = make_cache(2);
830
831        cache.put("small", 1, 1);
832        cache.put("large", 2, 10);
833
834        cache.put("medium", 3, 5);
835
836        assert!(cache.contains_key(&"small"));
837        assert!(!cache.contains_key(&"large"));
838        assert!(cache.contains_key(&"medium"));
839    }
840
841    #[test]
842    fn test_gdsf_update_existing() {
843        let mut cache = make_cache(2);
844
845        cache.put("key", 1, 1);
846        assert_eq!(cache.get(&"key"), Some(1));
847
848        assert_eq!(cache.put("key", 2, 2), Some(1));
849        assert_eq!(cache.get(&"key"), Some(2));
850        assert_eq!(cache.len(), 1);
851    }
852
853    #[test]
854    fn test_gdsf_zero_size_rejection() {
855        let mut cache = make_cache(2);
856
857        assert_eq!(cache.put("key", 1, 0), None);
858        assert_eq!(cache.len(), 0);
859        assert!(!cache.contains_key(&"key"));
860    }
861
862    #[test]
863    fn test_gdsf_pop() {
864        let mut cache = make_cache(2);
865
866        cache.put("a", 1, 1);
867        cache.put("b", 2, 1);
868
869        assert_eq!(cache.pop(&"a"), Some(1));
870        assert_eq!(cache.len(), 1);
871        assert!(!cache.contains_key(&"a"));
872        assert!(cache.contains_key(&"b"));
873
874        assert_eq!(cache.pop(&"nonexistent"), None);
875    }
876
877    #[test]
878    fn test_gdsf_clear() {
879        let mut cache = make_cache(2);
880
881        cache.put("a", 1, 1);
882        cache.put("b", 2, 1);
883        assert_eq!(cache.len(), 2);
884
885        cache.clear();
886        assert_eq!(cache.len(), 0);
887        assert!(cache.is_empty());
888        assert!(!cache.contains_key(&"a"));
889        assert!(!cache.contains_key(&"b"));
890    }
891
892    #[test]
893    fn test_gdsf_global_aging() {
894        let mut cache = make_cache(2);
895
896        cache.put("a", 1, 1);
897        cache.put("b", 2, 1);
898
899        let initial_age = cache.global_age();
900
901        cache.put("c", 3, 1);
902
903        assert!(cache.global_age() > initial_age);
904    }
905
906    #[test]
907    fn test_miri_stacked_borrows_fix() {
908        let mut cache = make_cache(10);
909
910        cache.put("a", 1, 10);
911        cache.put("b", 2, 20);
912        cache.put("c", 3, 15);
913
914        for _ in 0..3 {
915            assert_eq!(cache.get(&"a"), Some(1));
916            assert_eq!(cache.get(&"b"), Some(2));
917            assert_eq!(cache.get(&"c"), Some(3));
918        }
919
920        assert_eq!(cache.len(), 3);
921
922        if let Some(val) = cache.get_mut(&"a") {
923            *val += 10;
924        }
925        assert_eq!(cache.get(&"a"), Some(11));
926    }
927
928    #[test]
929    fn test_gdsf_segment_directly() {
930        let config = GdsfCacheConfig {
931            capacity: NonZeroUsize::new(2).unwrap(),
932            initial_age: 0.0,
933            max_size: u64::MAX,
934        };
935        let mut segment: GdsfSegment<&str, i32, DefaultHashBuilder> =
936            GdsfSegment::init(config, DefaultHashBuilder::default());
937        assert_eq!(segment.len(), 0);
938        assert!(segment.is_empty());
939        assert_eq!(segment.cap().get(), 2);
940        segment.put("a", 1, 1);
941        segment.put("b", 2, 2);
942        assert_eq!(segment.len(), 2);
943        assert_eq!(segment.get(&"a"), Some(1));
944        assert_eq!(segment.get(&"b"), Some(2));
945    }
946
947    #[test]
948    fn test_gdsf_concurrent_access() {
949        extern crate std;
950        use std::sync::{Arc, Mutex};
951        use std::thread;
952        use std::vec::Vec;
953
954        let cache = Arc::new(Mutex::new(make_cache::<String, i32>(100)));
955        let num_threads = 4;
956        let ops_per_thread = 100;
957
958        let mut handles: Vec<std::thread::JoinHandle<()>> = Vec::new();
959
960        for t in 0..num_threads {
961            let cache = Arc::clone(&cache);
962            handles.push(thread::spawn(move || {
963                for i in 0..ops_per_thread {
964                    let key = std::format!("key_{}_{}", t, i);
965                    let size = ((i % 10) + 1) as u64; // Varying sizes 1-10
966                    let mut guard = cache.lock().unwrap();
967                    guard.put(key.clone(), i, size);
968                    let _ = guard.get(&key);
969                }
970            }));
971        }
972
973        for handle in handles {
974            handle.join().unwrap();
975        }
976
977        let mut guard = cache.lock().unwrap();
978        assert!(guard.len() <= 100);
979        guard.clear(); // Clean up for MIRI
980    }
981
982    #[test]
983    fn test_gdsf_size_aware_tracking() {
984        let mut cache = make_cache(10);
985
986        assert_eq!(cache.current_size(), 0);
987        assert_eq!(cache.max_size(), u64::MAX);
988
989        // GDSF already requires size in put()
990        cache.put("a", 1, 100);
991        cache.put("b", 2, 200);
992        cache.put("c", 3, 150);
993
994        assert_eq!(cache.current_size(), 450);
995        assert_eq!(cache.len(), 3);
996
997        // GDSF doesn't have remove method, test clear instead
998        cache.clear();
999        assert_eq!(cache.current_size(), 0);
1000    }
1001
1002    #[test]
1003    fn test_gdsf_init_constructor() {
1004        let config = GdsfCacheConfig {
1005            capacity: NonZeroUsize::new(1000).unwrap(),
1006            initial_age: 0.0,
1007            max_size: 1024 * 1024,
1008        };
1009        let cache: GdsfCache<String, i32> = GdsfCache::init(config, None);
1010
1011        assert_eq!(cache.current_size(), 0);
1012        assert_eq!(cache.max_size(), 1024 * 1024);
1013    }
1014
1015    #[test]
1016    fn test_gdsf_with_limits_constructor() {
1017        let config = GdsfCacheConfig {
1018            capacity: NonZeroUsize::new(100).unwrap(),
1019            initial_age: 0.0,
1020            max_size: 1024 * 1024,
1021        };
1022        let cache: GdsfCache<String, String> = GdsfCache::init(config, None);
1023
1024        assert_eq!(cache.current_size(), 0);
1025        assert_eq!(cache.max_size(), 1024 * 1024);
1026        assert_eq!(cache.cap().get(), 100);
1027    }
1028}