Skip to main content

dualcache_ff/componant/
qsbr.rs

1#![allow(clippy::not_unsafe_ptr_arg_deref)]
2#![allow(clippy::missing_safety_doc)]
3use ::core::sync::atomic::{AtomicPtr, AtomicUsize, AtomicBool, Ordering};
4use core::ptr;
5
6static GLOBAL_EPOCH: AtomicUsize = AtomicUsize::new(1);
7static THREAD_STATES: AtomicPtr<ThreadStateNode> = AtomicPtr::new(ptr::null_mut());
8
9#[doc(hidden)]
10pub unsafe fn reset() {
11    GLOBAL_EPOCH.store(1, Ordering::SeqCst);
12    THREAD_STATES.store(ptr::null_mut(), Ordering::SeqCst);
13}
14
15#[derive(Copy, Clone)]
16struct RetiredNode {
17    index: u32,
18    epoch: u64,
19}
20
21const GARBAGE_CAP: usize = 16384;
22pub struct GarbageQueue {
23    items: [RetiredNode; GARBAGE_CAP],
24    head: AtomicUsize,
25    tail: AtomicUsize,
26}
27
28impl GarbageQueue {
29    const fn new() -> Self {
30        Self {
31            items: [RetiredNode { index: 0, epoch: 0 }; GARBAGE_CAP],
32            head: AtomicUsize::new(0),
33            tail: AtomicUsize::new(0),
34        }
35    }
36}
37
38pub struct MpmcQueue<const N: usize> {
39    head: AtomicUsize,
40    tail: AtomicUsize,
41    buffer: [::core::sync::atomic::AtomicU32; N],
42}
43
44impl<const N: usize> Default for MpmcQueue<N> {
45    fn default() -> Self {
46        Self::new()
47    }
48}
49
50impl<const N: usize> MpmcQueue<N> {
51    pub const fn new() -> Self {
52        Self {
53            head: AtomicUsize::new(0),
54            tail: AtomicUsize::new(0),
55            buffer: [const { ::core::sync::atomic::AtomicU32::new(u32::MAX) }; N],
56        }
57    }
58    pub fn push(&self, val: u32) -> bool {
59        let mut tail = self.tail.load(Ordering::Relaxed);
60        loop {
61            if tail.wrapping_sub(self.head.load(Ordering::Acquire)) >= N {
62                return false;
63            }
64            if self.tail.compare_exchange_weak(tail, tail + 1, Ordering::SeqCst, Ordering::Relaxed).is_ok() {
65                while self.buffer[tail % N].load(Ordering::Acquire) != u32::MAX {
66                    ::core::hint::spin_loop();
67                }
68                self.buffer[tail % N].store(val, Ordering::Release);
69                return true;
70            }
71            tail = self.tail.load(Ordering::Relaxed);
72        }
73    }
74    pub fn pop(&self) -> Option<u32> {
75        let mut head = self.head.load(Ordering::Relaxed);
76        loop {
77            if head == self.tail.load(Ordering::Acquire) {
78                return None;
79            }
80            let val = self.buffer[head % N].load(Ordering::Acquire);
81            if val != u32::MAX {
82                if self.head.compare_exchange_weak(head, head + 1, Ordering::SeqCst, Ordering::Relaxed).is_ok() {
83                    self.buffer[head % N].store(u32::MAX, Ordering::Release);
84                    return Some(val);
85                }
86            } else {
87                return None;
88            }
89            head = self.head.load(Ordering::Relaxed);
90        }
91    }
92}
93
94
95pub struct LocalFreeQueue {
96    items: [u32; 16384],
97    len: usize,
98}
99
100impl Default for LocalFreeQueue {
101    fn default() -> Self {
102        Self::new()
103    }
104}
105
106impl LocalFreeQueue {
107    pub const fn new() -> Self {
108        Self { items: [0; 16384], len: 0 }
109    }
110    pub fn pop(&mut self) -> Option<u32> {
111        if self.len > 0 {
112            self.len -= 1;
113            Some(self.items[self.len])
114        } else {
115            None
116        }
117    }
118    #[must_use]
119    pub fn push(&mut self, val: u32) -> bool {
120        if self.len < 16384 {
121            self.items[self.len] = val;
122            self.len += 1;
123            true
124        } else {
125            false
126        }
127    }
128}
129
130pub struct QsbrToken;
131
132#[cfg_attr(not(feature = "std"), no_std_tool::auto_static(capacity = 256, partition = "qsbr"))]
133pub struct ThreadStateNode {
134    pub active: AtomicBool,
135    pub epoch: AtomicUsize,
136    pub next: *mut ThreadStateNode,
137    pub garbage_queue: GarbageQueue,
138    pub local_free: core::cell::UnsafeCell<LocalFreeQueue>,
139}
140
141impl Default for ThreadStateNode {
142    fn default() -> Self {
143        Self::new()
144    }
145}
146
147impl ThreadStateNode {
148    pub const fn new() -> Self {
149        Self {
150            active: AtomicBool::new(false),
151            epoch: AtomicUsize::new(0),
152            next: ptr::null_mut(),
153            garbage_queue: GarbageQueue::new(),
154            local_free: core::cell::UnsafeCell::new(LocalFreeQueue::new()),
155        }
156    }
157}
158
159unsafe impl Send for ThreadStateNode {}
160unsafe impl Sync for ThreadStateNode {}
161
162/// Register a pre-allocated thread state node. The caller should allocate this node
163/// locally or in the static TLS blocks.
164pub fn register_node(node: *mut ThreadStateNode) {
165    let mut head = THREAD_STATES.load(Ordering::Acquire);
166    loop {
167        unsafe { (*node).next = head };
168        
169        // Yield to encourage a CAS collision for coverage
170        #[cfg(test)]
171        std::thread::yield_now();
172
173        match THREAD_STATES.compare_exchange_weak(
174            head,
175            node,
176            Ordering::Release,
177            Ordering::Relaxed,
178        ) {
179            Ok(_) => break,
180            Err(new_head) => {
181                head = new_head;
182                ::core::hint::spin_loop();
183            }
184        }
185    }
186}
187
188pub fn get_global_epoch() -> usize {
189    GLOBAL_EPOCH.load(Ordering::Relaxed)
190}
191
192/// A guard that pins the current thread to an epoch.
193pub struct Guard {
194    node: *mut ThreadStateNode,
195}
196
197impl Guard {
198    /// Create a new Guard using the explicitly provided ThreadStateNode.
199    pub fn new(node: *mut ThreadStateNode) -> Self {
200        let global_epoch = GLOBAL_EPOCH.load(Ordering::Acquire);
201        unsafe {
202            (*node).epoch.store(global_epoch, Ordering::Release);
203            (*node).active.store(true, Ordering::Release);
204        }
205        Self { node }
206    }
207
208    #[inline(always)]
209    pub unsafe fn unpinned(node: *mut ThreadStateNode) -> Self {
210        Self { node }
211    }
212    
213    #[inline(always)]
214    pub unsafe fn dummy() -> Self {
215        Self { node: core::ptr::null_mut() }
216    }
217    
218    /// Get the underlying ThreadStateNode pointer.
219    #[inline(always)]
220    pub fn node(&self) -> *mut ThreadStateNode {
221        self.node
222    }
223}
224
225impl Drop for Guard {
226    #[inline(always)]
227    fn drop(&mut self) {
228        if !self.node.is_null() {
229            let node_ref = unsafe { &*self.node };
230            node_ref.active.store(false, Ordering::Release);
231        }
232    }
233}
234
235pub fn pin_relaxed(node: *mut ThreadStateNode) -> Guard {
236    let node_ref = unsafe { &*node };
237    node_ref.active.store(true, Ordering::Relaxed);
238    Guard { node }
239}
240
241#[inline(always)]
242pub fn pin(node: *mut ThreadStateNode) -> Guard {
243    Guard::new(node)
244}
245
246
247/// Retire a node index into the thread-local garbage queue safely using QSBR.
248/// This prevents ABA by ensuring the index is not freed to the Arena until all threads observing it have advanced.
249pub fn retire<F: FnMut(u32)>(index: usize, node: *mut ThreadStateNode, mut _free_fn: F) {
250    let epoch = GLOBAL_EPOCH.load(Ordering::Acquire) as u64;
251    unsafe {
252        let q = &mut (*node).garbage_queue;
253        
254        // Wait until there's space (Daemon is slow, but we shouldn't drop)
255        let mut head = q.head.load(Ordering::Relaxed);
256        while head.wrapping_sub(q.tail.load(Ordering::Acquire)) >= GARBAGE_CAP {
257            // UPDATE EPOCH WHILE WAITING so daemon can advance min_epoch!
258            let cur_epoch = GLOBAL_EPOCH.load(Ordering::Relaxed);
259            (*node).epoch.store(cur_epoch, Ordering::Release);
260            ::core::hint::spin_loop();
261            head = q.head.load(Ordering::Relaxed);
262        }
263        
264        let idx = head % GARBAGE_CAP;
265        q.items[idx] = RetiredNode {
266            index: index as u32,
267            epoch,
268        };
269        q.head.store(head.wrapping_add(1), Ordering::Release);
270    }
271}
272
273/// The Daemon calls this globally to move safe nodes from GarbageQueues to Arena.
274pub fn daemon_reclaim<F: FnMut(&[u32])>(mut free_batch_fn: F) {
275    GLOBAL_EPOCH.fetch_add(1, Ordering::Relaxed);
276    let min_epoch = get_min_epoch();
277
278    let mut batch = [0u32; 128];
279    let mut batch_len = 0;
280
281    let mut node = THREAD_STATES.load(Ordering::Acquire);
282    while !node.is_null() {
283        unsafe {
284            let q = &mut (*node).garbage_queue;
285            let mut tail = q.tail.load(Ordering::Relaxed);
286            let head = q.head.load(Ordering::Acquire);
287            
288            while tail < head {
289                let idx = tail % GARBAGE_CAP;
290                let retired = q.items[idx];
291                
292                if retired.epoch < min_epoch as u64 {
293                    batch[batch_len] = retired.index;
294                    batch_len += 1;
295                    tail += 1;
296                    
297                    if batch_len == 128 {
298                        free_batch_fn(&batch[..batch_len]);
299                        batch_len = 0;
300                    }
301                } else {
302                    break;
303                }
304            }
305            q.tail.store(tail, Ordering::Release);
306            
307            node = (*node).next;
308        }
309    }
310    if batch_len > 0 {
311        free_batch_fn(&batch[..batch_len]);
312    }
313}
314
315fn get_min_epoch() -> usize {
316    let mut min_epoch = GLOBAL_EPOCH.load(Ordering::Acquire);
317    let mut node = THREAD_STATES.load(Ordering::Acquire);
318    while !node.is_null() {
319        unsafe {
320            if (*node).active.load(Ordering::Acquire) {
321                let e = (*node).epoch.load(Ordering::Acquire);
322                if e < min_epoch { min_epoch = e; }
323            }
324            node = (*node).next;
325        }
326    }
327    min_epoch
328}
329
330
331
332#[cfg(test)]
333mod tests {
334    use super::*;
335
336    #[test]
337    fn test_qsbr_thread_state_node_new() {
338        let node = ThreadStateNode::new();
339        assert_eq!(node.epoch.load(Ordering::Relaxed), 0);
340    }
341}