use core::alloc::Layout;
#[cfg(feature = "threading")]
const QSBR_OFFLINE: u64 = 0;
#[cfg(feature = "threading")]
const QSBR_INITIAL: u64 = 1;
#[cfg(feature = "threading")]
const QSBR_INCR: u64 = 2;
#[cfg(feature = "threading")]
pub(crate) use threading::*;
#[cfg(feature = "threading")]
mod threading {
use super::*;
use alloc::sync::{Arc, Weak};
use core::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::Mutex;
pub(crate) struct QsbrSlot {
seq: AtomicU64,
pub(crate) requested: AtomicBool,
}
struct Retired {
ptr: *mut u8,
layout: Layout,
goal: u64,
}
unsafe impl Send for Retired {}
pub(crate) struct Qsbr {
wr_seq: AtomicU64,
rd_seq: AtomicU64,
threads: Mutex<Vec<Weak<QsbrSlot>>>,
queue: Mutex<Vec<Retired>>,
pending: AtomicBool,
}
pub(crate) static QSBR: Qsbr = Qsbr::new();
impl Qsbr {
const fn new() -> Self {
Self {
wr_seq: AtomicU64::new(QSBR_INITIAL),
rd_seq: AtomicU64::new(QSBR_INITIAL),
threads: Mutex::new(Vec::new()),
queue: Mutex::new(Vec::new()),
pending: AtomicBool::new(false),
}
}
#[inline]
#[cfg_attr(not(test), allow(dead_code))]
pub(crate) fn break_pending(&self) -> bool {
self.pending.load(Ordering::Relaxed)
}
fn update_breaker_bit(&self, on: bool) {
if core::ptr::eq(self, &QSBR) {
if on {
crate::signal::set_qsbr_bit();
} else {
crate::signal::clear_qsbr_bit();
}
}
}
pub(crate) fn register(&self) -> Arc<QsbrSlot> {
let slot = Arc::new(QsbrSlot {
seq: AtomicU64::new(self.wr_seq.load(Ordering::Acquire)),
requested: AtomicBool::new(false),
});
self.threads.lock().unwrap().push(Arc::downgrade(&slot));
slot
}
fn advance(&self) -> u64 {
self.wr_seq.fetch_add(QSBR_INCR, Ordering::AcqRel) + QSBR_INCR
}
pub(crate) fn quiescent_state(&self, slot: &QsbrSlot) {
slot.seq
.store(self.wr_seq.load(Ordering::Acquire), Ordering::Release);
}
pub(crate) fn offline(&self, slot: &QsbrSlot) {
slot.seq.store(QSBR_OFFLINE, Ordering::Release);
}
pub(crate) fn online(&self, slot: &QsbrSlot) {
self.quiescent_state(slot);
}
fn poll(&self, goal: u64) -> bool {
if self.rd_seq.load(Ordering::Acquire) >= goal {
return true;
}
self.poll_scan() >= goal
}
fn poll_scan(&self) -> u64 {
let mut min_seq = self.wr_seq.load(Ordering::Acquire);
let mut threads = self.threads.lock().unwrap();
threads.retain(|weak| match weak.upgrade() {
Some(slot) => {
let seq = slot.seq.load(Ordering::Acquire);
if seq != QSBR_OFFLINE {
min_seq = min_seq.min(seq);
}
true
}
None => false,
});
drop(threads);
self.rd_seq.fetch_max(min_seq, Ordering::AcqRel);
min_seq
}
pub(crate) unsafe fn free_delayed(&self, ptr: *mut u8, layout: Layout) {
let goal = self.advance();
{
let mut queue = self.queue.lock().unwrap();
queue.push(Retired { ptr, layout, goal });
self.pending.store(true, Ordering::Release);
self.update_breaker_bit(true);
}
for weak in self.threads.lock().unwrap().iter() {
if let Some(slot) = weak.upgrade() {
slot.requested.store(true, Ordering::Release);
}
}
}
pub(crate) fn process(&self) {
let Ok(mut queue) = self.queue.try_lock() else {
return;
};
let safe_prefix = queue
.iter()
.position(|item| !self.poll(item.goal))
.unwrap_or(queue.len());
for item in queue.drain(..safe_prefix) {
unsafe { alloc::alloc::dealloc(item.ptr, item.layout) };
}
if queue.is_empty() {
self.pending.store(false, Ordering::Release);
self.update_breaker_bit(false);
}
}
#[cfg(unix)]
pub(crate) unsafe fn drain_all(&self) {
let mut queue = self.queue.lock().unwrap();
for item in queue.drain(..) {
unsafe { alloc::alloc::dealloc(item.ptr, item.layout) };
}
self.pending.store(false, Ordering::Release);
self.update_breaker_bit(false);
}
#[cfg(unix)]
pub(crate) unsafe fn reset_after_fork(&self) {
self.threads.lock().unwrap().clear();
unsafe { self.drain_all() };
}
#[cfg(test)]
fn pending(&self) -> usize {
self.queue.lock().unwrap().len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn poll_requires_all_online_threads() {
let q = Qsbr::new();
let a = q.register();
let b = q.register();
let goal = q.advance();
assert!(!q.poll(goal));
q.quiescent_state(&a);
assert!(!q.poll(goal));
q.quiescent_state(&b);
assert!(q.poll(goal));
}
#[test]
fn offline_thread_does_not_delay_grace() {
let q = Qsbr::new();
let a = q.register();
let b = q.register();
let goal = q.advance();
q.quiescent_state(&a);
q.offline(&b);
assert!(q.poll(goal));
}
#[test]
fn dead_thread_is_pruned() {
let q = Qsbr::new();
let a = q.register();
let b = q.register();
drop(b);
let goal = q.advance();
q.quiescent_state(&a);
assert!(q.poll(goal));
}
#[test]
fn process_frees_only_after_grace() {
let q = Qsbr::new();
let a = q.register();
let layout = Layout::new::<u64>();
let ptr = unsafe { alloc::alloc::alloc(layout) };
unsafe { q.free_delayed(ptr, layout) };
assert!(a.requested.load(Ordering::Acquire));
assert!(q.break_pending());
q.process();
assert_eq!(q.pending(), 1); assert!(q.break_pending());
q.quiescent_state(&a);
q.process();
assert_eq!(q.pending(), 0);
assert!(!q.break_pending());
}
}
}
#[inline]
pub(crate) unsafe fn free_delayed(ptr: *mut u8, layout: Layout) {
#[cfg(feature = "threading")]
unsafe {
QSBR.free_delayed(ptr, layout)
};
#[cfg(not(feature = "threading"))]
unsafe {
alloc::alloc::dealloc(ptr, layout)
};
}