Skip to main content

yip_io/
nonce.rs

1use crossbeam_utils::CachePadded;
2use std::sync::atomic::{AtomicU64, Ordering};
3
4pub const DEFAULT_NONCE_CHUNK_SIZE: u64 = 64;
5
6/// Dispenses nonces in contiguous chunks to worker threads to prevent
7/// atomic MESI cache-line contention across multi-core systems.
8pub struct ChunkedNonceDispenser {
9    next_nonce: CachePadded<AtomicU64>,
10    chunk_size: u64,
11}
12
13impl ChunkedNonceDispenser {
14    pub fn new(chunk_size: u64) -> Self {
15        Self {
16            next_nonce: CachePadded::new(AtomicU64::new(0)),
17            chunk_size: chunk_size.max(1),
18        }
19    }
20
21    pub fn claim_chunk(&self) -> LocalNonceWindow {
22        let base = self
23            .next_nonce
24            .fetch_add(self.chunk_size, Ordering::Relaxed);
25        LocalNonceWindow {
26            current: base,
27            limit: base.saturating_add(self.chunk_size),
28        }
29    }
30}
31
32/// Thread-local nonce allocation window.
33#[derive(Debug, Clone, Copy)]
34pub struct LocalNonceWindow {
35    current: u64,
36    limit: u64,
37}
38
39impl LocalNonceWindow {
40    pub fn empty() -> Self {
41        Self {
42            current: 0,
43            limit: 0,
44        }
45    }
46
47    /// Dispenses the next 64-bit nonce. Increments local counter with zero atomic instructions.
48    /// Replenishes from `dispenser` when the local chunk is exhausted.
49    #[inline]
50    pub fn next_nonce(&mut self, dispenser: &ChunkedNonceDispenser) -> Option<u64> {
51        if self.current < self.limit {
52            let n = self.current;
53            self.current += 1;
54            Some(n)
55        } else {
56            *self = dispenser.claim_chunk();
57            if self.current < self.limit {
58                let n = self.current;
59                self.current += 1;
60                Some(n)
61            } else {
62                None
63            }
64        }
65    }
66}