use core::cell::Cell;
use core::ptr;
use core::sync::atomic::{AtomicBool, AtomicPtr, Ordering};
pub(crate) const SLOTS: usize = 8;
#[repr(align(128))]
pub(crate) struct Record {
slots: [AtomicPtr<()>; SLOTS],
in_use: AtomicBool,
next: AtomicPtr<Record>,
}
static HEAD: AtomicPtr<Record> = AtomicPtr::new(ptr::null_mut());
struct Local {
record: Cell<*const Record>,
}
impl Drop for Local {
fn drop(&mut self) {
let record = self.record.get();
if !record.is_null() {
let record = unsafe { &*record };
for slot in &record.slots {
slot.store(ptr::null_mut(), Ordering::Release);
}
record.in_use.store(false, Ordering::Release);
}
}
}
std::thread_local! {
static LOCAL: Local = const { Local { record: Cell::new(ptr::null()) } };
}
pub(crate) struct Pin {
slot: &'static AtomicPtr<()>,
}
impl Pin {
#[inline]
pub(crate) fn protect(&self, p: *const ()) {
self.slot.store(p.cast_mut(), Ordering::SeqCst);
}
}
impl Drop for Pin {
#[inline]
fn drop(&mut self) {
self.slot.store(ptr::null_mut(), Ordering::Release);
}
}
#[inline]
pub(crate) fn pin(p: *const ()) -> Option<Pin> {
LOCAL
.try_with(|local| {
let mut record = local.record.get();
if record.is_null() {
record = acquire_record();
local.record.set(record);
}
let record: &'static Record = unsafe { &*record };
record
.slots
.iter()
.find(|slot| slot.load(Ordering::Relaxed).is_null())
.map(|slot| {
let pin = Pin { slot };
pin.protect(p);
pin
})
})
.ok()
.flatten()
}
#[cfg(test)]
pub(crate) fn this_thread_protects(p: *const ()) -> bool {
LOCAL
.try_with(|local| {
let record = local.record.get();
if record.is_null() {
return false;
}
let record = unsafe { &*record };
record
.slots
.iter()
.any(|slot| slot.load(Ordering::Relaxed).cast_const() == p)
})
.unwrap_or(false)
}
pub(crate) fn announced() -> Vec<*const ()> {
let mut out = Vec::new();
for_each_record(|record| {
for slot in &record.slots {
let p = slot.load(Ordering::SeqCst);
if !p.is_null() {
out.push(p.cast_const());
}
}
});
out
}
fn for_each_record(mut f: impl FnMut(&'static Record)) {
let mut cursor = HEAD.load(Ordering::SeqCst);
while !cursor.is_null() {
let record: &'static Record = unsafe { &*cursor };
f(record);
cursor = record.next.load(Ordering::Acquire);
}
}
#[cold]
fn acquire_record() -> *const Record {
let mut reused: *const Record = ptr::null();
for_each_record(|record| {
if reused.is_null()
&& record
.in_use
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
.is_ok()
{
reused = record;
}
});
if !reused.is_null() {
return reused;
}
let record: &'static Record = Box::leak(Box::new(Record {
slots: [const { AtomicPtr::new(ptr::null_mut()) }; SLOTS],
in_use: AtomicBool::new(true),
next: AtomicPtr::new(ptr::null_mut()),
}));
let raw = ptr::from_ref(record).cast_mut();
let mut head = HEAD.load(Ordering::Acquire);
loop {
record.next.store(head, Ordering::Relaxed);
match HEAD.compare_exchange_weak(head, raw, Ordering::SeqCst, Ordering::Acquire) {
Ok(_) => return raw,
Err(actual) => head = actual,
}
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn pin_publishes_and_clears() {
let target: *const () = core::ptr::without_provenance(0x1000);
{
let _pin = pin(target).expect("slot available");
assert!(this_thread_protects(target));
assert!(announced().contains(&target));
}
assert!(!this_thread_protects(target));
}
#[test]
fn nested_pins_use_distinct_slots_until_exhausted() {
let pins: Vec<Pin> = (0..SLOTS)
.map(|i| pin(core::ptr::without_provenance(0x2000 + i * 8)).expect("slot"))
.collect();
assert!(
pin(core::ptr::without_provenance(0x2100)).is_none(),
"all slots taken"
);
drop(pins);
assert!(pin(core::ptr::without_provenance(0x2100)).is_some());
}
#[test]
fn records_are_reused_after_thread_exit() {
let count = || {
let mut n = 0;
for_each_record(|_| n += 1);
n
};
for _ in 0..4 {
std::thread::spawn(|| drop(pin(core::ptr::without_provenance(0x10))))
.join()
.unwrap();
}
let before = count();
for _ in 0..32 {
std::thread::spawn(|| drop(pin(core::ptr::without_provenance(0x10))))
.join()
.unwrap();
}
assert!(
count() < before + 32,
"sequential threads must reuse records"
);
}
}