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}