Skip to main content

dualcache_ff/
lib.rs

1#![cfg_attr(not(any(feature = "std", feature = "daemon", test)), no_std)]
2
3pub mod utils;
4pub mod componant;
5pub mod core;
6
7#[cfg(feature = "std")]
8use ::core::sync::atomic::{AtomicBool, Ordering};
9#[cfg(feature = "std")]
10use crate::componant::tls::{TlsRegistry, TlsHandle};
11#[cfg(feature = "std")]
12use crate::componant::daemon::DaemonMessage;
13
14#[cfg(feature = "std")]
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum ThreadCount {
17    Pool(usize),
18    Pin(usize),
19    Mixed(usize, usize),
20}
21
22#[cfg(feature = "std")]
23/// `DualCacheFF` is the main entry point for the cache, providing standard API operations and managing the 
24/// background daemon for garbage collection and memory reclamation.
25#[repr(C, align(64))]
26pub struct DualCacheFF<
27    K, 
28    V, 
29    P, 
30    const CAP2: usize, 
31    const CAP1: usize, 
32    const CAP0: usize, 
33    const TOTAL_CAP: usize,
34    const MAX_THREADS: usize,
35    const TLS_CAP: usize,
36    const TLS_INDEX_CAP: usize,
37> 
38where 
39    P: crate::componant::config::CachePolicy + Send + Sync,
40{
41    core: crate::core::DualCacheCore<K, V, P, CAP2, CAP1, CAP0, TOTAL_CAP>,
42    pub daemon_mode: AtomicBool,
43    #[cfg(feature = "std")]
44    pub cata_mode: AtomicBool,
45    pub tls_registry: TlsRegistry<K, V, MAX_THREADS, TLS_CAP, TLS_INDEX_CAP>,
46    #[cfg(feature = "std")]
47    #[allow(clippy::type_complexity)]
48    pub global_tx: std::sync::RwLock<Option<::std::sync::Arc<no_std_tool::collections::mpsc_queue::BoundedQueue<DaemonMessage<K, V>, 65536>>>>,
49    #[cfg(feature = "std")]
50    pub daemon_handle: std::sync::RwLock<Option<crate::componant::daemon::Daemon>>,
51}
52
53#[cfg(feature = "std")]
54impl<
55    K, 
56    V, 
57    P, 
58    const CAP2: usize, 
59    const CAP1: usize, 
60    const CAP0: usize, 
61    const TOTAL_CAP: usize,
62    const MAX_THREADS: usize,
63    const TLS_CAP: usize,
64    const TLS_INDEX_CAP: usize,
65> Default for DualCacheFF<K, V, P, CAP2, CAP1, CAP0, TOTAL_CAP, MAX_THREADS, TLS_CAP, TLS_INDEX_CAP>
66where 
67    K: Clone + Eq + ::core::hash::Hash + Send + Sync + 'static,
68    V: Clone + Send + Sync + 'static,
69    P: crate::componant::config::CachePolicy + Send + Sync + 'static,
70 {
71    fn default() -> Self {
72        Self::new(P::Evict::default())
73    }
74}
75
76#[cfg(feature = "std")]
77impl<
78    K, 
79    V, 
80    P, 
81    const CAP2: usize, 
82    const CAP1: usize, 
83    const CAP0: usize, 
84    const TOTAL_CAP: usize,
85    const MAX_THREADS: usize,
86    const TLS_CAP: usize,
87    const TLS_INDEX_CAP: usize,
88> DualCacheFF<K, V, P, CAP2, CAP1, CAP0, TOTAL_CAP, MAX_THREADS, TLS_CAP, TLS_INDEX_CAP> 
89where 
90    K: Clone + Eq + ::core::hash::Hash + Send + Sync + 'static,
91    V: Clone + Send + Sync + 'static,
92    P: crate::componant::config::CachePolicy + Send + Sync + 'static,
93{
94    pub const fn new(eviction: P::Evict) -> Self {
95        Self {
96            core: crate::core::DualCacheCore::new(eviction),
97            daemon_mode: AtomicBool::new(false),
98            #[cfg(feature = "std")]
99            cata_mode: AtomicBool::new(false),
100            tls_registry: TlsRegistry::new(),
101            #[cfg(feature = "std")]
102            global_tx: std::sync::RwLock::new(None),
103            #[cfg(feature = "std")]
104            daemon_handle: std::sync::RwLock::new(None),
105        }
106    }
107
108    /// Start the CATA-DC Demiurge tuning engine in the background
109    #[cfg(feature = "std")]
110    pub fn set_cata_tuning(&'static self, on: bool) {
111        if on && !self.cata_mode.swap(true, std::sync::atomic::Ordering::SeqCst) {
112            crate::componant::cata::spawn_demiurge(self);
113        } else if !on {
114            self.cata_mode.store(false, std::sync::atomic::Ordering::SeqCst);
115        }
116    }
117
118    /// Turn the Daemon on or off. 
119    /// If turned on, a new daemon thread is automatically spawned and channel is established.
120    #[cfg(feature = "std")]
121    pub fn set_daemon_mode(&'static self, on: bool) {
122        self.daemon_mode.store(on, Ordering::SeqCst);
123        
124        if on {
125            let tx = unsafe { ::std::sync::Arc::<no_std_tool::collections::mpsc_queue::BoundedQueue<DaemonMessage<K, V>, 65536>>::new_zeroed().assume_init() };
126            let rx = tx.clone();
127            let mut broadcast_txs = std::vec::Vec::with_capacity(self.tls_registry.max_threads());
128            
129            for i in 0..self.tls_registry.max_threads() {
130                let dummy_handle = TlsHandle { id: i, qsbr_node: ::core::ptr::null_mut() };
131                let block = self.tls_registry.get_block_mut(&dummy_handle);
132                block.tx = Some(tx.clone());
133                
134                let hit_queue = unsafe { ::std::sync::Arc::<no_std_tool::collections::mpsc_queue::BoundedQueue<(usize, u8), 1024>>::new_zeroed().assume_init() };
135                block.hit_rx = Some(hit_queue.clone());
136                broadcast_txs.push(hit_queue);
137            }
138
139            let daemon_node = {
140                let node = std::boxed::Box::into_raw(std::boxed::Box::new(crate::componant::qsbr::ThreadStateNode::new()));
141                crate::componant::qsbr::register_node(node);
142                node
143            };
144            let daemon = crate::componant::daemon::Daemon::spawn(&self.core, rx, broadcast_txs, daemon_node);
145            if let Ok(mut handle_guard) = self.daemon_handle.write() {
146                *handle_guard = Some(daemon);
147            }
148            if let Ok(mut gtx) = self.global_tx.write() {
149                *gtx = Some(tx.clone());
150            }
151        } else {
152            if let Ok(mut gtx) = self.global_tx.write() {
153                *gtx = None;
154            }
155            self.tls_registry.clear_channels();
156            if let Ok(mut handle_guard) = self.daemon_handle.write()
157                && let Some(mut daemon) = handle_guard.take() 
158            {
159                daemon.join();
160            }
161        }
162    }
163
164    /// Register the current thread to get a fast TLS handle.
165    pub fn register_thread(&self) -> TlsHandle {
166        let handle = self.tls_registry.register_thread();
167        
168        #[cfg(feature = "std")]
169        if let Ok(gtx) = self.global_tx.read()
170            && let Some(ref global_tx) = *gtx {
171                let block = self.tls_registry.get_block_mut(&handle);
172                block.tx = Some(global_tx.clone());
173            }
174        
175        handle
176    }
177
178    pub fn get(&self, key: &K, handle: &TlsHandle) -> Option<V> {
179        let block = self.tls_registry.get_block_mut(handle);
180        block.op_count = block.op_count.wrapping_add(1);
181        
182        let op_count = block.op_count as u32;
183
184        #[cfg(feature = "std")]
185        if op_count & 63 == 0 {
186            let global = crate::componant::qsbr::get_global_epoch();
187            unsafe {
188                let node = &mut *handle.qsbr_node;
189                node.epoch.store(global, ::core::sync::atomic::Ordering::Relaxed);
190                node.active.store(true, ::core::sync::atomic::Ordering::Relaxed);
191            }
192        }
193        
194        let guard = ::core::mem::ManuallyDrop::new(unsafe { crate::componant::qsbr::Guard::unpinned(handle.qsbr_node) });
195        let hash = self.core.hash_key(key);
196        
197        // 1. T0 (Royal Class)
198        if let Some(val) = self.core.get_t0(hash, key, &guard, op_count) {
199            block.warmup_state = block.warmup_state.saturating_add(10);
200            block.cache.insert_fast_pass(hash, key.clone(), val.clone());
201            return Some(val.clone());
202        }
203
204        // 2. T1 (Elite Class) - FastTier
205        if let Some(val) = self.core.get_t1(hash, key, &guard, op_count) {
206            block.cache.insert(hash, key.clone(), val.clone());
207            return Some(val.clone());
208        }
209
210        // 3. TLS (Thread Local)
211        let (val_opt, promote, _sync) = block.cache.get(hash, key);
212        if let Some(val) = val_opt {
213            if promote {
214                self.core.put_t0(key.clone(), val.clone(), handle.qsbr_node);
215            }
216            #[cfg(feature = "std")]
217            if _sync > 0 {
218                if block.hit_batch_len < 32 {
219                    block.hit_batch[block.hit_batch_len as usize] = (hash, _sync);
220                    block.hit_batch_len += 1;
221                }
222                if block.hit_batch_len == 32 {
223                    if let Some(ref tx) = block.tx {
224                        let mut batch = [(0, 0); 32];
225                        batch.copy_from_slice(&block.hit_batch);
226                        let _ = tx.push(crate::componant::daemon::DaemonMessage::HitBatch(batch, 32));
227                    }
228                    block.hit_batch_len = 0;
229                }
230            }
231            return Some(val.clone());
232        }
233
234        // 3. T2 (Middle Class)
235        if let Some(val) = self.core.get_t2(hash, key, &guard, op_count) {
236            block.warmup_state = block.warmup_state.saturating_sub(10);
237            block.cache.insert(hash, key.clone(), val.0.clone());
238            return Some(val.0.clone());
239        }
240        None
241    }
242
243    pub fn insert(&self, key: K, value: V, handle: &TlsHandle) {
244        let block = self.tls_registry.get_block_mut(handle);
245        block.op_count = block.op_count.wrapping_add(1);
246        if crate::utils::unlikely(block.op_count.is_multiple_of(64)) {
247            self.core.try_reclaim(handle.qsbr_node);
248        }
249
250        let hash = self.core.hash_key(&key);
251
252        if block.warmup_state > 50 {
253            block.cache.insert_fast_pass(hash, key.clone(), value.clone());
254            self.core.put_t0(key, value, handle.qsbr_node);
255            block.warmup_state = block.warmup_state.saturating_sub(20);
256        } else {
257            if block.cache.insert(hash, key.clone(), value.clone()) {
258                self.core.put(key, value, handle.qsbr_node);
259            }
260        }
261    }
262
263    /// Insert a key-value pair directly as a high-priority "genius" item.
264    /// This bypasses normal promotion and pins the item directly in the hottest tier (T0).
265    /// Equivalent to 'insert_t1' or 'warmup' from v0.4.0.
266    pub fn warmup(&self, key: K, value: V, handle: &TlsHandle) {
267        let block = self.tls_registry.get_block_mut(handle);
268        block.op_count = block.op_count.wrapping_add(1);
269        if crate::utils::unlikely(block.op_count.is_multiple_of(64)) {
270            self.core.try_reclaim(handle.qsbr_node);
271        }
272
273        let hash = self.core.hash_key(&key);
274        block.cache.insert_fast_pass(hash, key.clone(), value.clone());
275        self.core.put_t0(key, value, handle.qsbr_node);
276    }
277}
278
279#[cfg(feature = "std")]
280unsafe impl<
281    K, 
282    V, 
283    P, 
284    const CAP2: usize, 
285    const CAP1: usize, 
286    const CAP0: usize, 
287    const TOTAL_CAP: usize,
288    const MAX_THREADS: usize,
289    const TLS_CAP: usize,
290    const TLS_INDEX_CAP: usize,
291> Send for DualCacheFF<K, V, P, CAP2, CAP1, CAP0, TOTAL_CAP, MAX_THREADS, TLS_CAP, TLS_INDEX_CAP> 
292where 
293    P: crate::componant::config::CachePolicy + Send + Sync,
294{}
295
296#[cfg(feature = "std")]
297unsafe impl<
298    K, 
299    V, 
300    P, 
301    const CAP2: usize, 
302    const CAP1: usize, 
303    const CAP0: usize, 
304    const TOTAL_CAP: usize,
305    const MAX_THREADS: usize,
306    const TLS_CAP: usize,
307    const TLS_INDEX_CAP: usize,
308> Sync for DualCacheFF<K, V, P, CAP2, CAP1, CAP0, TOTAL_CAP, MAX_THREADS, TLS_CAP, TLS_INDEX_CAP> 
309where 
310    P: crate::componant::config::CachePolicy + Send + Sync,
311{}
312
313
314#[cfg(feature = "std")]
315impl<
316    K, V, P, 
317    const CAP2: usize, const CAP1: usize, const CAP0: usize, const TOTAL_CAP: usize,
318    const MAX_THREADS: usize, const TLS_CAP: usize, const TLS_INDEX_CAP: usize
319> DualCacheFF<K, V, P, CAP2, CAP1, CAP0, TOTAL_CAP, MAX_THREADS, TLS_CAP, TLS_INDEX_CAP>
320where 
321    K: Clone + Eq,
322    V: Clone,
323    P: crate::componant::config::CachePolicy + Send + Sync,
324{
325    pub fn get_metrics(&self) -> (u64, u64) {
326        self.tls_registry.get_metrics()
327    }
328}
329
330#[cfg(feature = "std")]
331impl<
332    K, V, P, 
333    const T0_CAP: usize, const T1_CAP: usize, const T2_CAP: usize, const TOTAL_CAP: usize,
334    const MAX_THREADS: usize, const TLS_CAP: usize, const TLS_INDEX_CAP: usize
335> Drop for DualCacheFF<K, V, P, T0_CAP, T1_CAP, T2_CAP, TOTAL_CAP, MAX_THREADS, TLS_CAP, TLS_INDEX_CAP> 
336where
337    P: crate::componant::config::CachePolicy + Send + Sync,
338{
339    fn drop(&mut self) {
340        // Automatically reclaim background threads to prevent Epoch Stall Deadlocks
341        // and resource leaks, implementing proper RAII.
342        self.daemon_mode.store(false, ::core::sync::atomic::Ordering::SeqCst);
343        self.cata_mode.store(false, ::core::sync::atomic::Ordering::SeqCst);
344        
345        // Disconnect channels to trigger daemon shutdown
346        if let Ok(mut gtx) = self.global_tx.write() {
347            *gtx = None;
348        }
349            self.tls_registry.clear_channels();
350
351        // Join the daemon thread
352        if let Ok(mut handle_guard) = self.daemon_handle.write()
353            && let Some(mut daemon) = handle_guard.take() 
354        {
355            daemon.join();
356        }
357        
358        // Fields will be dropped naturally.
359    }
360}
361
362#[cfg(test)]
363mod tests {
364    use super::*;
365    use std::thread;
366
367    #[test]
368    fn test_static_global_cache() {
369        static GLOBAL_CACHE: DualCacheFF<u64, u64, crate::componant::config::DefaultExponentialPolicy, 256, 1024, 2048, 1024, 10, 256, 512> = DualCacheFF::new(crate::componant::policy::DefaultEvictionPolicy::new());
370        let handle = GLOBAL_CACHE.register_thread();
371        GLOBAL_CACHE.insert(1, 100, &handle);
372        GLOBAL_CACHE.insert(1, 100, &handle);
373        assert_eq!(GLOBAL_CACHE.get(&1, &handle), Some(100));
374    }
375
376    #[test]
377    fn test_daemon_off_sync() {
378        static CACHE: DualCacheFF<u64, u64, crate::componant::config::DefaultExponentialPolicy, 256, 1024, 2048, 4096, 10, 256, 512> = DualCacheFF::new(crate::componant::policy::DefaultEvictionPolicy::new());
379        let handle = CACHE.register_thread();
380
381        // Put twice to pass admission filter
382        CACHE.insert(1, 100, &handle);
383        CACHE.insert(1, 100, &handle);
384        assert_eq!(CACHE.get(&1, &handle), Some(100));
385        
386        // Put twice, verify isolation
387        CACHE.insert(2, 200, &handle);
388        CACHE.insert(2, 200, &handle);
389        assert_eq!(CACHE.get(&2, &handle), Some(200));
390    }
391
392    #[cfg(feature = "std")]
393    #[test]
394    fn test_daemon_on_async() {
395        use std::time::Duration;
396        
397        static CACHE: DualCacheFF<u64, u64, crate::componant::config::DefaultExponentialPolicy, 8, 16, 64, 88, 10, 256, 512> = DualCacheFF::new(crate::componant::policy::DefaultEvictionPolicy::new());
398        
399        // Turn ON Daemon (automatically spawns daemon)
400        CACHE.set_daemon_mode(true);
401        
402        let handle = CACHE.register_thread();
403
404        // Put twice to pass admission filter
405        CACHE.insert(10, 1000, &handle);
406        CACHE.insert(10, 1000, &handle);
407
408        // Insert 75 items to trigger capacity evictions (T2 cap is 64) 
409        // but avoid Arena OOM (Total cap is 88)
410        for i in 100..175 {
411            CACHE.insert(i, i * 10, &handle);
412            CACHE.insert(i, i * 10, &handle);
413        }
414        
415        // Get multiple times to reach promote threshold
416        for _ in 0..5 {
417            let _ = CACHE.get(&10, &handle);
418        }
419        
420        // Hit coverage for other DaemonMessage variants
421        if let Ok(gtx) = CACHE.global_tx.read() {
422            if let Some(ref tx) = *gtx {
423                let _ = tx.push(crate::componant::daemon::DaemonMessage::SetPollInterval(5));
424                
425                let ack = crate::componant::daemon::OneshotAck::new();
426                let _ = tx.push(crate::componant::daemon::DaemonMessage::Sync(ack.clone()));
427                ack.wait();
428            }
429        }
430        
431        // Wait a bit for daemon to process the promote message
432        thread::sleep(Duration::from_millis(50));
433        
434        // The value should be retrievable
435        assert_eq!(CACHE.get(&10, &handle), Some(1000));
436        
437        // Shut down daemon to complete thread lifecycle and test Shutdown/None paths
438        CACHE.set_daemon_mode(false);
439        thread::sleep(Duration::from_millis(50));
440    }
441
442    #[cfg(feature = "std")]
443    #[test]
444    fn test_extensive_coverage() {
445        use std::time::Duration;
446        
447        static CACHE: DualCacheFF<u64, u64, crate::componant::config::DefaultExponentialPolicy, 256, 1024, 2048, 4096, 10, 256, 512> = DualCacheFF::new(crate::componant::policy::DefaultEvictionPolicy::new());
448        let handle = CACHE.register_thread();
449
450        // Sync mode: insert many items to trigger evictions
451        for i in 0..100 {
452            CACHE.insert(i, i * 10, &handle);
453            CACHE.insert(i, i * 10, &handle); // Admitted
454            CACHE.get(&i, &handle);
455        }
456
457        // Hit item 10 many times to promote it through T2 -> T1 -> T0
458        for _ in 0..300 {
459            CACHE.get(&10, &handle);
460        }
461
462        // Turn on Daemon
463        CACHE.set_daemon_mode(true);
464
465        // Async mode: insert many items
466        for i in 100..200 {
467            CACHE.insert(i, i * 10, &handle);
468            CACHE.insert(i, i * 10, &handle); // Admitted
469            // Get multiple times to trigger promote messages and hit batches
470            for _ in 0..100 {
471                CACHE.get(&i, &handle);
472            }
473        }
474        // Wait for daemon to process
475        std::thread::sleep(Duration::from_millis(50));
476
477        // Test manual Promote to T0 and T2 via global_tx
478        if let Ok(gtx) = CACHE.global_tx.read()
479            && let Some(ref tx) = *gtx
480        {
481            let _ = tx.push(crate::componant::daemon::DaemonMessage::Promote(999, 999, 9990, 0));
482            let _ = tx.push(crate::componant::daemon::DaemonMessage::Promote(888, 888, 8880, 2));
483            
484            // Test HitBatch manual injection
485            let mut arr = [(0usize, 0u8); 32];
486            arr[0] = (123, 10);
487            arr[1] = (123, 5); // Duplicate hash to trigger `found = true`
488            arr[2] = (456, 1);
489            let _ = tx.push(crate::componant::daemon::DaemonMessage::HitBatch(arr, 3));
490        }
491        std::thread::sleep(Duration::from_millis(50));
492
493        let handle2 = CACHE.register_thread();
494        for i in 100..200 {
495            // handle2 TLS is empty, so it will hit core.get
496            CACHE.get(&i, &handle2);
497        }
498
499        // Insert many items via handle
500        for i in 1000..2000 {
501            CACHE.insert(i, i * 10, &handle);
502        }
503
504        // Get them via handle2. Since handle2 TLS is empty, they will miss TLS and hit Core.
505        // Each hit increments warmup_state by 10.
506        // Since there are 1000 items, all blocks will easily reach warmup_state > 100.
507        for i in 1000..2000 {
508            CACHE.get(&i, &handle2);
509        }
510        
511        let warmup = CACHE.tls_registry.get_block_mut(&handle2).warmup_state;
512        println!("Warmup state for handle2 after 1000 gets: {}", warmup);
513        
514        // Now warmup_state of all blocks is > 100.
515        // Insert will hit normal insert branch (not fast pass).
516        for i in 2000..3000 {
517            CACHE.insert(i, i * 10, &handle2);
518            CACHE.insert(i, i * 10, &handle2); // second time to pass probation filter
519        }
520
521        // Test over-capacity registration panic
522        for _ in 1..9 {
523            let _ = CACHE.register_thread();
524        }
525        // Since CACHE is static, catching unwind around its reference requires AssertUnwindSafe.
526        let res = std::panic::catch_unwind(|| {
527            let _ = CACHE.register_thread(); // Should panic
528        });
529        assert!(res.is_err());
530
531        // Send a Promote message to Daemon directly
532        if let Ok(gtx) = CACHE.global_tx.read() {
533            if let Some(ref tx) = *gtx {
534                let _ = tx.push(crate::componant::daemon::DaemonMessage::Promote(123, 123, 123, 0));
535                
536                // Test the remaining DaemonMessage variants to achieve 100% coverage
537                let _ = tx.push(crate::componant::daemon::DaemonMessage::SetPollInterval(5));
538                
539                let ack = crate::componant::daemon::OneshotAck::new();
540                let _ = tx.push(crate::componant::daemon::DaemonMessage::Sync(ack.clone()));
541                ack.wait();
542                
543                // We don't send Shutdown here because it would kill the daemon prematurely
544            }
545        } // Wait for daemon to process
546        thread::sleep(Duration::from_millis(50));
547
548        // Turn off daemon
549        CACHE.set_daemon_mode(false);
550        
551        // Wait for daemon to die
552        thread::sleep(Duration::from_millis(50));
553        
554        // Explicitly reclaim to hit coverage
555        CACHE.core.try_reclaim(handle.qsbr_node);
556        CACHE.core.try_reclaim(handle.qsbr_node);
557    }
558}