use core::{
cell::UnsafeCell,
ops::{Deref, DerefMut, Drop},
};
use std::sync::{Arc, Mutex, Weak};
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 GuardInner = Mutex<Option<Box<dyn FnOnce() + Send + Sync>>>;
pub(crate) struct Guard {
_value: Arc<GuardInner>,
}
pub(crate) struct DropAll(Weak<GuardInner>);
impl Drop for DropAll {
fn drop(&mut self) {
if let Some(guard) = self.0.upgrade() {
if let Some(f) = guard.lock().unwrap().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: Arc::new(Mutex::new(Some(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(Arc::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, 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);
}
}