use core::{
cell::UnsafeCell,
ops::{Deref, DerefMut, Drop},
};
use std::{fmt::Debug, sync::Arc};
mod guard_rc {
#[cfg(not(all(shuttle, feature = "_shuttle")))]
mod imp {
use std::sync::{Arc, Mutex, Weak};
pub(crate) struct GuardArc<T>(Arc<Mutex<Option<T>>>);
pub(crate) struct GuardWeak<T>(Weak<Mutex<Option<T>>>);
impl<T> GuardArc<T> {
#[inline]
pub(crate) fn new(value: T) -> Self {
Self(Arc::new(Mutex::new(Some(value))))
}
#[inline]
pub(crate) fn downgrade(this: &Self) -> GuardWeak<T> {
GuardWeak(Arc::downgrade(&this.0))
}
#[inline]
pub(crate) fn is_present(&self) -> bool {
self.0.lock().unwrap().is_some()
}
#[inline]
pub(crate) fn take(&self) -> Option<T> {
self.0.lock().unwrap().take()
}
}
impl<T> Clone for GuardArc<T> {
#[inline]
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<T> GuardWeak<T> {
#[inline]
pub(crate) fn upgrade(&self) -> Option<GuardArc<T>> {
self.0.upgrade().map(GuardArc)
}
}
}
#[cfg(all(shuttle, feature = "_shuttle"))]
mod imp {
pub(crate) use metrique_writer_core::shuttle_test_support::{GuardArc, GuardWeak};
}
pub(crate) use imp::{GuardArc, GuardWeak};
}
use guard_rc::{GuardArc, GuardWeak};
#[derive(Debug)]
pub(crate) struct Parent<T> {
value: Arc<UnsafeCell<T>>,
guard: Guard,
}
unsafe impl<T> Send for Parent<T> where T: Send {}
unsafe impl<T> Sync for Parent<T> where T: Sync {}
type GuardPayload = Box<dyn FnOnce() + Send + Sync>;
pub(crate) struct Guard {
_value: GuardArc<GuardPayload>,
}
impl Debug for Guard {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let is_open = self._value.is_present();
f.debug_struct("Guard").field("open", &is_open).finish()
}
}
pub(crate) struct DropAll(GuardWeak<GuardPayload>);
impl Drop for DropAll {
fn drop(&mut self) {
if let Some(guard) = self.0.upgrade() {
if let Some(f) = guard.take() {
(f)()
}
}
}
}
impl<T: Send + Sync + 'static> Parent<T> {
pub(crate) fn new(value: T) -> Self {
let value: Arc<UnsafeCell<T>> = Arc::new(value.into());
struct AssertSendSync<T>(T);
unsafe impl<T> Send for AssertSendSync<T> {}
unsafe impl<T> Sync for AssertSendSync<T> {}
let guard_value = AssertSendSync(value.clone());
let guard = Guard {
_value: GuardArc::new(Box::new(|| drop(guard_value))),
};
Self { value, guard }
}
pub(crate) fn new_guard(&self) -> Guard {
Guard {
_value: self.guard._value.clone(),
}
}
pub(crate) fn force_drop_guard(&self) -> DropAll {
DropAll(GuardArc::downgrade(&self.guard._value))
}
}
impl<T> Deref for Parent<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
unsafe { &*UnsafeCell::get(self.value.as_ref()) }
}
}
impl<T> DerefMut for Parent<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { &mut *UnsafeCell::get(self.value.as_ref()) }
}
}
#[cfg(test)]
mod test {
use core::{
assert_eq,
ops::Drop,
sync::atomic::{AtomicBool, AtomicUsize, Ordering},
};
use std::sync::Arc;
use super::Parent;
struct IsDropped {
inner: Arc<AtomicBool>,
}
impl IsDropped {
fn new() -> (Self, Arc<AtomicBool>) {
let inner = Arc::new(AtomicBool::default());
(
IsDropped {
inner: inner.clone(),
},
inner,
)
}
}
impl Drop for IsDropped {
fn drop(&mut self) {
self.inner.store(true, Ordering::SeqCst);
}
}
#[test]
fn immediate_drop_drops() {
let (tester, is_dropped) = IsDropped::new();
let primary = Parent::new(tester);
drop(primary);
assert_eq!(is_dropped.load(Ordering::Relaxed), true);
}
#[test]
fn children_keep_parent_alive() {
let (tester, is_dropped) = IsDropped::new();
let primary = Parent::new(tester);
let guard_1 = primary.new_guard();
let guard_2 = primary.new_guard();
assert_eq!(is_dropped.load(Ordering::Relaxed), false);
drop(guard_1);
assert_eq!(is_dropped.load(Ordering::Relaxed), false);
drop(primary);
assert_eq!(is_dropped.load(Ordering::Relaxed), false);
drop(guard_2);
assert_eq!(is_dropped.load(Ordering::Relaxed), true);
}
#[test]
fn drop_all_doesnt_drop_primary() {
let (tester, is_dropped) = IsDropped::new();
let primary = Parent::new(tester);
let drop_all = primary.force_drop_guard();
drop(drop_all);
assert_eq!(is_dropped.load(Ordering::Relaxed), false);
drop(primary);
assert_eq!(is_dropped.load(Ordering::Relaxed), true);
}
#[test]
fn make_two_drop_alls() {
let (tester, is_dropped) = IsDropped::new();
let primary = Parent::new(tester);
let drop_all_1 = primary.force_drop_guard();
let drop_all_2 = primary.force_drop_guard();
assert_eq!(is_dropped.load(Ordering::Relaxed), false);
drop(drop_all_1);
drop(drop_all_2);
assert_eq!(is_dropped.load(Ordering::Relaxed), false);
drop(primary);
assert_eq!(is_dropped.load(Ordering::Relaxed), true);
}
#[test]
fn drop_all_doesnt_keep_parent_alive() {
let (tester, is_dropped) = IsDropped::new();
let primary = Parent::new(tester);
let drop_all = primary.force_drop_guard();
assert_eq!(is_dropped.load(Ordering::Relaxed), false);
drop(primary);
assert_eq!(is_dropped.load(Ordering::Relaxed), true);
drop(drop_all);
}
#[test]
fn all_guards_can_be_dropped() {
let (tester, is_dropped) = IsDropped::new();
let sut = Parent::new(tester);
let _guard_1 = sut.new_guard();
let _guard_2 = sut.new_guard();
assert_eq!(is_dropped.load(Ordering::Relaxed), false);
let force_drop_guard = sut.force_drop_guard();
assert_eq!(is_dropped.load(Ordering::Relaxed), false);
drop(sut);
assert_eq!(is_dropped.load(Ordering::Relaxed), false);
drop(_guard_1);
assert_eq!(is_dropped.load(Ordering::Relaxed), false);
drop(force_drop_guard);
assert_eq!(is_dropped.load(Ordering::Relaxed), true);
drop(_guard_2);
}
struct DropCounter {
count: Arc<AtomicUsize>,
}
impl DropCounter {
fn new() -> (Self, Arc<AtomicUsize>) {
let count = Arc::new(AtomicUsize::new(0));
(
DropCounter {
count: count.clone(),
},
count,
)
}
}
impl Drop for DropCounter {
fn drop(&mut self) {
self.count.fetch_add(1, Ordering::SeqCst);
}
}
#[test]
fn stress_concurrent_guard_and_drop_all_race_releases_value_exactly_once() {
use std::sync::Barrier;
const GUARDS: usize = 4;
for _ in 0..1000 {
let (tester, drop_count) = DropCounter::new();
let primary = Parent::new(tester);
let guards: Vec<_> = (0..GUARDS).map(|_| primary.new_guard()).collect();
let drop_all = primary.force_drop_guard();
let barrier = Arc::new(Barrier::new(GUARDS + 1 + 1));
let mut handles: Vec<_> = guards
.into_iter()
.map(|guard| {
let barrier = barrier.clone();
std::thread::spawn(move || {
barrier.wait();
drop(guard);
})
})
.collect();
handles.push({
let barrier = barrier.clone();
std::thread::spawn(move || {
barrier.wait();
drop(drop_all);
})
});
barrier.wait();
drop(primary);
for h in handles {
h.join().unwrap();
}
assert_eq!(
drop_count.load(Ordering::SeqCst),
1,
"value must be dropped exactly once, regardless of how the guard/drop_all/primary drops interleaved"
);
}
}
}
#[cfg(all(test, shuttle, feature = "_shuttle"))]
mod shuttle_tests {
use core::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use super::Parent;
use metrique_writer_core::shuttle_test;
struct DropCounter {
count: Arc<AtomicUsize>,
}
impl Drop for DropCounter {
fn drop(&mut self) {
self.count.fetch_add(1, Ordering::SeqCst);
}
}
shuttle_test! {
num_iters = 2_000, depth = 3;
fn concurrent_drop_alls_race_releases_value_exactly_once() {
let count = Arc::new(AtomicUsize::new(0));
let tester = DropCounter {
count: count.clone(),
};
let primary = Parent::new(tester);
let drop_all_1 = primary.force_drop_guard();
let drop_all_2 = primary.force_drop_guard();
let h1 = shuttle::thread::spawn(move || drop(drop_all_1));
let h2 = shuttle::thread::spawn(move || drop(drop_all_2));
drop(primary);
h1.join().unwrap();
h2.join().unwrap();
assert_eq!(
count.load(Ordering::SeqCst),
1,
"value must be dropped exactly once, regardless of how the two DropAlls and primary interleave"
);
}
}
shuttle_test! {
num_iters = 2_000, depth = 3;
fn concurrent_guards_race_releases_value_exactly_once() {
const GUARDS: usize = 2;
let count = Arc::new(AtomicUsize::new(0));
let tester = DropCounter {
count: count.clone(),
};
let primary = Parent::new(tester);
let guards: Vec<_> = (0..GUARDS).map(|_| primary.new_guard()).collect();
let handles: Vec<_> = guards
.into_iter()
.map(|guard| shuttle::thread::spawn(move || drop(guard)))
.collect();
drop(primary);
for h in handles {
h.join().unwrap();
}
assert_eq!(
count.load(Ordering::SeqCst),
1,
"value must be dropped exactly once, regardless of how the guards and primary interleave"
);
}
}
shuttle_test! {
num_iters = 2_000, depth = 3;
fn concurrent_guards_and_drop_all_race_releases_value_exactly_once() {
const GUARDS: usize = 2;
let count = Arc::new(AtomicUsize::new(0));
let tester = DropCounter {
count: count.clone(),
};
let primary = Parent::new(tester);
let guards: Vec<_> = (0..GUARDS).map(|_| primary.new_guard()).collect();
let drop_all = primary.force_drop_guard();
let mut handles: Vec<_> = guards
.into_iter()
.map(|guard| shuttle::thread::spawn(move || drop(guard)))
.collect();
handles.push(shuttle::thread::spawn(move || drop(drop_all)));
drop(primary);
for h in handles {
h.join().unwrap();
}
assert_eq!(
count.load(Ordering::SeqCst),
1,
"value must be dropped exactly once, regardless of how the guards, drop_all, and primary interleave"
);
}
}
fn drop_all_lingering_does_not_delay_release() {
let count = Arc::new(AtomicUsize::new(0));
let tester = DropCounter {
count: count.clone(),
};
let primary = Parent::new(tester);
let drop_all = primary.force_drop_guard();
drop(primary);
assert_eq!(
count.load(Ordering::SeqCst),
1,
"value must release once Parent (and all Guards) drop, even with a live DropAll"
);
drop(drop_all);
}
#[test]
fn drop_all_lingering_does_not_delay_release_check() {
shuttle::check(drop_all_lingering_does_not_delay_release);
}
shuttle_test! {
num_iters = 2_000, depth = 3;
fn concurrent_guard_creation_and_drop_releases_value_exactly_once() {
const GUARDS: usize = 2;
let count = Arc::new(AtomicUsize::new(0));
let tester = DropCounter {
count: count.clone(),
};
let primary = Parent::new(tester);
shuttle::thread::scope(|s| {
for _ in 0..GUARDS {
s.spawn(|| drop(primary.new_guard()));
}
});
drop(primary);
assert_eq!(
count.load(Ordering::SeqCst),
1,
"value must be dropped exactly once, regardless of how concurrent guard creation and drop interleave"
);
}
}
shuttle_test! {
num_iters = 2_000, depth = 3;
fn concurrent_guard_and_drop_all_creation_and_drop_releases_value_exactly_once() {
const GUARDS: usize = 2;
const DROP_ALLS: usize = 2;
let count = Arc::new(AtomicUsize::new(0));
let tester = DropCounter {
count: count.clone(),
};
let primary = Parent::new(tester);
shuttle::thread::scope(|s| {
for _ in 0..GUARDS {
s.spawn(|| drop(primary.new_guard()));
}
for _ in 0..DROP_ALLS {
s.spawn(|| drop(primary.force_drop_guard()));
}
});
drop(primary);
assert_eq!(
count.load(Ordering::SeqCst),
1,
"value must be dropped exactly once, regardless of how concurrent guard/DropAll creation and drop interleave"
);
}
}
}