#![cfg_attr(not(feature = "std"), no_std)]
#![deny(missing_docs)]
#[cfg(not(feature = "std"))]
extern crate alloc;
#[cfg(not(feature = "std"))]
use alloc::boxed::Box;
use core::marker::PhantomData;
use core::ptr;
use core::sync::atomic::{AtomicPtr, Ordering};
pub struct AtomicSlot<T> {
inner: AtomicPtr<T>,
_phantom: PhantomData<Option<Box<T>>>,
}
impl<T> Default for AtomicSlot<T> {
fn default() -> Self {
Self::empty()
}
}
impl<T> AtomicSlot<T> {
pub fn new(value: Box<T>) -> Self {
Self {
inner: AtomicPtr::new(Box::into_raw(value)),
_phantom: PhantomData,
}
}
pub fn empty() -> Self {
Self {
inner: AtomicPtr::new(ptr::null_mut()),
_phantom: PhantomData,
}
}
pub fn swap(&self, value: Option<Box<T>>) -> Option<Box<T>> {
self.swap_ordered(value, Ordering::AcqRel)
}
pub fn take(&self) -> Option<Box<T>> {
self.take_ordered(Ordering::AcqRel)
}
pub fn store(&self, value: Option<Box<T>>) {
self.store_ordered(value, Ordering::AcqRel)
}
pub fn swap_ordered(&self, value: Option<Box<T>>, order: Ordering) -> Option<Box<T>> {
let raw = value.map(Box::into_raw).unwrap_or(ptr::null_mut());
let prev = self.inner.swap(raw, order);
if prev.is_null() {
None
} else {
Some(unsafe { Box::from_raw(prev) })
}
}
pub fn take_ordered(&self, order: Ordering) -> Option<Box<T>> {
self.swap_ordered(None, order)
}
pub fn store_ordered(&self, value: Option<Box<T>>, order: Ordering) {
let _ = self.swap_ordered(value, order);
}
pub fn is_some(&self) -> bool {
!self.is_none()
}
pub fn is_none(&self) -> bool {
self.is_none_ordered(Ordering::Acquire)
}
pub fn is_none_ordered(&self, order: Ordering) -> bool {
self.inner.load(order).is_null()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
use std::thread;
#[test]
fn sequential_swap_and_take() {
let slot = AtomicSlot::new(Box::new(10));
let old = slot.swap(Some(Box::new(20)));
assert_eq!(*old.unwrap(), 10);
let taken = slot.take();
assert_eq!(*taken.unwrap(), 20);
assert!(slot.is_none());
}
#[test]
fn sequential_empty_store() {
let slot = AtomicSlot::<i32>::empty();
assert!(slot.is_none());
slot.store(Some(Box::new(5)));
assert!(slot.is_some());
assert_eq!(*slot.take().unwrap(), 5);
}
#[test]
fn atomic_slot_is_send() {
let slot = AtomicSlot::new(Box::new(42));
let handle = thread::spawn(move || {
assert_eq!(*slot.take().unwrap(), 42);
});
handle.join().unwrap();
}
#[test]
fn atomic_slot_is_sync() {
let slot = Arc::new(AtomicSlot::new(Box::new(100)));
let slot_clone = slot.clone();
let handle = thread::spawn(move || {
assert_eq!(*slot_clone.take().unwrap(), 100);
});
handle.join().unwrap();
}
#[test]
fn racing_threads_take_once() {
let slot = Arc::new(AtomicSlot::new(Box::new(7)));
let mut handles = Vec::new();
for _ in 0..4 {
let slot = slot.clone();
handles.push(thread::spawn(move || {
slot.take().map(|b| *b)
}));
}
let results: Vec<_> = handles.into_iter()
.map(|h| h.join().unwrap())
.collect();
assert_eq!(results.iter().filter(|r| r.is_some()).count(), 1);
assert_eq!(results.iter().filter(|r| r.is_none()).count(), 3);
}
}