Skip to main content

dualcache_ff/
daemon.rs

1#[cfg(not(feature = "std"))]
2use alloc::{boxed::Box, vec::Vec};
3
4use crate::sync::{Arc, ArcSlice};
5use crate::sync::atomic::{AtomicBool, AtomicU32, Ordering};
6use crate::sync::index_types::AtomicTick;
7use core::hash::{Hash, BuildHasher};
8
9use crate::storage::Cache;
10use crate::filters::{T1, T2};
11use crate::lossy_queue::{LossyQueue, OneshotAck};
12use crate::cache::WorkerState;
13use crate::core_cache::CoreCache;
14
15// ── Command ───────────────────────────────────────────────────────────────
16
17pub enum Command<K, V> {
18    /// Single insert from Worker (goes through probation gate).
19    Insert(K, V, u64, bool),
20    /// Batch of (K, V, hash) from sharded worker buffers.
21    BatchInsert(Vec<(K, V, u64, bool)>),
22    /// Insert directly as a top priority item bypassing probation.
23    InsertT1(K, V, u64),
24    /// Remove by key+hash.
25    Remove(K, u64),
26    /// Blocking clear — caller spins on `OneshotAck::wait()`.
27    Clear(Arc<OneshotAck>),
28    /// Blocking maintenance flush — caller spins on `OneshotAck::wait()`.
29    Sync(Arc<OneshotAck>),
30    /// Signal Daemon to exit its run loop.
31    Shutdown,
32}
33
34// ── Daemon ────────────────────────────────────────────────────────────────
35
36pub struct Daemon<K, V, S> {
37    pub hasher: S,
38    pub core: CoreCache<K, V>,
39    pub cmd_rx: Arc<LossyQueue<Command<K, V>>>,
40    pub hit_rx: Arc<LossyQueue<[usize; 64]>>,
41    /// Configurable poll interval in microseconds (1 000–10 000 µs).
42    pub poll_us: u64,
43    /// Monotonically increasing tick counter — incremented on every poll loop.
44    pub daemon_tick: Arc<AtomicTick>,
45}
46
47unsafe impl<K: Send, V: Send, S: Send> Send for Daemon<K, V, S> {}
48
49impl<K, V, S> Daemon<K, V, S>
50where
51    K: Hash + Eq + Send + Sync + Clone + 'static,
52    V: Send + Sync + Clone + 'static,
53    S: BuildHasher + Clone + Send + 'static,
54{
55    #[allow(clippy::too_many_arguments)]
56    pub fn new(
57        hasher: S,
58        capacity: usize,
59        t1: Arc<T1<K, V>>,
60        t2: Arc<T2<K, V>>,
61        cache: Arc<Cache<K, V>>,
62        cmd_rx: Arc<LossyQueue<Command<K, V>>>,
63        hit_rx: Arc<LossyQueue<[usize; 64]>>,
64        epoch: Arc<AtomicU32>,
65        duration: u32,
66        poll_us: u64,
67        worker_states: ArcSlice<WorkerState>,
68        daemon_tick: Arc<AtomicTick>,
69        is_cold_start: Arc<AtomicBool>,
70    ) -> Self {
71        Self {
72            hasher,
73            core: CoreCache::new(
74                capacity,
75                t1,
76                t2,
77                cache,
78                epoch,
79                duration,
80                worker_states,
81                is_cold_start,
82            ),
83            cmd_rx,
84            hit_rx,
85            poll_us,
86            daemon_tick,
87        }
88    }
89
90    /// Main Daemon event loop.
91    pub fn run(mut self) {
92        #[cfg(feature = "std")]
93        let mut last_epoch_tick = std::time::Instant::now();
94
95        loop {
96            // ── Drain command queue (up to 8192 commands per poll) ────────
97            let mut processed = 0u32;
98            loop {
99                match self.cmd_rx.try_recv() {
100                    Some(Command::Shutdown) => return,
101                    Some(cmd) => {
102                        self.process_cmd(cmd);
103                        processed += 1;
104                        if processed >= 8192 {
105                            break;
106                        }
107                    }
108                    None => break,
109                }
110            }
111
112            // ── Epoch tick ────────────────────────────────────────────────
113            #[cfg(feature = "std")]
114            {
115                let now = std::time::Instant::now();
116                if now.duration_since(last_epoch_tick)
117                    >= std::time::Duration::from_millis(100)
118                {
119                    self.core.epoch.fetch_add(1, Ordering::Relaxed);
120                    last_epoch_tick = now;
121                }
122            }
123            #[cfg(not(feature = "std"))]
124            {
125                let tick = self.daemon_tick.load(Ordering::Relaxed);
126                if tick % 100 == 0 {
127                    self.core.epoch.fetch_add(1, Ordering::Relaxed);
128                }
129            }
130
131            // ── Phase 1: Collect hit indices into accumulator ─────────────────
132            while let Some(batch) = self.hit_rx.try_recv() {
133                self.core.process_hits(&batch);
134                if self.core.hit_accumulator.len() >= 8192 {
135                    break;
136                }
137            }
138
139            // ── Maintenance (GC + hit processing + eviction) ──────────────
140            self.core.maintenance();
141
142            // ── Advance daemon_tick ───────────────────────────────────────
143            #[cfg(any(feature = "loom", loom))]
144            {
145                if processed > 0 {
146                    self.daemon_tick.fetch_add(1, Ordering::Relaxed);
147                }
148            }
149            #[cfg(not(any(feature = "loom", loom)))]
150            {
151                self.daemon_tick.fetch_add(1, Ordering::Relaxed);
152            }
153
154            // ── Idle sleep / spin ─────────────────────────────────────────
155            if processed == 0 {
156                #[cfg(any(feature = "loom", loom))]
157                loom::thread::yield_now();
158                #[cfg(all(feature = "std", not(any(feature = "loom", loom))))]
159                std::thread::sleep(std::time::Duration::from_micros(self.poll_us));
160                #[cfg(not(feature = "std"))]
161                core::hint::spin_loop();
162            }
163        }
164    }
165
166    #[inline(always)]
167    fn process_cmd(&mut self, cmd: Command<K, V>) -> bool {
168        match cmd {
169            Command::Insert(k, v, hash, is_t1) => {
170                self.core.handle_admission_insert(k, v, hash, is_t1);
171                true
172            }
173            Command::BatchInsert(batch) => {
174                for (k, v, hash, is_t1) in batch {
175                    self.core.handle_admission_insert(k, v, hash, is_t1);
176                }
177                true
178            }
179            Command::InsertT1(k, v, hash) => {
180                self.core.handle_insert_t1(k, v, hash);
181                true
182            }
183            Command::Remove(k, hash) => {
184                self.core.handle_remove(k, hash);
185                true
186            }
187            Command::Clear(ack) => {
188                self.core.handle_clear();
189                ack.signal();
190                true
191            }
192            Command::Sync(ack) => {
193                self.core.maintenance();
194                ack.signal();
195                true
196            }
197            Command::Shutdown => unreachable!("handled in run()"),
198        }
199    }
200}