1extern crate alloc;
2#[cfg(not(feature = "std"))]
3use alloc::{vec, vec::Vec, boxed::Box};
4
5use crate::components::CachePadded;
6use crate::daemon::{Command, Daemon};
7use crate::lossy_queue::{LossyQueue, OneshotAck};
8use crate::unsafe_core::{Cache, T1, T2, WorkerSlot};
9use ahash::RandomState;
10use core::hash::{BuildHasher, Hash};
11use crate::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering};
12use crate::sync::index_types::{AtomicTick, TickType};
13use crate::sync::{Arc, ArcSlice, new_arc_slice};
14use crate::config::Config;
15
16#[cfg(any(feature = "loom", loom))]
22loom::lazy_static! {
23 pub static ref GLOBAL_EPOCH: loom::sync::atomic::AtomicUsize = loom::sync::atomic::AtomicUsize::new(1);
24}
25
26#[cfg(not(any(feature = "loom", loom)))]
27pub static GLOBAL_EPOCH: AtomicUsize = AtomicUsize::new(1);
28
29pub struct WorkerState {
32 pub local_epoch: CachePadded<AtomicUsize>,
33}
34
35impl Default for WorkerState {
36 fn default() -> Self {
37 Self::new()
38 }
39}
40
41impl WorkerState {
42 pub fn new() -> Self {
43 Self {
44 local_epoch: CachePadded::new(AtomicUsize::new(0)),
45 }
46 }
47}
48
49
50use crate::components::{DefaultTls, DefaultSpawner};
51
52pub struct DualCacheFF<K, V, S = RandomState> {
55 pub hasher: S,
56 pub t1: Arc<T1<K, V>>,
57 pub t2: Arc<T2<K, V>>,
58 pub cache: Arc<Cache<K, V>>,
59 pub cmd_tx: Arc<LossyQueue<Command<K, V>>>,
60 pub hit_tx: Arc<LossyQueue<[usize; 64]>>,
61 pub epoch: Arc<AtomicU32>,
62 pub worker_states: ArcSlice<WorkerState>,
64 pub miss_buffers: ArcSlice<WorkerSlot<K, V>>,
66 pub daemon_tick: Arc<AtomicTick>,
69 pub flush_tick_threshold: TickType,
71 pub is_cold_start: Arc<AtomicBool>,
73 pub tls: DefaultTls,
75}
76
77impl<K, V, S: Clone> Clone for DualCacheFF<K, V, S> {
78 fn clone(&self) -> Self {
79 Self {
80 hasher: self.hasher.clone(),
81 t1: self.t1.clone(),
82 t2: self.t2.clone(),
83 cache: self.cache.clone(),
84 cmd_tx: self.cmd_tx.clone(),
85 hit_tx: self.hit_tx.clone(),
86 epoch: self.epoch.clone(),
87 worker_states: self.worker_states.clone(),
88 miss_buffers: self.miss_buffers.clone(),
89 daemon_tick: self.daemon_tick.clone(),
90 flush_tick_threshold: self.flush_tick_threshold,
91 is_cold_start: self.is_cold_start.clone(),
92 tls: self.tls.clone(),
93 }
94 }
95}
96
97#[cfg(any(feature = "std", feature = "loom", loom))]
100impl<K, V> DualCacheFF<K, V, RandomState>
101where
102 K: Hash + Eq + Send + Sync + Clone + 'static,
103 V: Send + Sync + Clone + 'static,
104{
105 #[inline]
109 pub fn new(config: Config) -> Self {
110 Self::new_with_spawner(config, DefaultSpawner)
111 }
112}
113
114#[cfg(any(feature = "std", feature = "loom", loom))]
115impl<K, V> DualCacheFF<K, V, RandomState>
116where
117 K: Hash + Eq + Send + Sync + Clone + 'static,
118 V: Send + Sync + Clone + 'static,
119{
120 #[inline]
122 pub fn new_with_tls(config: Config, tls: DefaultTls) -> Self {
123 Self::new_with_tls_and_spawner(config, tls, DefaultSpawner)
124 }
125}
126
127impl<K, V> DualCacheFF<K, V, RandomState>
130where
131 K: Hash + Eq + Send + Sync + Clone + 'static,
132 V: Send + Sync + Clone + 'static,
133{
134 #[cfg(any(feature = "std", feature = "loom", loom))]
136 pub fn new_with_spawner(config: Config, spawner: DefaultSpawner) -> Self {
137 let (cache, daemon) = Self::new_headless(config);
138 spawner.spawn(alloc::boxed::Box::new(move || daemon.run()));
139 cache
140 }
141
142 pub fn new_headless(config: Config) -> (Self, Daemon<K, V, RandomState>) {
155 Self::new_headless_with_tls(config, DefaultTls)
156 }
157}
158
159impl<K, V> DualCacheFF<K, V, RandomState>
160where
161 K: Hash + Eq + Send + Sync + Clone + 'static,
162 V: Send + Sync + Clone + 'static,
163{
164 pub fn new_headless_with_tls(config: Config, tls: DefaultTls) -> (Self, Daemon<K, V, RandomState>) {
166 let hasher = RandomState::new();
167 let t1 = Arc::new(T1::new(config.t1_slots));
168 let t2 = Arc::new(T2::new(config.t2_slots));
169 let cache = Arc::new(Cache::new(config.capacity));
170 let cmd_q: Arc<LossyQueue<Command<K, V>>> = Arc::new(LossyQueue::new(8192));
171 let hit_q: Arc<LossyQueue<[usize; 64]>> = Arc::new(LossyQueue::new(1024));
172 let epoch = Arc::new(AtomicU32::new(0));
173 let daemon_tick = Arc::new(AtomicTick::new(0));
174 let is_cold_start = Arc::new(AtomicBool::new(true));
175
176 let mut buffers = Vec::with_capacity(config.threads);
177 let mut states = Vec::with_capacity(config.threads);
178 for _ in 0..config.threads {
179 buffers.push(WorkerSlot::new());
180 states.push(WorkerState::new());
181 }
182 let miss_buffers = new_arc_slice(buffers);
183 let worker_states = new_arc_slice(states);
184
185 let daemon = Daemon::new(
186 hasher.clone(),
187 config.capacity,
188 t1.clone(),
189 t2.clone(),
190 cache.clone(),
191 cmd_q.clone(),
192 hit_q.clone(),
193 epoch.clone(),
194 config.duration,
195 config.poll_us,
196 worker_states.clone(),
197 daemon_tick.clone(),
198 is_cold_start.clone(),
199 );
200
201 let this = Self {
202 hasher,
203 t1,
204 t2,
205 cache,
206 cmd_tx: cmd_q,
207 hit_tx: hit_q,
208 epoch,
209 worker_states,
210 miss_buffers,
211 daemon_tick,
212 flush_tick_threshold: (config.poll_us as TickType).max(1),
213 is_cold_start,
214 tls,
215 };
216
217 (this, daemon)
218 }
219
220 #[cfg(any(feature = "std", feature = "loom", loom))]
222 pub fn new_with_tls_and_spawner(config: Config, tls: DefaultTls, spawner: DefaultSpawner) -> Self
223 {
224 let (cache, daemon) = Self::new_headless_with_tls(config, tls);
225 spawner.spawn(alloc::boxed::Box::new(move || daemon.run()));
226 cache
227 }
228
229}
230
231impl<K, V, S> DualCacheFF<K, V, S>
234where
235 K: Hash + Eq + Send + Sync + Clone + 'static,
236 V: Send + Sync + Clone + 'static,
237 S: BuildHasher + Clone + Send + 'static,
238{
239 pub fn sync(&self) {
243 self.with_hit_buf(|state| {
245 if state.1 > 0_usize {
246 let _ = self.hit_tx.try_send(state.0);
247 state.1 = 0;
248 }
249 });
250
251 for slot in self.miss_buffers.iter() {
253 let buf: &mut crate::unsafe_core::BatchBuf<K, V> = slot.get_mut_safe();
254 if !buf.is_empty() {
255 let batch = buf.drain_to_vec();
256 let _ = self.cmd_tx.try_send(Command::BatchInsert(batch));
257 }
258 }
259
260 let ack = OneshotAck::new();
262 self.cmd_tx.send_blocking(Command::Sync(ack.clone()));
263 ack.wait();
264 }
265
266 pub fn get(&self, key: &K) -> Option<V> {
271 let hash = self.hash(key);
272 let current_epoch_cache = self.epoch.load(Ordering::Relaxed);
273
274 let mut id_opt = None;
276 let global_epoch = GLOBAL_EPOCH.load(Ordering::Relaxed);
277 self.with_worker_id(|id| {
278 if id < self.worker_states.len() {
279 self.worker_states[id]
280 .local_epoch
281 .store(global_epoch, Ordering::Relaxed);
282 id_opt = Some(id);
283 }
284 });
285
286 let has_epoch = id_opt.is_some() || {
287 #[cfg(not(feature = "std"))]
288 { true }
289 #[cfg(feature = "std")]
290 { false }
291 };
292
293 let mut res: Option<V> = None;
294 let mut hit_g_idx: Option<u32> = None;
295
296 if has_epoch {
297 if let Some(node) = self.t1.get_node(hash)
299 && node.key == *key
300 && (node.expire_at == 0 || node.expire_at >= current_epoch_cache)
301 {
302 res = Some(node.value.clone());
303 hit_g_idx = Some(node.g_idx);
304 self.tls.with_warmup_state(|s| *s = s.saturating_add(10));
305 }
306
307 if res.is_none()
309 && let Some(node) = self.t2.get_node(hash)
310 && node.key == *key
311 && (node.expire_at == 0 || node.expire_at >= current_epoch_cache)
312 {
313 res = Some(node.value.clone());
314 hit_g_idx = Some(node.g_idx);
315 }
317
318 if res.is_none() {
320 let tag = (hash >> 48) as u16;
321 if let Some(global_idx) = self.cache.index_probe(hash, tag)
322 && let Some(v) = self
323 .cache
324 .node_get_full(global_idx, key, current_epoch_cache)
325 {
326 res = Some(v);
327 hit_g_idx = Some(global_idx as u32);
328 self.tls.with_warmup_state(|s| *s = s.saturating_sub(10));
329 }
330 }
331 }
332
333 if let Some(id) = id_opt {
335 self.worker_states[id]
336 .local_epoch
337 .store(0, Ordering::Relaxed);
338 }
339
340 if let Some(g_idx) = hit_g_idx {
341 self.record_hit(g_idx as usize);
342 }
343
344 res
345 }
346
347 pub fn insert(&self, key: K, value: V) {
361 let hash = self.hash(&key);
362
363 let mut id_opt = None;
364 let is_cold = self.is_cold_start.load(Ordering::Relaxed);
365 let mut bypass = is_cold;
366
367 if !bypass {
368 let global_epoch = GLOBAL_EPOCH.load(Ordering::Relaxed);
371 self.with_worker_id(|id| {
372 if id < self.worker_states.len() {
373 self.worker_states[id]
374 .local_epoch
375 .store(global_epoch, Ordering::Relaxed);
376 id_opt = Some(id);
377 }
378 });
379
380 if id_opt.is_some() {
381 if self.t1.get_node(hash).is_some_and(|node| node.key == key) {
383 bypass = true;
384 }
385
386 if !bypass
388 && self.t2.get_node(hash).is_some_and(|node| node.key == key) {
389 bypass = true;
390 }
391
392 if !bypass {
394 let tag = (hash >> 48) as u16;
395 if self.cache.index_probe(hash, tag)
396 .and_then(|global_idx| self.cache.get_node(global_idx))
397 .is_some_and(|node| node.key == key)
398 {
399 bypass = true;
400 }
401 }
402 }
403
404 if let Some(id) = id_opt {
406 self.worker_states[id]
407 .local_epoch
408 .store(0, Ordering::Relaxed);
409 }
410 }
411
412 let pass = if bypass {
413 true
414 } else {
415 self.with_l1_filter(|state| {
417 let idx = (hash as usize) & 4095_usize;
418 let val = state.0[idx];
419
420 state.1 += 1;
421 if state.1 >= 4096_usize {
422 for x in state.0.iter_mut() {
423 *x >>= 1;
424 }
425 state.1 = 0;
426 }
427
428 if val < 1_u8 {
429 state.0[idx] = 1;
430 false
431 } else {
432 if val < 2_u8 {
433 state.0[idx] = 2;
434 }
435 true
436 }
437 }).unwrap_or(true) };
439
440 if !pass {
441 return;
442 }
443
444 let mut warmup_state = 255;
445 self.tls.with_warmup_state(|s| warmup_state = *s);
446 let is_t1 = warmup_state < 100;
447
448 let current_tick = self.daemon_tick.load(Ordering::Relaxed);
450 let mut should_time_flush = false;
451 self.with_last_flush_tick(|tick| {
452 should_time_flush = current_tick.wrapping_sub(*tick) >= self.flush_tick_threshold;
453 });
454
455 let mut option_kv = Some((key, value));
457 let pushed_to_buf = self.with_worker_id(|id| {
458 let (k, v) = option_kv.take().unwrap();
459 if id >= self.miss_buffers.len() {
460 let _ = self.cmd_tx.try_send(Command::Insert(k, v, hash, is_t1));
462 return;
463 }
464
465 let buf: &mut crate::unsafe_core::BatchBuf<K, V> = self.miss_buffers[id].get_mut_safe();
467 let capacity_flush = buf.push((k, v, hash, is_t1));
468
469 if capacity_flush || (should_time_flush && !buf.is_empty()) {
470 let batch = buf.drain_to_vec();
471 let _ = self.cmd_tx.try_send(Command::BatchInsert(batch));
472 self.with_last_flush_tick(|tick| {
473 *tick = current_tick;
474 });
475 }
476 });
477
478 if pushed_to_buf.is_none()
479 && let Some((k, v)) = option_kv {
480 let _ = self.cmd_tx.try_send(Command::Insert(k, v, hash, is_t1));
481 }
482 }
483
484 pub fn begin_cold_start_session(&self) -> ColdStartSession<'_, K, V, S> {
486 ColdStartSession { cache: self }
487 }
488
489 pub fn remove(&self, key: &K) {
491 let hash = self.hash(key);
492
493 self.with_worker_id(|id| {
495 if id < self.miss_buffers.len() {
496 let buf: &mut crate::unsafe_core::BatchBuf<K, V> = self.miss_buffers[id].get_mut_safe();
497 if !buf.is_empty() {
498 let batch = buf.drain_to_vec();
499 let _ = self.cmd_tx.try_send(Command::BatchInsert(batch));
500 let tick = self.daemon_tick.load(Ordering::Relaxed);
501 self.with_last_flush_tick(|c| *c = tick);
502 }
503 }
504 });
505
506 self.cmd_tx.send_blocking(Command::Remove(key.clone(), hash));
507 }
508
509 pub fn clear(&self) {
511 let ack = OneshotAck::new();
512 self.cmd_tx.send_blocking(Command::Clear(ack.clone()));
513 ack.wait();
514 }
515
516 #[inline(always)]
519 fn with_worker_id<F, R>(&self, f: F) -> Option<R>
520 where
521 F: FnOnce(usize) -> R,
522 {
523 self.tls.get_worker_id().map(f)
524 }
525
526 #[inline(always)]
527 fn with_hit_buf<F, R>(&self, f: F) -> Option<R>
528 where
529 F: FnOnce(&mut ([usize; 64], usize)) -> R,
530 {
531 self.tls.with_hit_buf(f)
532 }
533
534 #[inline(always)]
535 fn with_l1_filter<F, R>(&self, f: F) -> Option<R>
536 where
537 F: FnOnce(&mut ([u8; 4096], usize)) -> R,
538 {
539 self.tls.with_l1_filter(f)
540 }
541
542 #[inline(always)]
543 fn with_last_flush_tick<F, R>(&self, f: F) -> Option<R>
544 where
545 F: FnOnce(&mut TickType) -> R,
546 {
547 self.tls.with_last_flush_tick(f)
548 }
549
550 #[inline(always)]
551 fn hash(&self, key: &K) -> u64 {
552 self.hasher.hash_one(key)
553 }
554
555 #[inline(always)]
560 fn record_hit(&self, global_idx: usize) {
561 let opt = self.with_hit_buf(|state| {
562 let idx = state.1;
563 state.0[idx] = global_idx;
564 state.1 += 1;
565 if state.1 == 64_usize {
566 let _ = self.hit_tx.try_send(state.0);
567 state.0 = [usize::MAX; 64];
568 state.1 = 0;
569 }
570 });
571
572 if opt.is_none() {
573 let mut batch = [usize::MAX; 64];
574 batch[0] = global_idx;
575 let _ = self.hit_tx.try_send(batch);
576 }
577 }
578}
579
580impl<K, V, S> Drop for DualCacheFF<K, V, S> {
581 fn drop(&mut self) {
582 if Arc::strong_count(&self.cmd_tx) <= 2 {
583 let _ = self.cmd_tx.try_send(Command::Shutdown);
584 }
585 }
586}
587
588
589pub struct ColdStartSession<'a, K, V, S> {
593 cache: &'a DualCacheFF<K, V, S>,
594}
595
596impl<'a, K, V, S> ColdStartSession<'a, K, V, S>
597where
598 K: Hash + Eq + Send + Sync + Clone + 'static,
599 V: Send + Sync + Clone + 'static,
600 S: BuildHasher + Clone + Send + 'static,
601{
602 pub fn warmup(&self, key: K, value: V) {
606 let hash = self.cache.hash(&key);
607 let _ = self.cache.cmd_tx.try_send(Command::InsertT1(key, value, hash));
608 }
609}