#![cfg(all(feature = "std", feature = "garbage"))]
use once_cell::sync::OnceCell;
use tap::Pipe;
use std::{
collections::VecDeque,
marker::PhantomData,
mem::{
self,
ManuallyDrop,
},
ops::{
Deref,
DerefMut,
},
sync::{
mpsc,
Mutex,
MutexGuard,
Once,
RwLock,
},
thread,
};
use typemap::TypeMap;
#[repr(transparent)]
pub struct BgDrop<T: 'static> {
inner: ManuallyDrop<T>,
}
impl<T: 'static> BgDrop<T> {
#[inline(always)]
pub fn new(value: T) -> Self {
Self {
inner: ManuallyDrop::new(value),
}
}
#[inline(always)]
pub fn into_inner(mut self) -> T {
unsafe { ManuallyDrop::take(&mut self.inner) }
}
#[inline(always)]
#[doc(hidden)]
pub fn bg_drop(self) -> Self {
self
}
#[inline(always)]
fn dtor(&mut self) {
if !mem::needs_drop::<T>() {
return;
}
init();
let val = unsafe { ManuallyDrop::take(&mut self.inner) };
let sender = match sender() {
Some(s) => s,
None => return,
};
dq().entry::<Key<T>>()
.or_insert_with(VecDeque::new)
.pipe(|v| v.push_back(val));
if sender.send(dtor::<T>).is_err() {
dtor::<T>();
}
}
}
impl<T: 'static> AsRef<T> for BgDrop<T> {
fn as_ref(&self) -> &T {
&*self.inner
}
}
impl<T: 'static> AsMut<T> for BgDrop<T> {
fn as_mut(&mut self) -> &mut T {
&mut *self.inner
}
}
impl<T: 'static> Deref for BgDrop<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&*self.inner
}
}
impl<T: 'static> DerefMut for BgDrop<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut *self.inner
}
}
impl<T: 'static> Drop for BgDrop<T> {
fn drop(&mut self) {
self.dtor();
}
}
pub trait BgDropExt: Sized + 'static {
fn bg_drop(self) -> BgDrop<Self> {
BgDrop::new(self)
}
}
impl<T: Sized + 'static> BgDropExt for T {
}
pub fn shutdown() {
static STOP: Once = Once::new();
STOP.call_once(|| {
let _: Option<AssertThreadsafe<mpsc::Sender<Dtor>>> = SEND
.get()
.and_then(|rw| rw.write().ok())
.and_then(|mut sender| sender.take());
let _: Option<()> = JOIN
.get()
.and_then(|mx| mx.lock().ok())
.and_then(|mut mg| mg.take())
.and_then(|jh| jh.join().ok());
});
}
type Dtor = fn() -> ();
static SEND: OnceCell<RwLock<Option<AssertThreadsafe<mpsc::Sender<Dtor>>>>> =
OnceCell::new();
static DUMP: OnceCell<Mutex<AssertThreadsafe<TypeMap>>> = OnceCell::new();
static JOIN: OnceCell<Mutex<Option<thread::JoinHandle<()>>>> = OnceCell::new();
#[inline(never)]
fn init() {
let (send, recv) = mpsc::channel::<Dtor>();
SEND.get_or_init(|| {
send.pipe(AssertThreadsafe::new)
.pipe(Some)
.pipe(RwLock::new)
});
DUMP.get_or_init(|| {
TypeMap::new().pipe(AssertThreadsafe::new).pipe(Mutex::new)
});
JOIN.get_or_init(|| {
thread::spawn(move || {
while let Ok(ev) = recv.recv() {
(ev)()
}
let _ = mem::replace(&mut **dq(), TypeMap::new());
})
.pipe(Some)
.pipe(Mutex::new)
});
}
fn dq() -> MutexGuard<'static, AssertThreadsafe<TypeMap>> {
unsafe { DUMP.get_unchecked() }
.lock()
.expect("Collection buffer should never observe a panic")
}
fn dtor<T: 'static>() {
let _tmp = dq()
.get_mut::<Key<T>>()
.and_then(VecDeque::pop_front);
}
fn sender() -> Option<mpsc::Sender<Dtor>> {
unsafe { SEND.get_unchecked() }
.read()
.ok()?
.as_ref()?
.inner
.clone()
.pipe(Some)
}
struct Key<T: 'static>(PhantomData<T>);
impl<T: 'static> typemap::Key for Key<T> {
type Value = VecDeque<T>;
}
#[repr(transparent)]
struct AssertThreadsafe<T> {
inner: T,
}
impl<T> AssertThreadsafe<T> {
fn new(inner: T) -> Self {
Self { inner }
}
}
unsafe impl<T> Send for AssertThreadsafe<T> {
}
unsafe impl<T> Sync for AssertThreadsafe<T> {
}
impl<T> Deref for AssertThreadsafe<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl<T> DerefMut for AssertThreadsafe<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::{
sync::atomic::{
AtomicUsize,
Ordering::Relaxed,
},
thread,
time::Duration,
};
#[test]
fn trash_pickup() {
static COUNTER: AtomicUsize = AtomicUsize::new(0);
struct Deferrer<F: FnMut()>(F);
impl<F: FnMut()> Drop for Deferrer<F> {
fn drop(&mut self) {
(self.0)()
}
}
let kept = Deferrer(|| {
COUNTER.fetch_add(1, Relaxed);
});
let sent = Deferrer(|| {
COUNTER.fetch_add(1, Relaxed);
})
.bg_drop();
drop(kept);
drop(sent);
while COUNTER.load(Relaxed) < 2 {
thread::sleep(Duration::from_millis(100));
}
}
}