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
// While the crate is no_std
// this module is not; it depends on std::thread
extern crate std;
use std::boxed::Box;

use crate::{Backdrop, BackdropStrategy};

/// Strategy which drops the contained value in a newly spawned background thread.
///
/// A new thread is spawned (using [`std::thread::spawn`]) for every dropped value.
/// This is conceptually very simple, but relatively slow since spawning a thread has overhead.
pub struct ThreadStrategy();

impl<T: Send + 'static> BackdropStrategy<T> for ThreadStrategy {
    #[inline]
    fn execute(droppable: T) {
        std::thread::spawn(|| {
            core::mem::drop(droppable);
        });
    }
}

/// Convenient alias for a [`Backdrop`] that uses the [`ThreadStrategy`]
pub type ThreadBackdrop<T> = Backdrop<T, ThreadStrategy>;

/// Handle that can be used to send trash to the 'trash thread' that runs in the background for the [`TrashThreadStrategy`].
///
/// Only the global singleton [`static@GLOBAL_TRASH_THREAD_HANDLE`] instance of this struct is used.
pub struct GlobalTrashThreadHandle(std::sync::mpsc::SyncSender<Box<dyn Send>>);
use lazy_static::lazy_static;
lazy_static! {
    /// The global handle used by the [`TrashThreadStrategy`].
    ///
    /// This trash thread is a global thread that is started using [`mod@lazy_static`].
    ///
    /// If you use this strategy, you probably want to control when the thread
    /// is started using [`lazy_static::initialize(&GLOBAL_TRASH_THREAD_HANDLE)`](lazy_static::initialize)
    /// (If you do not, it is started when it is used for the first time,
    /// meaning the very first drop will be slower.)
    pub static ref GLOBAL_TRASH_THREAD_HANDLE: GlobalTrashThreadHandle = {
        let (send, recv) = std::sync::mpsc::sync_channel(10);
        std::thread::spawn(move || {
            for droppable in recv {
                core::mem::drop(droppable)
            }
        });
        GlobalTrashThreadHandle(send)
    };
}

/// Strategy which sends any to-be-dropped values to a dedicated global 'trash thread'
///
/// This trash thread is a global thread that is started using [`mod@lazy_static`].
/// You probably want to control when it is started using [`lazy_static::initialize(&GLOBAL_TRASH_THREAD_HANDLE)`]
/// (If you do not, it is started when it is used for the first time,
/// meaning the very first drop will be slower.)
///
/// Sending is done using a [`std::sync::mpsc::sync_channel`].
/// In the current implementation, there are 10 slots available in the channel
/// before a caller thread would block.
///
/// Also note that this global, detached, thread, will not be joined/cleaned up when your program exits.
/// This is probably fine, though it might mean that a few final destructors are not run.
/// But be aware that tools like e.g. Miri complain about this.
pub struct GlobalTrashThreadStrategy();

impl<T: Send + 'static> BackdropStrategy<T> for GlobalTrashThreadStrategy {
    #[inline]
    fn execute(droppable: T) {
        let handle = &GLOBAL_TRASH_THREAD_HANDLE;
        let _ = handle.0.send(Box::new(droppable));
    }
}

/// Convenient alias for a [`Backdrop`] that uses the [`GlobalTrashThreadStrategy`]
pub type GlobalTrashThreadBackdrop<T> = Backdrop<T, GlobalTrashThreadStrategy>;


lazy_static! {
    static ref TRASH_THREAD_HANDLE: std::sync::RwLock<Option<std::sync::mpsc::SyncSender<Box<dyn Send>>>> = {
        std::sync::RwLock::new(None)
    };
}

/// Strategy which sends any to-be-dropped values to a dedicated 'trash thread'
///
/// This thread _must_ have been started first using [`TrashThreadStrategy::with_trash_thread`].
///
/// This strategy is similar to the [`GlobalTrashThreadStrategy`], but it makes sure that
/// the trash thread is cleaned up properly  (and any yet-to-be-dropped objects contained on it dropped) on exit.
///
/// # Panics
/// If Backdrop objects using this strategy are dropped without the thread having been started,
/// i.e. outside of the context of [`TrashThreadStrategy::with_trash_thread`],
/// a panic will happen.
pub struct TrashThreadStrategy();

impl TrashThreadStrategy {
    /// Starts a new Trash Thread,
    /// calls the given function or closure while it is alive,
    /// and afterwards cleans up the Trash Thread again.
    ///
    /// You probably want to e.g. surround the contents of your `main` or other 'big work' function
    /// with this.
    ///
    /// This function is reentrant: Nesting it will start a second (third, etc.) trash thread
    /// which will be used for the duration of the nested call.
    pub fn with_trash_thread<R>(fun: impl FnOnce() -> R) -> R {
        let (send, recv) = std::sync::mpsc::sync_channel(10);
        let thread_handle = std::thread::spawn(move || {
            for droppable in recv {
                core::mem::drop(droppable)
            }
        });
        let orig_handle = {
            let mut handle = TRASH_THREAD_HANDLE.write().unwrap();
            std::mem::replace(&mut *handle, Some(send))
        };

        let result = fun();

        {
            let mut handle = TRASH_THREAD_HANDLE.write().unwrap();
            // At this point the last 'send' handle is overwritten/dropped,
            // which should make the trash thread exit.
            *handle = orig_handle;
        };
        thread_handle.join().expect("Could not join on trash thread");
        result
    }
}

impl<T: Send + 'static> BackdropStrategy<T> for TrashThreadStrategy {
    #[inline]
    fn execute(droppable: T) {
        let handle = &TRASH_THREAD_HANDLE;
        let _ = handle.read().unwrap().as_ref().expect("No trash thread was started").send(Box::new(droppable));
    }
}

/// Convenient alias for a [`Backdrop`] that uses the [`TrashThreadStrategy`]
pub type TrashThreadBackdrop<T> = Backdrop<T, TrashThreadStrategy>;