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, 0, ::core::ptr::null(), None);
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        let (_, _, _, warmup_thresh) = self.core.blackjack.load_params();
253        if block.warmup_state > warmup_thresh {
254            block.cache.insert_fast_pass(hash, key.clone(), value.clone());
255            self.core.put_t0(key, value, handle.qsbr_node);
256            block.warmup_state = block.warmup_state.saturating_sub(20);
257        } else {
258            if block.cache.insert(hash, key.clone(), value.clone()) {
259                self.core.put(key, value, handle.qsbr_node);
260            }
261        }
262    }
263
264    /// Insert a key-value pair directly as a high-priority "genius" item.
265    /// This bypasses normal promotion and pins the item directly in the hottest tier (T0).
266    /// Equivalent to 'insert_t1' or 'warmup' from v0.4.0.
267    pub fn warmup(&self, key: K, value: V, handle: &TlsHandle) {
268        let block = self.tls_registry.get_block_mut(handle);
269        block.op_count = block.op_count.wrapping_add(1);
270        if crate::utils::unlikely(block.op_count.is_multiple_of(64)) {
271            self.core.try_reclaim(handle.qsbr_node);
272        }
273
274        let hash = self.core.hash_key(&key);
275        block.cache.insert_fast_pass(hash, key.clone(), value.clone());
276        self.core.put_t0(key, value, handle.qsbr_node);
277    }
278}
279
280#[cfg(feature = "std")]
281unsafe impl<
282    K, 
283    V, 
284    P, 
285    const CAP2: usize, 
286    const CAP1: usize, 
287    const CAP0: usize, 
288    const TOTAL_CAP: usize,
289    const MAX_THREADS: usize,
290    const TLS_CAP: usize,
291    const TLS_INDEX_CAP: usize,
292> Send for DualCacheFF<K, V, P, CAP2, CAP1, CAP0, TOTAL_CAP, MAX_THREADS, TLS_CAP, TLS_INDEX_CAP> 
293where 
294    P: crate::componant::config::CachePolicy + Send + Sync,
295{}
296
297#[cfg(feature = "std")]
298unsafe impl<
299    K, 
300    V, 
301    P, 
302    const CAP2: usize, 
303    const CAP1: usize, 
304    const CAP0: usize, 
305    const TOTAL_CAP: usize,
306    const MAX_THREADS: usize,
307    const TLS_CAP: usize,
308    const TLS_INDEX_CAP: usize,
309> Sync for DualCacheFF<K, V, P, CAP2, CAP1, CAP0, TOTAL_CAP, MAX_THREADS, TLS_CAP, TLS_INDEX_CAP> 
310where 
311    P: crate::componant::config::CachePolicy + Send + Sync,
312{}
313
314
315#[cfg(feature = "std")]
316impl<
317    K, V, P, 
318    const CAP2: usize, const CAP1: usize, const CAP0: usize, const TOTAL_CAP: usize,
319    const MAX_THREADS: usize, const TLS_CAP: usize, const TLS_INDEX_CAP: usize
320> DualCacheFF<K, V, P, CAP2, CAP1, CAP0, TOTAL_CAP, MAX_THREADS, TLS_CAP, TLS_INDEX_CAP>
321where 
322    K: Clone + Eq,
323    V: Clone,
324    P: crate::componant::config::CachePolicy + Send + Sync,
325{
326    pub fn get_metrics(&self) -> (u64, u64) {
327        self.tls_registry.get_metrics()
328    }
329}
330
331#[cfg(feature = "std")]
332impl<
333    K, V, P, 
334    const T0_CAP: usize, const T1_CAP: usize, const T2_CAP: usize, const TOTAL_CAP: usize,
335    const MAX_THREADS: usize, const TLS_CAP: usize, const TLS_INDEX_CAP: usize
336> Drop for DualCacheFF<K, V, P, T0_CAP, T1_CAP, T2_CAP, TOTAL_CAP, MAX_THREADS, TLS_CAP, TLS_INDEX_CAP> 
337where
338    P: crate::componant::config::CachePolicy + Send + Sync,
339{
340    fn drop(&mut self) {
341        // Automatically reclaim background threads to prevent Epoch Stall Deadlocks
342        // and resource leaks, implementing proper RAII.
343        self.daemon_mode.store(false, ::core::sync::atomic::Ordering::SeqCst);
344        self.cata_mode.store(false, ::core::sync::atomic::Ordering::SeqCst);
345        
346        // Disconnect channels to trigger daemon shutdown
347        if let Ok(mut gtx) = self.global_tx.write() {
348            *gtx = None;
349        }
350            self.tls_registry.clear_channels();
351
352        // Join the daemon thread
353        if let Ok(mut handle_guard) = self.daemon_handle.write()
354            && let Some(mut daemon) = handle_guard.take() 
355        {
356            daemon.join();
357        }
358        
359        // Fields will be dropped naturally.
360    }
361}
362
363#[cfg(test)]
364mod tests {
365    use super::*;
366    use std::thread;
367
368    #[test]
369    fn test_static_global_cache() {
370        static GLOBAL_CACHE: DualCacheFF<u64, u64, crate::componant::config::DefaultExponentialPolicy, 256, 1024, 2048, 1024, 10, 256, 512> = DualCacheFF::new(crate::componant::policy::DefaultEvictionPolicy::new());
371        let handle = GLOBAL_CACHE.register_thread();
372        GLOBAL_CACHE.insert(1, 100, &handle);
373        GLOBAL_CACHE.insert(1, 100, &handle);
374        assert_eq!(GLOBAL_CACHE.get(&1, &handle), Some(100));
375    }
376
377    #[test]
378    fn test_daemon_off_sync() {
379        static CACHE: DualCacheFF<u64, u64, crate::componant::config::DefaultExponentialPolicy, 256, 1024, 2048, 4096, 10, 256, 512> = DualCacheFF::new(crate::componant::policy::DefaultEvictionPolicy::new());
380        let handle = CACHE.register_thread();
381
382        // Put twice to pass admission filter
383        CACHE.insert(1, 100, &handle);
384        CACHE.insert(1, 100, &handle);
385        assert_eq!(CACHE.get(&1, &handle), Some(100));
386        
387        // Put twice, verify isolation
388        CACHE.insert(2, 200, &handle);
389        CACHE.insert(2, 200, &handle);
390        assert_eq!(CACHE.get(&2, &handle), Some(200));
391    }
392
393    #[cfg(feature = "std")]
394    #[test]
395    fn test_daemon_on_async() {
396        use std::time::Duration;
397        
398        static CACHE: DualCacheFF<u64, u64, crate::componant::config::DefaultExponentialPolicy, 8, 16, 64, 88, 10, 256, 512> = DualCacheFF::new(crate::componant::policy::DefaultEvictionPolicy::new());
399        
400        // Turn ON Daemon (automatically spawns daemon)
401        CACHE.set_daemon_mode(true);
402        
403        let handle = CACHE.register_thread();
404
405        // Put twice to pass admission filter
406        CACHE.insert(10, 1000, &handle);
407        CACHE.insert(10, 1000, &handle);
408
409        // Insert 75 items to trigger capacity evictions (T2 cap is 64) 
410        // but avoid Arena OOM (Total cap is 88)
411        for i in 100..175 {
412            CACHE.insert(i, i * 10, &handle);
413            CACHE.insert(i, i * 10, &handle);
414        }
415        
416        // Get multiple times to reach promote threshold
417        for _ in 0..5 {
418            let _ = CACHE.get(&10, &handle);
419        }
420        
421        // Hit coverage for other DaemonMessage variants
422        if let Some(ref tx) = *CACHE.global_tx.read().unwrap() {
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        // Wait a bit for daemon to process the promote message
431        thread::sleep(Duration::from_millis(50));
432        
433        // The value should be retrievable
434        assert_eq!(CACHE.get(&10, &handle), Some(1000));
435        
436        // Shut down daemon to complete thread lifecycle and test Shutdown/None paths
437        CACHE.set_daemon_mode(false);
438        thread::sleep(Duration::from_millis(50));
439    }
440
441    #[cfg(feature = "std")]
442    #[test]
443    fn test_extensive_coverage() {
444        use std::time::Duration;
445        
446        static CACHE: DualCacheFF<u64, u64, crate::componant::config::DefaultExponentialPolicy, 256, 1024, 2048, 4096, 10, 256, 512> = DualCacheFF::new(crate::componant::policy::DefaultEvictionPolicy::new());
447        let handle = CACHE.register_thread();
448
449        // Sync mode: insert many items to trigger evictions
450        for i in 0..100 {
451            CACHE.insert(i, i * 10, &handle);
452            CACHE.insert(i, i * 10, &handle); // Admitted
453            CACHE.get(&i, &handle);
454        }
455
456        // Hit item 10 many times to promote it through T2 -> T1 -> T0
457        for _ in 0..300 {
458            CACHE.get(&10, &handle);
459        }
460
461        // Turn on Daemon
462        CACHE.set_daemon_mode(true);
463
464        // Async mode: insert many items
465        for i in 100..200 {
466            CACHE.insert(i, i * 10, &handle);
467            CACHE.insert(i, i * 10, &handle); // Admitted
468            // Get multiple times to trigger promote messages and hit batches
469            for _ in 0..100 {
470                CACHE.get(&i, &handle);
471            }
472        }
473        // Wait for daemon to process
474        std::thread::sleep(Duration::from_millis(50));
475
476        // Test manual Promote to T0 and T2 via global_tx
477        if let Ok(gtx) = CACHE.global_tx.read()
478            && let Some(ref tx) = *gtx
479        {
480            let _ = tx.push(crate::componant::daemon::DaemonMessage::Promote(999, 999, 9990, 0));
481            let _ = tx.push(crate::componant::daemon::DaemonMessage::Promote(888, 888, 8880, 2));
482            
483            // Test HitBatch manual injection
484            let mut arr = [(0usize, 0u8); 32];
485            arr[0] = (123, 10);
486            arr[1] = (123, 5); // Duplicate hash to trigger `found = true`
487            arr[2] = (456, 1);
488            let _ = tx.push(crate::componant::daemon::DaemonMessage::HitBatch(arr, 3));
489        }
490        std::thread::sleep(Duration::from_millis(50));
491
492        let handle2 = CACHE.register_thread();
493        for i in 100..200 {
494            // handle2 TLS is empty, so it will hit core.get
495            CACHE.get(&i, &handle2);
496        }
497
498        // Insert many items via handle
499        for i in 1000..2000 {
500            CACHE.insert(i, i * 10, &handle);
501        }
502
503        // Get them via handle2. Since handle2 TLS is empty, they will miss TLS and hit Core.
504        // Each hit increments warmup_state by 10.
505        // Since there are 1000 items, all blocks will easily reach warmup_state > 100.
506        for i in 1000..2000 {
507            CACHE.get(&i, &handle2);
508        }
509        
510        let warmup = CACHE.tls_registry.get_block_mut(&handle2).warmup_state;
511        println!("Warmup state for handle2 after 1000 gets: {}", warmup);
512        
513        // Now warmup_state of all blocks is > 100.
514        // Insert will hit normal insert branch (not fast pass).
515        for i in 2000..3000 {
516            CACHE.insert(i, i * 10, &handle2);
517            CACHE.insert(i, i * 10, &handle2); // second time to pass probation filter
518        }
519
520        // Test over-capacity registration panic
521        for _ in 1..9 {
522            let _ = CACHE.register_thread();
523        }
524        // Since CACHE is static, catching unwind around its reference requires AssertUnwindSafe.
525        let res = std::panic::catch_unwind(|| {
526            let _ = CACHE.register_thread(); // Should panic
527        });
528        assert!(res.is_err());
529
530        // Send a Promote message to Daemon directly
531        if let Some(ref tx) = *CACHE.global_tx.read().unwrap() {
532            let _ = tx.push(crate::componant::daemon::DaemonMessage::Promote(123, 123, 123, 0));
533            
534            // Test the remaining DaemonMessage variants to achieve 100% coverage
535            let _ = tx.push(crate::componant::daemon::DaemonMessage::SetPollInterval(5));
536            
537            let ack = crate::componant::daemon::OneshotAck::new();
538            let _ = tx.push(crate::componant::daemon::DaemonMessage::Sync(ack.clone()));
539            ack.wait();
540        }
541        // Wait for daemon to process
542        thread::sleep(Duration::from_millis(50));
543
544        // Turn off daemon
545        CACHE.set_daemon_mode(false);
546        
547        // Wait for daemon to die
548        thread::sleep(Duration::from_millis(50));
549        
550        // Explicitly reclaim to hit coverage
551        CACHE.core.try_reclaim(handle.qsbr_node);
552        CACHE.core.try_reclaim(handle.qsbr_node);
553    }
554}