pyo3/sync/
once_lock.rs

1use crate::{
2    internal::state::SuspendAttach, types::any::PyAnyMethods, Bound, Py, PyResult, PyTypeCheck,
3    Python,
4};
5
6/// An equivalent to [`std::sync::OnceLock`] for initializing objects while attached to
7/// the Python interpreter.
8///
9/// Unlike `OnceLock<T>`, this type will not deadlock with the interpreter.
10/// Before blocking calls the cell will detach from the runtime and then
11/// re-attach once the cell is unblocked.
12///
13/// # Re-entrant initialization
14///
15/// Like `OnceLock<T>`, it is an error to re-entrantly initialize a `PyOnceLock<T>`. The exact
16/// behavior in this case is not guaranteed, it may either deadlock or panic.
17///
18/// # Examples
19///
20/// The following example shows how to use `PyOnceLock` to share a reference to a Python list
21/// between threads:
22///
23/// ```
24/// use pyo3::sync::PyOnceLock;
25/// use pyo3::prelude::*;
26/// use pyo3::types::PyList;
27///
28/// static LIST_CELL: PyOnceLock<Py<PyList>> = PyOnceLock::new();
29///
30/// pub fn get_shared_list(py: Python<'_>) -> &Bound<'_, PyList> {
31///     LIST_CELL
32///         .get_or_init(py, || PyList::empty(py).unbind())
33///         .bind(py)
34/// }
35/// # Python::attach(|py| assert_eq!(get_shared_list(py).len(), 0));
36/// ```
37#[derive(Default)]
38pub struct PyOnceLock<T> {
39    inner: once_cell::sync::OnceCell<T>,
40}
41
42impl<T> PyOnceLock<T> {
43    /// Create a `PyOnceLock` which does not yet contain a value.
44    pub const fn new() -> Self {
45        Self {
46            inner: once_cell::sync::OnceCell::new(),
47        }
48    }
49
50    /// Get a reference to the contained value, or `None` if the cell has not yet been written.
51    #[inline]
52    pub fn get(&self, _py: Python<'_>) -> Option<&T> {
53        self.inner.get()
54    }
55
56    /// Get a reference to the contained value, initializing it if needed using the provided
57    /// closure.
58    ///
59    /// See the type-level documentation for detail on re-entrancy and concurrent initialization.
60    #[inline]
61    pub fn get_or_init<F>(&self, py: Python<'_>, f: F) -> &T
62    where
63        F: FnOnce() -> T,
64    {
65        self.inner
66            .get()
67            .unwrap_or_else(|| init_once_cell_py_attached(&self.inner, py, f))
68    }
69
70    /// Like `get_or_init`, but accepts a fallible initialization function. If it fails, the cell
71    /// is left uninitialized.
72    ///
73    /// See the type-level documentation for detail on re-entrancy and concurrent initialization.
74    pub fn get_or_try_init<F, E>(&self, py: Python<'_>, f: F) -> Result<&T, E>
75    where
76        F: FnOnce() -> Result<T, E>,
77    {
78        self.inner
79            .get()
80            .map_or_else(|| try_init_once_cell_py_attached(&self.inner, py, f), Ok)
81    }
82
83    /// Get the contents of the cell mutably. This is only possible if the reference to the cell is
84    /// unique.
85    pub fn get_mut(&mut self) -> Option<&mut T> {
86        self.inner.get_mut()
87    }
88
89    /// Set the value in the cell.
90    ///
91    /// If the cell has already been written, `Err(value)` will be returned containing the new
92    /// value which was not written.
93    pub fn set(&self, _py: Python<'_>, value: T) -> Result<(), T> {
94        self.inner.set(value)
95    }
96
97    /// Takes the value out of the cell, moving it back to an uninitialized state.
98    ///
99    /// Has no effect and returns None if the cell has not yet been written.
100    pub fn take(&mut self) -> Option<T> {
101        self.inner.take()
102    }
103
104    /// Consumes the cell, returning the wrapped value.
105    ///
106    /// Returns None if the cell has not yet been written.
107    pub fn into_inner(self) -> Option<T> {
108        self.inner.into_inner()
109    }
110}
111
112impl<T> PyOnceLock<Py<T>> {
113    /// Creates a new cell that contains a new Python reference to the same contained object.
114    ///
115    /// Returns an uninitialized cell if `self` has not yet been initialized.
116    pub fn clone_ref(&self, py: Python<'_>) -> Self {
117        let cloned = PyOnceLock::new();
118        if let Some(value) = self.get(py) {
119            let _ = cloned.set(py, value.clone_ref(py));
120        }
121        cloned
122    }
123}
124
125impl<T> PyOnceLock<Py<T>>
126where
127    T: PyTypeCheck,
128{
129    /// This is a shorthand method for `get_or_init` which imports the type from Python on init.
130    ///
131    /// # Example: Using `PyOnceLock` to store a class in a static variable.
132    ///
133    /// `PyOnceLock` can be used to avoid importing a class multiple times:
134    /// ```
135    /// # use pyo3::prelude::*;
136    /// # use pyo3::sync::PyOnceLock;
137    /// # use pyo3::types::{PyDict, PyType};
138    /// # use pyo3::intern;
139    /// #
140    /// #[pyfunction]
141    /// fn create_ordered_dict<'py>(py: Python<'py>, dict: Bound<'py, PyDict>) -> PyResult<Bound<'py, PyAny>> {
142    ///     // Even if this function is called multiple times,
143    ///     // the `OrderedDict` class will be imported only once.
144    ///     static ORDERED_DICT: PyOnceLock<Py<PyType>> = PyOnceLock::new();
145    ///     ORDERED_DICT
146    ///         .import(py, "collections", "OrderedDict")?
147    ///         .call1((dict,))
148    /// }
149    ///
150    /// # Python::attach(|py| {
151    /// #     let dict = PyDict::new(py);
152    /// #     dict.set_item(intern!(py, "foo"), 42).unwrap();
153    /// #     let fun = wrap_pyfunction!(create_ordered_dict, py).unwrap();
154    /// #     let ordered_dict = fun.call1((&dict,)).unwrap();
155    /// #     assert!(dict.eq(ordered_dict).unwrap());
156    /// # });
157    /// ```
158    pub fn import<'py>(
159        &self,
160        py: Python<'py>,
161        module_name: &str,
162        attr_name: &str,
163    ) -> PyResult<&Bound<'py, T>> {
164        self.get_or_try_init(py, || {
165            let type_object = py.import(module_name)?.getattr(attr_name)?.cast_into()?;
166            Ok(type_object.unbind())
167        })
168        .map(|ty| ty.bind(py))
169    }
170}
171
172#[cold]
173fn init_once_cell_py_attached<'a, F, T>(
174    cell: &'a once_cell::sync::OnceCell<T>,
175    _py: Python<'_>,
176    f: F,
177) -> &'a T
178where
179    F: FnOnce() -> T,
180{
181    // SAFETY: detach from the runtime right before a possibly blocking call
182    // then reattach when the blocking call completes and before calling
183    // into the C API.
184    let ts_guard = unsafe { SuspendAttach::new() };
185
186    // By having detached here, we guarantee that `.get_or_init` cannot deadlock with
187    // the Python interpreter
188    cell.get_or_init(move || {
189        drop(ts_guard);
190        f()
191    })
192}
193
194#[cold]
195fn try_init_once_cell_py_attached<'a, F, T, E>(
196    cell: &'a once_cell::sync::OnceCell<T>,
197    _py: Python<'_>,
198    f: F,
199) -> Result<&'a T, E>
200where
201    F: FnOnce() -> Result<T, E>,
202{
203    // SAFETY: detach from the runtime right before a possibly blocking call
204    // then reattach when the blocking call completes and before calling
205    // into the C API.
206    let ts_guard = unsafe { SuspendAttach::new() };
207
208    // By having detached here, we guarantee that `.get_or_init` cannot deadlock with
209    // the Python interpreter
210    cell.get_or_try_init(move || {
211        drop(ts_guard);
212        f()
213    })
214}
215
216#[cfg(test)]
217mod tests {
218    use super::*;
219
220    #[test]
221    fn test_once_cell() {
222        Python::attach(|py| {
223            let mut cell = PyOnceLock::new();
224
225            assert!(cell.get(py).is_none());
226
227            assert_eq!(cell.get_or_try_init(py, || Err(5)), Err(5));
228            assert!(cell.get(py).is_none());
229
230            assert_eq!(cell.get_or_try_init(py, || Ok::<_, ()>(2)), Ok(&2));
231            assert_eq!(cell.get(py), Some(&2));
232
233            assert_eq!(cell.get_or_try_init(py, || Err(5)), Ok(&2));
234
235            assert_eq!(cell.take(), Some(2));
236            assert_eq!(cell.into_inner(), None);
237
238            let cell_py = PyOnceLock::new();
239            assert!(cell_py.clone_ref(py).get(py).is_none());
240            cell_py.get_or_init(py, || py.None());
241            assert!(cell_py.clone_ref(py).get(py).unwrap().is_none(py));
242        })
243    }
244
245    #[test]
246    fn test_once_cell_drop() {
247        #[derive(Debug)]
248        struct RecordDrop<'a>(&'a mut bool);
249
250        impl Drop for RecordDrop<'_> {
251            fn drop(&mut self) {
252                *self.0 = true;
253            }
254        }
255
256        Python::attach(|py| {
257            let mut dropped = false;
258            let cell = PyOnceLock::new();
259            cell.set(py, RecordDrop(&mut dropped)).unwrap();
260            let drop_container = cell.get(py).unwrap();
261
262            assert!(!*drop_container.0);
263            drop(cell);
264            assert!(dropped);
265        });
266    }
267}