1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
#![doc = include_str!("../readme.md")]
use std::{fmt, mem, ptr};
#[cfg(not(loom))]
use std::sync::{
atomic::{AtomicPtr, Ordering},
Arc,
};
#[cfg(loom)]
use loom::sync::{
atomic::{AtomicPtr, Ordering},
Arc,
};
/// An atomic pointer to an [`Arc`].
///
/// This pointer provides a safe atomic pointer to an [`Arc`]. Each load will
/// clone the [`Arc`] ensuring that concurrent reads/writes will only drop the
/// value when all references are decremented. The inner [`Arc`] can be swapped
/// atomically with another value.
///
/// This value is not itself cloneable and can itself be wrapped in an [`Arc`].
pub struct AtomicArc<T> {
ptr: AtomicPtr<T>,
}
impl<T> AtomicArc<T> {
/// Creates a new atomic pointer to an [`Arc`].
pub fn new(arc: Arc<T>) -> Self {
let raw = Arc::into_raw(arc) as *mut _;
let ptr = AtomicPtr::new(ptr::null_mut());
ptr.store(raw, Ordering::SeqCst);
Self { ptr }
}
/// Load the current value cloning the inner [`Arc`].
///
/// This will increment a reference count of the current [`Arc`] and return
/// this to the caller.
pub fn load(&self) -> Arc<T> {
// Ordering: Loads must be ordered globally after all store operations
// to allow the ref-cnt of the underlying arc to track references.
let raw = self.ptr.load(Ordering::SeqCst);
// Safety: original arc is always created with 'into_raw'.
unsafe {
// We want an arc but we don't want to actually decrement the ref
// count being held by the pointer.
let arc = mem::ManuallyDrop::new(Arc::from_raw(raw));
// Now clone the original and provide it to the caller.
mem::ManuallyDrop::into_inner(arc.clone())
}
}
fn swap_ptr(&self, raw: *mut T) -> Arc<T> {
// Ordering: Orders all writes (globally) before the final drop of this value.
let prev = self.ptr.swap(raw, Ordering::SeqCst);
// Safety: Original arc is always created with 'into_raw'.
unsafe {
// Consumes the original reference count.
Arc::from_raw(prev)
}
}
/// Replace the current value, dropping the previously stored [`Arc`].
pub fn store(&self, arc: Arc<T>) {
let _ = self.swap(arc);
}
/// Swap the current value, returning the previously stored [`Arc`].
///
/// The returned [`Arc`] may have additional references still held by other
/// load calls previously requested.
pub fn swap(&self, arc: Arc<T>) -> Arc<T> {
let raw = Arc::into_raw(arc) as *mut _;
self.swap_ptr(raw)
}
}
impl<T> Drop for AtomicArc<T> {
fn drop(&mut self) {
let _ = self.swap_ptr(ptr::null_mut());
}
}
impl<T: fmt::Debug> fmt::Debug for AtomicArc<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("AtomicArc").field(&self.load()).finish()
}
}
#[cfg(all(test, not(loom)))]
mod tests {
use std::thread;
use super::*;
struct PrintOnDrop(i32);
impl Drop for PrintOnDrop {
fn drop(&mut self) {
println!("drop: {}", self.0);
}
}
#[test]
fn basics() {
let arc = AtomicArc::new(Arc::new(PrintOnDrop(1)));
let _orig = arc.load();
arc.swap(Arc::new(PrintOnDrop(2)));
}
#[test]
fn concurrent() {
let arc = Arc::new(AtomicArc::new(Arc::new(PrintOnDrop(1))));
let t1 = thread::spawn({
let handle = arc.clone();
move || {
for _ in 0..10 {
println!("{}", handle.load().0);
}
}
});
let t2 = thread::spawn({
let handle = arc.clone();
move || {
for _ in 0..10 {
println!("{}", handle.load().0);
}
}
});
arc.swap(Arc::new(PrintOnDrop(2)));
arc.swap(Arc::new(PrintOnDrop(3)));
t1.join().unwrap();
t2.join().unwrap();
}
}
#[cfg(all(test, loom))]
mod loom_tests {
use loom::thread;
use super::*;
#[test]
fn single_thread() {
loom::model(|| {
let arc = Arc::new(AtomicArc::new(Arc::new(0)));
let handle = arc.clone();
thread::spawn(move || {
let v = *handle.load();
assert!(v == 0 || v == 1);
});
arc.swap(Arc::new(1));
});
}
#[test]
fn two_threads() {
loom::model(|| {
let arc = Arc::new(AtomicArc::new(Arc::new(0)));
let handle1 = arc.clone();
let handle2 = arc.clone();
thread::spawn(move || {
let v = *handle1.load();
assert!(v == 0 || v == 1);
});
thread::spawn(move || {
let v = *handle2.load();
assert!(v == 0 || v == 1);
});
arc.swap(Arc::new(1));
});
}
#[test]
fn init_and_swap() {
loom::model(|| {
let arc = Arc::new(AtomicArc::new(Arc::new(0)));
arc.swap(Arc::new(1));
let handle = arc.clone();
thread::spawn(move || {
let v = *handle.load();
assert!(v == 1 || v == 2);
});
arc.swap(Arc::new(2));
});
}
}