use core::alloc::Layout;
use core::marker::PhantomData;
use core::ops::Deref;
use core::ptr::NonNull;
use core::sync::atomic::{AtomicUsize, Ordering, fence};
use super::Owner;
pub(crate) struct BlockArc<T> {
ptr: NonNull<Block<T>>,
owns: PhantomData<Block<T>>,
}
struct Block<T> {
count: AtomicUsize,
owner: Owner,
value: T,
}
unsafe impl<T: Send + Sync> Send for BlockArc<T> {}
unsafe impl<T: Send + Sync> Sync for BlockArc<T> {}
impl<T> Unpin for BlockArc<T> {}
impl<T> BlockArc<T> {
pub(crate) fn new(value: T) -> Self {
let (ptr, owner) = super::alloc(Layout::new::<Block<T>>());
let ptr = ptr.cast::<Block<T>>();
unsafe {
ptr.as_ptr().write(Block {
count: AtomicUsize::new(1),
owner,
value,
});
}
Self {
ptr,
owns: PhantomData,
}
}
fn block(&self) -> &Block<T> {
unsafe { self.ptr.as_ref() }
}
}
impl<T> Clone for BlockArc<T> {
fn clone(&self) -> Self {
if self.block().count.fetch_add(1, Ordering::Relaxed) > isize::MAX as usize {
std::process::abort();
}
Self {
ptr: self.ptr,
owns: PhantomData,
}
}
}
impl<T> Deref for BlockArc<T> {
type Target = T;
fn deref(&self) -> &T {
&self.block().value
}
}
impl<T> Drop for BlockArc<T> {
fn drop(&mut self) {
if self.block().count.fetch_sub(1, Ordering::Release) != 1 {
return;
}
fence(Ordering::Acquire);
let owner = self.block().owner;
unsafe {
core::ptr::drop_in_place(self.ptr.as_ptr());
super::free(self.ptr.cast(), Layout::new::<Block<T>>(), owner);
}
}
}
#[cfg(test)]
mod tests {
use std::sync::atomic::AtomicUsize as Counter;
use super::*;
struct Counted<'a>(&'a Counter);
impl Drop for Counted<'_> {
fn drop(&mut self) {
self.0.fetch_add(1, Ordering::SeqCst);
}
}
#[test]
fn the_value_is_dropped_once_with_its_last_handle() {
let drops = Counter::new(0);
let a = BlockArc::new(Counted(&drops));
let b = a.clone();
drop(a);
assert_eq!(drops.load(Ordering::SeqCst), 0);
drop(b);
assert_eq!(drops.load(Ordering::SeqCst), 1);
}
#[test]
fn handles_on_two_threads_share_one_value() {
let a = BlockArc::new(Counter::new(0));
let b = a.clone();
std::thread::spawn(move || {
b.fetch_add(1, Ordering::SeqCst);
})
.join()
.unwrap();
assert_eq!(a.load(Ordering::SeqCst), 1);
}
#[test]
fn a_block_dropped_last_elsewhere_comes_back_to_its_thread() {
std::thread::spawn(|| {
let a = BlockArc::new([0u64; 16]);
let addr = core::ptr::from_ref(&*a) as usize;
std::thread::spawn(move || drop(a)).join().unwrap();
let b = BlockArc::new([1u64; 16]);
assert_eq!(core::ptr::from_ref(&*b) as usize, addr);
})
.join()
.unwrap();
}
}