#[cfg(not(loom))]
use std::sync::atomic::{AtomicPtr as StdAtomicPtr, AtomicU8, AtomicUsize};
use std::{ops::Deref, ptr::NonNull, sync::atomic::Ordering};
#[cfg(loom)]
use loom::sync::atomic::{AtomicPtr as StdAtomicPtr, AtomicU8, AtomicUsize};
const DEFAULT_COLLECTION_THRESHOLD: u8 = 8;
#[derive(Debug, Default)]
struct HazardNode {
hazard: StdAtomicPtr<()>,
next: StdAtomicPtr<HazardNode>,
}
impl HazardNode {
#[cfg(not(loom))]
const fn new(ptr: *mut ()) -> Self {
Self {
hazard: StdAtomicPtr::new(ptr),
next: StdAtomicPtr::new(std::ptr::null_mut()),
}
}
#[cfg(loom)]
fn new(ptr: *mut ()) -> Self {
Self {
hazard: StdAtomicPtr::new(ptr),
next: StdAtomicPtr::new(std::ptr::null_mut()),
}
}
}
struct RetiredNode {
ptr: *mut (),
deleter: unsafe fn(*mut ()),
next: *mut RetiredNode,
}
pub struct Replaced<T> {
ptr: Option<NonNull<T>>,
}
impl<T> Replaced<T> {
pub fn retire(self, domain: &Domain) {
if let Some(ptr) = self.ptr {
unsafe { domain.retire_ptr(ptr.as_ptr()) };
}
std::mem::forget(self);
}
pub fn forget(self) {
std::mem::forget(self);
}
}
impl<T> Drop for Replaced<T> {
fn drop(&mut self) {
#[cfg(debug_assertions)]
eprintln!("lockout_hazard: Replaced<T> dropped without retiring — memory leaked");
}
}
#[derive(Debug)]
pub struct AtomicPtr<T> {
inner: StdAtomicPtr<T>,
}
impl<T> AtomicPtr<T> {
#[cfg(not(loom))]
pub const fn new(ptr: *mut T) -> Self {
Self {
inner: StdAtomicPtr::new(ptr),
}
}
#[cfg(loom)]
pub fn new(ptr: *mut T) -> Self {
Self {
inner: StdAtomicPtr::new(ptr),
}
}
pub fn from_box(val: Box<T>) -> Self {
Self::new(Box::into_raw(val))
}
pub fn load(&self, order: Ordering) -> *mut T {
self.inner.load(order)
}
pub fn swap(&self, new: *mut T, order: Ordering) -> Replaced<T> {
let old = self.inner.swap(new, order);
Replaced {
ptr: NonNull::new(old),
}
}
pub fn compare_exchange(
&self,
current: *mut T,
new: *mut T,
success: Ordering,
failure: Ordering,
) -> Result<Replaced<T>, *mut T> {
self.inner
.compare_exchange(current, new, success, failure)
.map(|old| Replaced {
ptr: NonNull::new(old),
})
}
}
impl<T> From<StdAtomicPtr<T>> for AtomicPtr<T> {
fn from(ptr: StdAtomicPtr<T>) -> Self {
Self { inner: ptr }
}
}
impl<T> From<Box<T>> for AtomicPtr<T> {
fn from(val: Box<T>) -> Self {
Self::from_box(val)
}
}
#[derive(Debug)]
pub struct Guard<'a, T> {
slot: &'a StdAtomicPtr<()>,
ptr: *mut T,
}
unsafe impl<T: Send + Sync> Send for Guard<'_, T> {}
unsafe impl<T: Send + Sync> Sync for Guard<'_, T> {}
impl<'a, T> Guard<'a, T> {
fn new(slot: &'a StdAtomicPtr<()>, ptr: *mut T) -> Self {
Self { slot, ptr }
}
pub fn get(&self) -> &T {
unsafe { &*self.ptr }
}
pub fn as_raw(&self) -> *mut T {
self.ptr
}
fn set_null(&self) {
self.slot.store(std::ptr::null_mut(), Ordering::SeqCst);
}
pub fn clear(self) {
self.set_null();
std::mem::forget(self);
}
}
impl<T> Deref for Guard<'_, T> {
type Target = T;
fn deref(&self) -> &T {
self.get()
}
}
impl<T> Drop for Guard<'_, T> {
fn drop(&mut self) {
self.set_null();
}
}
#[derive(Debug)]
pub struct Domain<const COLLECTION_THRESHOLD: u8 = DEFAULT_COLLECTION_THRESHOLD> {
hazard_list: HazardNode,
hazard_count: AtomicUsize,
retired_head: StdAtomicPtr<RetiredNode>,
retire_count: AtomicU8,
}
unsafe impl Send for Domain {}
unsafe impl Sync for Domain {}
impl Default for Domain<DEFAULT_COLLECTION_THRESHOLD> {
fn default() -> Self {
Self::new()
}
}
impl<const COLLECTION_THRESHOLD: u8> Drop for Domain<COLLECTION_THRESHOLD> {
fn drop(&mut self) {
let mut retired = self.retired_head.load(Ordering::Relaxed);
while !retired.is_null() {
let node = unsafe { Box::from_raw(retired) };
unsafe { (node.deleter)(node.ptr) };
retired = node.next;
}
let mut next = self.hazard_list.next.load(Ordering::Relaxed);
while !next.is_null() {
let node = unsafe { Box::from_raw(next) };
next = node.next.load(Ordering::Relaxed);
}
}
}
impl Domain {
#[cfg(not(loom))]
pub const fn new() -> Self {
Self::with_threshold()
}
#[cfg(loom)]
pub fn new() -> Self {
Self::with_threshold()
}
}
impl<const COLLECTION_THRESHOLD: u8> Domain<COLLECTION_THRESHOLD> {
#[cfg(not(loom))]
pub const fn with_threshold() -> Self {
Self {
hazard_list: HazardNode::new(std::ptr::null_mut()),
hazard_count: AtomicUsize::new(1),
retired_head: StdAtomicPtr::new(std::ptr::null_mut()),
retire_count: AtomicU8::new(0),
}
}
#[cfg(loom)]
pub fn with_threshold() -> Self {
Self {
hazard_list: HazardNode::new(std::ptr::null_mut()),
hazard_count: AtomicUsize::new(1),
retired_head: StdAtomicPtr::new(std::ptr::null_mut()),
retire_count: AtomicU8::new(0),
}
}
pub fn protect<T>(&self, ptr: &AtomicPtr<T>) -> Option<Guard<'_, T>> {
self.protect_atomic(&ptr.inner)
}
fn protect_atomic<T>(&self, ptr: &StdAtomicPtr<T>) -> Option<Guard<'_, T>> {
loop {
let ptr_before = ptr.load(Ordering::SeqCst);
if ptr_before.is_null() {
return None;
}
let guard = self.reserve(ptr_before);
let ptr_after = ptr.load(Ordering::SeqCst);
if ptr_after == ptr_before {
return Some(guard);
}
#[cfg(loom)]
loom::thread::yield_now();
}
}
pub unsafe fn protect_ptr<T>(&self, ptr: *mut T) -> Option<Guard<'_, T>> {
if ptr.is_null() {
return None;
}
Some(self.reserve(ptr))
}
fn reserve<T>(&self, ptr: *mut T) -> Guard<'_, T> {
let mut current = &self.hazard_list;
loop {
if current
.hazard
.compare_exchange_weak(
std::ptr::null_mut(),
ptr as *mut (),
Ordering::SeqCst,
Ordering::Relaxed,
)
.is_ok()
{
return Guard::new(¤t.hazard, ptr);
}
let next = current.next.load(Ordering::Acquire);
if !next.is_null() {
current = unsafe { next.as_ref().unwrap_unchecked() };
#[cfg(loom)]
loom::thread::yield_now();
continue;
}
let new_node = Box::into_raw(Box::new(HazardNode::new(ptr as *mut ())));
match current.next.compare_exchange(
std::ptr::null_mut(),
new_node,
Ordering::Release,
Ordering::Acquire,
) {
Ok(_) => {
self.hazard_count.fetch_add(1, Ordering::SeqCst);
return Guard::new(
unsafe { &new_node.as_ref().unwrap_unchecked().hazard },
ptr,
);
}
Err(_) => {
drop(unsafe { Box::from_raw(new_node) });
current = unsafe {
current
.next
.load(Ordering::Acquire)
.as_ref()
.unwrap_unchecked()
};
#[cfg(loom)]
loom::thread::yield_now();
}
}
}
}
fn push_retired(&self, node: *mut RetiredNode) {
loop {
let head = self.retired_head.load(Ordering::Relaxed);
unsafe { (*node).next = head };
if self
.retired_head
.compare_exchange_weak(head, node, Ordering::Release, Ordering::Relaxed)
.is_ok()
{
return;
}
#[cfg(loom)]
loom::thread::yield_now();
}
}
pub unsafe fn retire_ptr<T>(&self, ptr: *mut T) {
unsafe fn deleter<T>(p: *mut ()) {
drop(unsafe { Box::from_raw(p as *mut T) });
}
let node = Box::into_raw(Box::new(RetiredNode {
ptr: ptr as *mut (),
deleter: deleter::<T>,
next: std::ptr::null_mut(),
}));
self.push_retired(node);
let count = self.retire_count.fetch_add(1, Ordering::Relaxed) + 1;
if count.is_multiple_of(COLLECTION_THRESHOLD) {
self.collect();
}
}
pub fn collect(&self) {
self.retire_count.store(0, Ordering::Relaxed);
let mut retired = self
.retired_head
.swap(std::ptr::null_mut(), Ordering::Acquire);
if retired.is_null() {
return;
}
let hazard_ptrs = loop {
let expected_nodes = self.hazard_count.load(Ordering::SeqCst);
let mut seen_nodes = 0usize;
let mut hazard_ptrs = Vec::new();
let mut current = &self.hazard_list;
loop {
seen_nodes += 1;
let ptr = current.hazard.load(Ordering::SeqCst);
if !ptr.is_null() {
hazard_ptrs.push(ptr);
}
let next = current.next.load(Ordering::SeqCst);
if next.is_null() {
break;
}
current = unsafe { &*next };
}
let final_nodes = self.hazard_count.load(Ordering::SeqCst);
if seen_nodes == expected_nodes && final_nodes == expected_nodes {
break hazard_ptrs;
}
};
while !retired.is_null() {
let node = unsafe { Box::from_raw(retired) };
retired = node.next;
if hazard_ptrs.contains(&node.ptr) {
let raw = Box::into_raw(node);
self.push_retired(raw);
} else {
unsafe { (node.deleter)(node.ptr) };
}
}
}
}