pyo3/
sync.rs

1//! Synchronization mechanisms based on the Python GIL.
2//!
3//! With the acceptance of [PEP 703] (aka a "freethreaded Python") for Python 3.13, these
4//! are likely to undergo significant developments in the future.
5//!
6//! [PEP 703]: https://peps.python.org/pep-703/
7use crate::{
8    types::{any::PyAnyMethods, PyString, PyType},
9    Bound, Py, PyResult, PyVisit, Python,
10};
11use std::cell::UnsafeCell;
12
13/// Value with concurrent access protected by the GIL.
14///
15/// This is a synchronization primitive based on Python's global interpreter lock (GIL).
16/// It ensures that only one thread at a time can access the inner value via shared references.
17/// It can be combined with interior mutability to obtain mutable references.
18///
19/// # Example
20///
21/// Combining `GILProtected` with `RefCell` enables mutable access to static data:
22///
23/// ```
24/// # use pyo3::prelude::*;
25/// use pyo3::sync::GILProtected;
26/// use std::cell::RefCell;
27///
28/// static NUMBERS: GILProtected<RefCell<Vec<i32>>> = GILProtected::new(RefCell::new(Vec::new()));
29///
30/// Python::with_gil(|py| {
31///     NUMBERS.get(py).borrow_mut().push(42);
32/// });
33/// ```
34pub struct GILProtected<T> {
35    value: T,
36}
37
38impl<T> GILProtected<T> {
39    /// Place the given value under the protection of the GIL.
40    pub const fn new(value: T) -> Self {
41        Self { value }
42    }
43
44    /// Gain access to the inner value by giving proof of having acquired the GIL.
45    pub fn get<'py>(&'py self, _py: Python<'py>) -> &'py T {
46        &self.value
47    }
48
49    /// Gain access to the inner value by giving proof that garbage collection is happening.
50    pub fn traverse<'py>(&'py self, _visit: PyVisit<'py>) -> &'py T {
51        &self.value
52    }
53}
54
55unsafe impl<T> Sync for GILProtected<T> where T: Send {}
56
57/// A write-once cell similar to [`once_cell::OnceCell`](https://docs.rs/once_cell/latest/once_cell/).
58///
59/// Unlike `once_cell::sync` which blocks threads to achieve thread safety, this implementation
60/// uses the Python GIL to mediate concurrent access. This helps in cases where `once_cell` or
61/// `lazy_static`'s synchronization strategy can lead to deadlocks when interacting with the Python
62/// GIL. For an example, see
63#[doc = concat!("[the FAQ section](https://pyo3.rs/v", env!("CARGO_PKG_VERSION"), "/faq.html)")]
64/// of the guide.
65///
66/// Note that:
67///  1) `get_or_init` and `get_or_try_init` do not protect against infinite recursion
68///     from reentrant initialization.
69///  2) If the initialization function `f` provided to `get_or_init` (or `get_or_try_init`)
70///     temporarily releases the GIL (e.g. by calling `Python::import`) then it is possible
71///     for a second thread to also begin initializing the `GITOnceCell`. Even when this
72///     happens `GILOnceCell` guarantees that only **one** write to the cell ever occurs -
73///     this is treated as a race, other threads will discard the value they compute and
74///     return the result of the first complete computation.
75///
76/// # Examples
77///
78/// The following example shows how to use `GILOnceCell` to share a reference to a Python list
79/// between threads:
80///
81/// ```
82/// use pyo3::sync::GILOnceCell;
83/// use pyo3::prelude::*;
84/// use pyo3::types::PyList;
85///
86/// static LIST_CELL: GILOnceCell<Py<PyList>> = GILOnceCell::new();
87///
88/// pub fn get_shared_list(py: Python<'_>) -> &Bound<'_, PyList> {
89///     LIST_CELL
90///         .get_or_init(py, || PyList::empty_bound(py).unbind())
91///         .bind(py)
92/// }
93/// # Python::with_gil(|py| assert_eq!(get_shared_list(py).len(), 0));
94/// ```
95#[derive(Default)]
96pub struct GILOnceCell<T>(UnsafeCell<Option<T>>);
97
98// T: Send is needed for Sync because the thread which drops the GILOnceCell can be different
99// to the thread which fills it.
100unsafe impl<T: Send + Sync> Sync for GILOnceCell<T> {}
101unsafe impl<T: Send> Send for GILOnceCell<T> {}
102
103impl<T> GILOnceCell<T> {
104    /// Create a `GILOnceCell` which does not yet contain a value.
105    pub const fn new() -> Self {
106        Self(UnsafeCell::new(None))
107    }
108
109    /// Get a reference to the contained value, or `None` if the cell has not yet been written.
110    #[inline]
111    pub fn get(&self, _py: Python<'_>) -> Option<&T> {
112        // Safe because if the cell has not yet been written, None is returned.
113        unsafe { &*self.0.get() }.as_ref()
114    }
115
116    /// Get a reference to the contained value, initializing it if needed using the provided
117    /// closure.
118    ///
119    /// See the type-level documentation for detail on re-entrancy and concurrent initialization.
120    #[inline]
121    pub fn get_or_init<F>(&self, py: Python<'_>, f: F) -> &T
122    where
123        F: FnOnce() -> T,
124    {
125        if let Some(value) = self.get(py) {
126            return value;
127        }
128
129        // .unwrap() will never panic because the result is always Ok
130        self.init(py, || Ok::<T, std::convert::Infallible>(f()))
131            .unwrap()
132    }
133
134    /// Like `get_or_init`, but accepts a fallible initialization function. If it fails, the cell
135    /// is left uninitialized.
136    ///
137    /// See the type-level documentation for detail on re-entrancy and concurrent initialization.
138    #[inline]
139    pub fn get_or_try_init<F, E>(&self, py: Python<'_>, f: F) -> Result<&T, E>
140    where
141        F: FnOnce() -> Result<T, E>,
142    {
143        if let Some(value) = self.get(py) {
144            return Ok(value);
145        }
146
147        self.init(py, f)
148    }
149
150    #[cold]
151    fn init<F, E>(&self, py: Python<'_>, f: F) -> Result<&T, E>
152    where
153        F: FnOnce() -> Result<T, E>,
154    {
155        // Note that f() could temporarily release the GIL, so it's possible that another thread
156        // writes to this GILOnceCell before f() finishes. That's fine; we'll just have to discard
157        // the value computed here and accept a bit of wasted computation.
158        let value = f()?;
159        let _ = self.set(py, value);
160
161        Ok(self.get(py).unwrap())
162    }
163
164    /// Get the contents of the cell mutably. This is only possible if the reference to the cell is
165    /// unique.
166    pub fn get_mut(&mut self) -> Option<&mut T> {
167        self.0.get_mut().as_mut()
168    }
169
170    /// Set the value in the cell.
171    ///
172    /// If the cell has already been written, `Err(value)` will be returned containing the new
173    /// value which was not written.
174    pub fn set(&self, _py: Python<'_>, value: T) -> Result<(), T> {
175        // Safe because GIL is held, so no other thread can be writing to this cell concurrently.
176        let inner = unsafe { &mut *self.0.get() };
177        if inner.is_some() {
178            return Err(value);
179        }
180
181        *inner = Some(value);
182        Ok(())
183    }
184
185    /// Takes the value out of the cell, moving it back to an uninitialized state.
186    ///
187    /// Has no effect and returns None if the cell has not yet been written.
188    pub fn take(&mut self) -> Option<T> {
189        self.0.get_mut().take()
190    }
191
192    /// Consumes the cell, returning the wrapped value.
193    ///
194    /// Returns None if the cell has not yet been written.
195    pub fn into_inner(self) -> Option<T> {
196        self.0.into_inner()
197    }
198}
199
200impl<T> GILOnceCell<Py<T>> {
201    /// Create a new cell that contains a new Python reference to the same contained object.
202    ///
203    /// Returns an uninitialised cell if `self` has not yet been initialised.
204    pub fn clone_ref(&self, py: Python<'_>) -> Self {
205        Self(UnsafeCell::new(self.get(py).map(|ob| ob.clone_ref(py))))
206    }
207}
208
209impl GILOnceCell<Py<PyType>> {
210    /// Get a reference to the contained Python type, initializing it if needed.
211    ///
212    /// This is a shorthand method for `get_or_init` which imports the type from Python on init.
213    pub(crate) fn get_or_try_init_type_ref<'py>(
214        &self,
215        py: Python<'py>,
216        module_name: &str,
217        attr_name: &str,
218    ) -> PyResult<&Bound<'py, PyType>> {
219        self.get_or_try_init(py, || {
220            let type_object = py
221                .import_bound(module_name)?
222                .getattr(attr_name)?
223                .downcast_into()?;
224            Ok(type_object.unbind())
225        })
226        .map(|ty| ty.bind(py))
227    }
228}
229
230/// Interns `text` as a Python string and stores a reference to it in static storage.
231///
232/// A reference to the same Python string is returned on each invocation.
233///
234/// # Example: Using `intern!` to avoid needlessly recreating the same Python string
235///
236/// ```
237/// use pyo3::intern;
238/// # use pyo3::{prelude::*, types::PyDict};
239///
240/// #[pyfunction]
241/// fn create_dict(py: Python<'_>) -> PyResult<Bound<'_, PyDict>> {
242///     let dict = PyDict::new_bound(py);
243///     //             👇 A new `PyString` is created
244///     //                for every call of this function.
245///     dict.set_item("foo", 42)?;
246///     Ok(dict)
247/// }
248///
249/// #[pyfunction]
250/// fn create_dict_faster(py: Python<'_>) -> PyResult<Bound<'_, PyDict>> {
251///     let dict = PyDict::new_bound(py);
252///     //               👇 A `PyString` is created once and reused
253///     //                  for the lifetime of the program.
254///     dict.set_item(intern!(py, "foo"), 42)?;
255///     Ok(dict)
256/// }
257/// #
258/// # Python::with_gil(|py| {
259/// #     let fun_slow = wrap_pyfunction_bound!(create_dict, py).unwrap();
260/// #     let dict = fun_slow.call0().unwrap();
261/// #     assert!(dict.contains("foo").unwrap());
262/// #     let fun = wrap_pyfunction_bound!(create_dict_faster, py).unwrap();
263/// #     let dict = fun.call0().unwrap();
264/// #     assert!(dict.contains("foo").unwrap());
265/// # });
266/// ```
267#[macro_export]
268macro_rules! intern {
269    ($py: expr, $text: expr) => {{
270        static INTERNED: $crate::sync::Interned = $crate::sync::Interned::new($text);
271        INTERNED.get($py)
272    }};
273}
274
275/// Implementation detail for `intern!` macro.
276#[doc(hidden)]
277pub struct Interned(&'static str, GILOnceCell<Py<PyString>>);
278
279impl Interned {
280    /// Creates an empty holder for an interned `str`.
281    pub const fn new(value: &'static str) -> Self {
282        Interned(value, GILOnceCell::new())
283    }
284
285    /// Gets or creates the interned `str` value.
286    #[inline]
287    pub fn get<'py>(&self, py: Python<'py>) -> &Bound<'py, PyString> {
288        self.1
289            .get_or_init(py, || PyString::intern_bound(py, self.0).into())
290            .bind(py)
291    }
292}
293
294#[cfg(test)]
295mod tests {
296    use super::*;
297
298    use crate::types::{dict::PyDictMethods, PyDict};
299
300    #[test]
301    fn test_intern() {
302        Python::with_gil(|py| {
303            let foo1 = "foo";
304            let foo2 = intern!(py, "foo");
305            let foo3 = intern!(py, stringify!(foo));
306
307            let dict = PyDict::new_bound(py);
308            dict.set_item(foo1, 42_usize).unwrap();
309            assert!(dict.contains(foo2).unwrap());
310            assert_eq!(
311                dict.get_item(foo3)
312                    .unwrap()
313                    .unwrap()
314                    .extract::<usize>()
315                    .unwrap(),
316                42
317            );
318        });
319    }
320
321    #[test]
322    fn test_once_cell() {
323        Python::with_gil(|py| {
324            let mut cell = GILOnceCell::new();
325
326            assert!(cell.get(py).is_none());
327
328            assert_eq!(cell.get_or_try_init(py, || Err(5)), Err(5));
329            assert!(cell.get(py).is_none());
330
331            assert_eq!(cell.get_or_try_init(py, || Ok::<_, ()>(2)), Ok(&2));
332            assert_eq!(cell.get(py), Some(&2));
333
334            assert_eq!(cell.get_or_try_init(py, || Err(5)), Ok(&2));
335
336            assert_eq!(cell.take(), Some(2));
337            assert_eq!(cell.into_inner(), None);
338
339            let cell_py = GILOnceCell::new();
340            assert!(cell_py.clone_ref(py).get(py).is_none());
341            cell_py.get_or_init(py, || py.None());
342            assert!(cell_py.clone_ref(py).get(py).unwrap().is_none(py));
343        })
344    }
345}