Skip to main content

ferroui_base/utilities/
thread_bound.rs

1use std::mem::ManuallyDrop;
2use std::thread::{self, ThreadId};
3
4/// A value that stays on the thread that created it, inside an object that
5/// is shared between threads.
6///
7/// A render resource is shared between the UI thread and the render thread,
8/// yet some of them keep a part that only one of the threads uses (the
9/// render target a render target bitmap draws itself with, for example).
10/// The part is reached through [`get`](Self::get), which panics on any other
11/// thread, so the object as a whole can be `Send + Sync`.
12///
13/// When the object is dropped on another thread the value is not dropped but
14/// leaked: its destructor may only run on its own thread. Owners release
15/// what the value holds from their `dispose`, on the right thread.
16pub struct ThreadBound<T> {
17    value: ManuallyDrop<T>,
18    thread: ThreadId,
19}
20
21// SAFETY: the value is only reached, and only dropped, on the thread that
22// created it; every other thread sees an opaque box.
23unsafe impl<T> Send for ThreadBound<T> {}
24unsafe impl<T> Sync for ThreadBound<T> {}
25
26impl<T> ThreadBound<T> {
27    /// Binds `value` to the current thread.
28    pub fn new(value: T) -> Self {
29        Self { value: ManuallyDrop::new(value), thread: thread::current().id() }
30    }
31
32    /// Whether the current thread is the one the value is bound to.
33    pub fn is_on_thread(&self) -> bool {
34        thread::current().id() == self.thread
35    }
36
37    /// The value.
38    ///
39    /// # Panics
40    ///
41    /// On a thread other than the one the value was created on.
42    pub fn get(&self) -> &T {
43        assert!(self.is_on_thread(), "the value is bound to the thread that created it");
44        &self.value
45    }
46
47    /// Takes the value out.
48    ///
49    /// # Panics
50    ///
51    /// On a thread other than the one the value was created on.
52    pub fn into_inner(self) -> T {
53        assert!(self.is_on_thread(), "the value is bound to the thread that created it");
54        let mut this = ManuallyDrop::new(self);
55        // SAFETY: `this` is not dropped, so the value is taken exactly once.
56        unsafe { ManuallyDrop::take(&mut this.value) }
57    }
58}
59
60impl<T> Drop for ThreadBound<T> {
61    fn drop(&mut self) {
62        if self.is_on_thread() {
63            // SAFETY: the value is not used after this, and this is its thread.
64            unsafe { ManuallyDrop::drop(&mut self.value) };
65        }
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    // Not from upstream.
72    use super::*;
73    use std::rc::Rc;
74    use std::sync::Arc;
75
76    #[test]
77    fn the_value_is_reached_and_dropped_on_its_thread() {
78        let value = Rc::new(5);
79        let bound = ThreadBound::new(value.clone());
80
81        assert_eq!(5, **bound.get());
82        drop(bound);
83        assert_eq!(1, Rc::strong_count(&value));
84    }
85
86    #[test]
87    fn another_thread_cannot_reach_the_value_and_leaks_it() {
88        let value = Rc::new(5);
89        let bound = Arc::new(ThreadBound::new(value.clone()));
90
91        let moved = bound.clone();
92        let reached = thread::spawn(move || std::panic::catch_unwind(|| moved.is_on_thread()).unwrap_or(true))
93            .join()
94            .unwrap();
95        assert!(!reached);
96
97        let moved = bound;
98        thread::spawn(move || drop(moved)).join().unwrap();
99        // Not dropped: the count of the handle is untouched by the other thread.
100        assert_eq!(2, Rc::strong_count(&value));
101    }
102}