mmtk/util/rust_util/
atomic_box.rs

1use std::sync::atomic::{AtomicPtr, Ordering};
2
3use bytemuck::Zeroable;
4
5/// A lazily initialized box.  Similar to an `Option<Box<T>>`, but can be initialized atomically.
6///
7/// It is designed for implementing shared data.  Therefore, methods with `&self`, namely
8/// [`OnceOptionBox::get`] and the [`OnceOptionBox::get_or_init`] methods, only return shared
9/// references to the content (`&T`).  The user should use types that support multi-threaded
10/// accesses, such as mutexes or atomic types, if the inner type is supposed to be modified
11/// concurrently.
12///
13/// Once initialized, this object will own its content.  The content is allocated in the heap, and
14/// will be dropped and deallocated when this instance is dropped.
15///
16/// # Comparison to existing data structures
17///
18/// [`std::sync::OnceLock`] also provides thread-safe lazily-initialized cells.  But as its name
19/// suggests, it uses locks for synchronization, whereas `OnceOptionBox` is lock-free.  `OnceLock`
20/// also has a field of [`std::sync::Once`] which increases the space overhead.  `OnceOptionBox`
21/// only has one atomic pointer field and is more suitable for large arrays of lazily initialized
22/// elements.
23pub struct OnceOptionBox<T> {
24    inner: AtomicPtr<T>,
25}
26
27impl<T> OnceOptionBox<T> {
28    /// Create an empty `OnceOptionBox` instance.
29    pub fn new() -> OnceOptionBox<T> {
30        Self {
31            inner: AtomicPtr::new(std::ptr::null_mut()),
32        }
33    }
34
35    /// Get a reference to the content of this box, or `None` if not yet initialized.
36    pub fn get(&self, order: Ordering) -> Option<&T> {
37        let ptr = self.inner.load(order);
38        unsafe { ptr.as_ref() }
39    }
40
41    /// Get a reference to the content of this box.  If not initialized, it will call `init` to
42    /// initialize this box.
43    ///
44    /// When multiple threads attempt to initialize this box concurrently, all threads may call
45    /// their supplied `init` closure, but only one thread will successfully initialize this box to
46    /// the return value of `init`.  Other threads will drop their return values of `init`.  All
47    /// callers will return the reference to the value created by the successful thread.
48    pub fn get_or_init(
49        &self,
50        order_load: Ordering,
51        order_store: Ordering,
52        init: impl FnOnce() -> T,
53    ) -> &T {
54        if let Some(get_result) = self.get(order_load) {
55            return get_result;
56        }
57
58        let new_inner = Box::into_raw(Box::new(init()));
59        let cas_result = self.inner.compare_exchange(
60            std::ptr::null_mut(),
61            new_inner,
62            order_store,
63            Ordering::Relaxed,
64        );
65        match cas_result {
66            Ok(old_inner) => {
67                debug_assert_eq!(old_inner, std::ptr::null_mut());
68                unsafe { new_inner.as_ref().unwrap() }
69            }
70            Err(old_inner) => {
71                drop(unsafe { Box::from_raw(new_inner) });
72                unsafe { old_inner.as_ref().unwrap() }
73            }
74        }
75    }
76}
77
78impl<T> Drop for OnceOptionBox<T> {
79    fn drop(&mut self) {
80        let ptr = *self.inner.get_mut();
81        if !ptr.is_null() {
82            drop(unsafe { Box::from_raw(ptr) });
83        }
84    }
85}
86
87unsafe impl<T> Zeroable for OnceOptionBox<T> {}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92
93    #[test]
94    fn construct() {
95        let oob = OnceOptionBox::<usize>::new();
96        let elem = oob.get(Ordering::Relaxed);
97        assert_eq!(elem, None);
98    }
99
100    #[test]
101    fn init() {
102        let oob = OnceOptionBox::<usize>::new();
103        let elem = oob.get_or_init(Ordering::Relaxed, Ordering::Relaxed, || 42);
104        assert_eq!(*elem, 42);
105    }
106
107    #[test]
108    fn reinit() {
109        let oob = OnceOptionBox::<usize>::new();
110        let elem = oob.get_or_init(Ordering::Relaxed, Ordering::Relaxed, || 42);
111        assert_eq!(*elem, 42);
112        let elem2 = oob.get_or_init(Ordering::Relaxed, Ordering::Relaxed, || 43);
113        assert_eq!(*elem2, 42);
114    }
115}