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/ |
7 | use crate::{ |
8 | gil::SuspendGIL, |
9 | sealed::Sealed, |
10 | types::{any::PyAnyMethods, PyAny, PyString}, |
11 | Bound, Py, PyResult, PyTypeCheck, Python, |
12 | }; |
13 | use std::{ |
14 | cell::UnsafeCell, |
15 | marker::PhantomData, |
16 | mem::MaybeUninit, |
17 | sync::{Once, OnceState}, |
18 | }; |
19 | |
20 | #[cfg (not(Py_GIL_DISABLED))] |
21 | use crate::PyVisit; |
22 | |
23 | /// Value with concurrent access protected by the GIL. |
24 | /// |
25 | /// This is a synchronization primitive based on Python's global interpreter lock (GIL). |
26 | /// It ensures that only one thread at a time can access the inner value via shared references. |
27 | /// It can be combined with interior mutability to obtain mutable references. |
28 | /// |
29 | /// This type is not defined for extensions built against the free-threaded CPython ABI. |
30 | /// |
31 | /// # Example |
32 | /// |
33 | /// Combining `GILProtected` with `RefCell` enables mutable access to static data: |
34 | /// |
35 | /// ``` |
36 | /// # use pyo3::prelude::*; |
37 | /// use pyo3::sync::GILProtected; |
38 | /// use std::cell::RefCell; |
39 | /// |
40 | /// static NUMBERS: GILProtected<RefCell<Vec<i32>>> = GILProtected::new(RefCell::new(Vec::new())); |
41 | /// |
42 | /// Python::with_gil(|py| { |
43 | /// NUMBERS.get(py).borrow_mut().push(42); |
44 | /// }); |
45 | /// ``` |
46 | #[cfg (not(Py_GIL_DISABLED))] |
47 | pub struct GILProtected<T> { |
48 | value: T, |
49 | } |
50 | |
51 | #[cfg (not(Py_GIL_DISABLED))] |
52 | impl<T> GILProtected<T> { |
53 | /// Place the given value under the protection of the GIL. |
54 | pub const fn new(value: T) -> Self { |
55 | Self { value } |
56 | } |
57 | |
58 | /// Gain access to the inner value by giving proof of having acquired the GIL. |
59 | pub fn get<'py>(&'py self, _py: Python<'py>) -> &'py T { |
60 | &self.value |
61 | } |
62 | |
63 | /// Gain access to the inner value by giving proof that garbage collection is happening. |
64 | pub fn traverse<'py>(&'py self, _visit: PyVisit<'py>) -> &'py T { |
65 | &self.value |
66 | } |
67 | } |
68 | |
69 | #[cfg (not(Py_GIL_DISABLED))] |
70 | unsafe impl<T> Sync for GILProtected<T> where T: Send {} |
71 | |
72 | /// A write-once primitive similar to [`std::sync::OnceLock<T>`]. |
73 | /// |
74 | /// Unlike `OnceLock<T>` which blocks threads to achieve thread safety, `GilOnceCell<T>` |
75 | /// allows calls to [`get_or_init`][GILOnceCell::get_or_init] and |
76 | /// [`get_or_try_init`][GILOnceCell::get_or_try_init] to race to create an initialized value. |
77 | /// (It is still guaranteed that only one thread will ever write to the cell.) |
78 | /// |
79 | /// On Python versions that run with the Global Interpreter Lock (GIL), this helps to avoid |
80 | /// deadlocks between initialization and the GIL. For an example of such a deadlock, see |
81 | #[doc = concat!( |
82 | "[the FAQ section](https://pyo3.rs/v" , |
83 | env!("CARGO_PKG_VERSION" ), |
84 | "/faq.html#im-experiencing-deadlocks-using-pyo3-with-stdsynconcelock-stdsynclazylock-lazy_static-and-once_cell)" |
85 | )] |
86 | /// of the guide. |
87 | /// |
88 | /// Note that because the GIL blocks concurrent execution, in practice the means that |
89 | /// [`get_or_init`][GILOnceCell::get_or_init] and |
90 | /// [`get_or_try_init`][GILOnceCell::get_or_try_init] may race if the initialization |
91 | /// function leads to the GIL being released and a thread context switch. This can |
92 | /// happen when importing or calling any Python code, as long as it releases the |
93 | /// GIL at some point. On free-threaded Python without any GIL, the race is |
94 | /// more likely since there is no GIL to prevent races. In the future, PyO3 may change |
95 | /// the semantics of GILOnceCell to behave more like the GIL build in the future. |
96 | /// |
97 | /// # Re-entrant initialization |
98 | /// |
99 | /// [`get_or_init`][GILOnceCell::get_or_init] and |
100 | /// [`get_or_try_init`][GILOnceCell::get_or_try_init] do not protect against infinite recursion |
101 | /// from reentrant initialization. |
102 | /// |
103 | /// # Examples |
104 | /// |
105 | /// The following example shows how to use `GILOnceCell` to share a reference to a Python list |
106 | /// between threads: |
107 | /// |
108 | /// ``` |
109 | /// use pyo3::sync::GILOnceCell; |
110 | /// use pyo3::prelude::*; |
111 | /// use pyo3::types::PyList; |
112 | /// |
113 | /// static LIST_CELL: GILOnceCell<Py<PyList>> = GILOnceCell::new(); |
114 | /// |
115 | /// pub fn get_shared_list(py: Python<'_>) -> &Bound<'_, PyList> { |
116 | /// LIST_CELL |
117 | /// .get_or_init(py, || PyList::empty(py).unbind()) |
118 | /// .bind(py) |
119 | /// } |
120 | /// # Python::with_gil(|py| assert_eq!(get_shared_list(py).len(), 0)); |
121 | /// ``` |
122 | pub struct GILOnceCell<T> { |
123 | once: Once, |
124 | data: UnsafeCell<MaybeUninit<T>>, |
125 | |
126 | /// (Copied from std::sync::OnceLock) |
127 | /// |
128 | /// `PhantomData` to make sure dropck understands we're dropping T in our Drop impl. |
129 | /// |
130 | /// ```compile_error,E0597 |
131 | /// use pyo3::Python; |
132 | /// use pyo3::sync::GILOnceCell; |
133 | /// |
134 | /// struct A<'a>(#[allow(dead_code)] &'a str); |
135 | /// |
136 | /// impl<'a> Drop for A<'a> { |
137 | /// fn drop(&mut self) {} |
138 | /// } |
139 | /// |
140 | /// let cell = GILOnceCell::new(); |
141 | /// { |
142 | /// let s = String::new(); |
143 | /// let _ = Python::with_gil(|py| cell.set(py,A(&s))); |
144 | /// } |
145 | /// ``` |
146 | _marker: PhantomData<T>, |
147 | } |
148 | |
149 | impl<T> Default for GILOnceCell<T> { |
150 | fn default() -> Self { |
151 | Self::new() |
152 | } |
153 | } |
154 | |
155 | // T: Send is needed for Sync because the thread which drops the GILOnceCell can be different |
156 | // to the thread which fills it. (e.g. think scoped thread which fills the cell and then exits, |
157 | // leaving the cell to be dropped by the main thread). |
158 | unsafe impl<T: Send + Sync> Sync for GILOnceCell<T> {} |
159 | unsafe impl<T: Send> Send for GILOnceCell<T> {} |
160 | |
161 | impl<T> GILOnceCell<T> { |
162 | /// Create a `GILOnceCell` which does not yet contain a value. |
163 | pub const fn new() -> Self { |
164 | Self { |
165 | once: Once::new(), |
166 | data: UnsafeCell::new(MaybeUninit::uninit()), |
167 | _marker: PhantomData, |
168 | } |
169 | } |
170 | |
171 | /// Get a reference to the contained value, or `None` if the cell has not yet been written. |
172 | #[inline ] |
173 | pub fn get(&self, _py: Python<'_>) -> Option<&T> { |
174 | if self.once.is_completed() { |
175 | // SAFETY: the cell has been written. |
176 | Some(unsafe { (*self.data.get()).assume_init_ref() }) |
177 | } else { |
178 | None |
179 | } |
180 | } |
181 | |
182 | /// Get a reference to the contained value, initializing it if needed using the provided |
183 | /// closure. |
184 | /// |
185 | /// See the type-level documentation for detail on re-entrancy and concurrent initialization. |
186 | #[inline ] |
187 | pub fn get_or_init<F>(&self, py: Python<'_>, f: F) -> &T |
188 | where |
189 | F: FnOnce() -> T, |
190 | { |
191 | if let Some(value) = self.get(py) { |
192 | return value; |
193 | } |
194 | |
195 | // .unwrap() will never panic because the result is always Ok |
196 | self.init(py, || Ok::<T, std::convert::Infallible>(f())) |
197 | .unwrap() |
198 | } |
199 | |
200 | /// Like `get_or_init`, but accepts a fallible initialization function. If it fails, the cell |
201 | /// is left uninitialized. |
202 | /// |
203 | /// See the type-level documentation for detail on re-entrancy and concurrent initialization. |
204 | #[inline ] |
205 | pub fn get_or_try_init<F, E>(&self, py: Python<'_>, f: F) -> Result<&T, E> |
206 | where |
207 | F: FnOnce() -> Result<T, E>, |
208 | { |
209 | if let Some(value) = self.get(py) { |
210 | return Ok(value); |
211 | } |
212 | |
213 | self.init(py, f) |
214 | } |
215 | |
216 | #[cold ] |
217 | fn init<F, E>(&self, py: Python<'_>, f: F) -> Result<&T, E> |
218 | where |
219 | F: FnOnce() -> Result<T, E>, |
220 | { |
221 | // Note that f() could temporarily release the GIL, so it's possible that another thread |
222 | // writes to this GILOnceCell before f() finishes. That's fine; we'll just have to discard |
223 | // the value computed here and accept a bit of wasted computation. |
224 | |
225 | // TODO: on the freethreaded build, consider wrapping this pair of operations in a |
226 | // critical section (requires a critical section API which can use a PyMutex without |
227 | // an object.) |
228 | let value = f()?; |
229 | let _ = self.set(py, value); |
230 | |
231 | Ok(self.get(py).unwrap()) |
232 | } |
233 | |
234 | /// Get the contents of the cell mutably. This is only possible if the reference to the cell is |
235 | /// unique. |
236 | pub fn get_mut(&mut self) -> Option<&mut T> { |
237 | if self.once.is_completed() { |
238 | // SAFETY: the cell has been written. |
239 | Some(unsafe { (*self.data.get()).assume_init_mut() }) |
240 | } else { |
241 | None |
242 | } |
243 | } |
244 | |
245 | /// Set the value in the cell. |
246 | /// |
247 | /// If the cell has already been written, `Err(value)` will be returned containing the new |
248 | /// value which was not written. |
249 | pub fn set(&self, _py: Python<'_>, value: T) -> Result<(), T> { |
250 | let mut value = Some(value); |
251 | // NB this can block, but since this is only writing a single value and |
252 | // does not call arbitrary python code, we don't need to worry about |
253 | // deadlocks with the GIL. |
254 | self.once.call_once_force(|_| { |
255 | // SAFETY: no other threads can be writing this value, because we are |
256 | // inside the `call_once_force` closure. |
257 | unsafe { |
258 | // `.take().unwrap()` will never panic |
259 | (*self.data.get()).write(value.take().unwrap()); |
260 | } |
261 | }); |
262 | |
263 | match value { |
264 | // Some other thread wrote to the cell first |
265 | Some(value) => Err(value), |
266 | None => Ok(()), |
267 | } |
268 | } |
269 | |
270 | /// Takes the value out of the cell, moving it back to an uninitialized state. |
271 | /// |
272 | /// Has no effect and returns None if the cell has not yet been written. |
273 | pub fn take(&mut self) -> Option<T> { |
274 | if self.once.is_completed() { |
275 | // Reset the cell to its default state so that it won't try to |
276 | // drop the value again. |
277 | self.once = Once::new(); |
278 | // SAFETY: the cell has been written. `self.once` has been reset, |
279 | // so when `self` is dropped the value won't be read again. |
280 | Some(unsafe { self.data.get_mut().assume_init_read() }) |
281 | } else { |
282 | None |
283 | } |
284 | } |
285 | |
286 | /// Consumes the cell, returning the wrapped value. |
287 | /// |
288 | /// Returns None if the cell has not yet been written. |
289 | pub fn into_inner(mut self) -> Option<T> { |
290 | self.take() |
291 | } |
292 | } |
293 | |
294 | impl<T> GILOnceCell<Py<T>> { |
295 | /// Creates a new cell that contains a new Python reference to the same contained object. |
296 | /// |
297 | /// Returns an uninitialized cell if `self` has not yet been initialized. |
298 | pub fn clone_ref(&self, py: Python<'_>) -> Self { |
299 | let cloned: GILOnceCell> = Self { |
300 | once: Once::new(), |
301 | data: UnsafeCell::new(MaybeUninit::uninit()), |
302 | _marker: PhantomData, |
303 | }; |
304 | if let Some(value: &Py) = self.get(py) { |
305 | let _ = cloned.set(py, value.clone_ref(py)); |
306 | } |
307 | cloned |
308 | } |
309 | } |
310 | |
311 | impl<T> GILOnceCell<Py<T>> |
312 | where |
313 | T: PyTypeCheck, |
314 | { |
315 | /// Get a reference to the contained Python type, initializing the cell if needed. |
316 | /// |
317 | /// This is a shorthand method for `get_or_init` which imports the type from Python on init. |
318 | /// |
319 | /// # Example: Using `GILOnceCell` to store a class in a static variable. |
320 | /// |
321 | /// `GILOnceCell` can be used to avoid importing a class multiple times: |
322 | /// ``` |
323 | /// # use pyo3::prelude::*; |
324 | /// # use pyo3::sync::GILOnceCell; |
325 | /// # use pyo3::types::{PyDict, PyType}; |
326 | /// # use pyo3::intern; |
327 | /// # |
328 | /// #[pyfunction] |
329 | /// fn create_ordered_dict<'py>(py: Python<'py>, dict: Bound<'py, PyDict>) -> PyResult<Bound<'py, PyAny>> { |
330 | /// // Even if this function is called multiple times, |
331 | /// // the `OrderedDict` class will be imported only once. |
332 | /// static ORDERED_DICT: GILOnceCell<Py<PyType>> = GILOnceCell::new(); |
333 | /// ORDERED_DICT |
334 | /// .import(py, "collections" , "OrderedDict" )? |
335 | /// .call1((dict,)) |
336 | /// } |
337 | /// |
338 | /// # Python::with_gil(|py| { |
339 | /// # let dict = PyDict::new(py); |
340 | /// # dict.set_item(intern!(py, "foo" ), 42).unwrap(); |
341 | /// # let fun = wrap_pyfunction!(create_ordered_dict, py).unwrap(); |
342 | /// # let ordered_dict = fun.call1((&dict,)).unwrap(); |
343 | /// # assert!(dict.eq(ordered_dict).unwrap()); |
344 | /// # }); |
345 | /// ``` |
346 | pub fn import<'py>( |
347 | &self, |
348 | py: Python<'py>, |
349 | module_name: &str, |
350 | attr_name: &str, |
351 | ) -> PyResult<&Bound<'py, T>> { |
352 | self.get_or_try_init(py, || { |
353 | let type_object = py |
354 | .import(module_name)? |
355 | .getattr(attr_name)? |
356 | .downcast_into()?; |
357 | Ok(type_object.unbind()) |
358 | }) |
359 | .map(|ty| ty.bind(py)) |
360 | } |
361 | } |
362 | |
363 | impl<T> Drop for GILOnceCell<T> { |
364 | fn drop(&mut self) { |
365 | if self.once.is_completed() { |
366 | // SAFETY: the cell has been written. |
367 | unsafe { MaybeUninit::assume_init_drop(self.data.get_mut()) } |
368 | } |
369 | } |
370 | } |
371 | |
372 | /// Interns `text` as a Python string and stores a reference to it in static storage. |
373 | /// |
374 | /// A reference to the same Python string is returned on each invocation. |
375 | /// |
376 | /// # Example: Using `intern!` to avoid needlessly recreating the same Python string |
377 | /// |
378 | /// ``` |
379 | /// use pyo3::intern; |
380 | /// # use pyo3::{prelude::*, types::PyDict}; |
381 | /// |
382 | /// #[pyfunction] |
383 | /// fn create_dict(py: Python<'_>) -> PyResult<Bound<'_, PyDict>> { |
384 | /// let dict = PyDict::new(py); |
385 | /// // 👇 A new `PyString` is created |
386 | /// // for every call of this function. |
387 | /// dict.set_item("foo" , 42)?; |
388 | /// Ok(dict) |
389 | /// } |
390 | /// |
391 | /// #[pyfunction] |
392 | /// fn create_dict_faster(py: Python<'_>) -> PyResult<Bound<'_, PyDict>> { |
393 | /// let dict = PyDict::new(py); |
394 | /// // 👇 A `PyString` is created once and reused |
395 | /// // for the lifetime of the program. |
396 | /// dict.set_item(intern!(py, "foo" ), 42)?; |
397 | /// Ok(dict) |
398 | /// } |
399 | /// # |
400 | /// # Python::with_gil(|py| { |
401 | /// # let fun_slow = wrap_pyfunction!(create_dict, py).unwrap(); |
402 | /// # let dict = fun_slow.call0().unwrap(); |
403 | /// # assert!(dict.contains("foo" ).unwrap()); |
404 | /// # let fun = wrap_pyfunction!(create_dict_faster, py).unwrap(); |
405 | /// # let dict = fun.call0().unwrap(); |
406 | /// # assert!(dict.contains("foo" ).unwrap()); |
407 | /// # }); |
408 | /// ``` |
409 | #[macro_export ] |
410 | macro_rules! intern { |
411 | ($py: expr, $text: expr) => {{ |
412 | static INTERNED: $crate::sync::Interned = $crate::sync::Interned::new($text); |
413 | INTERNED.get($py) |
414 | }}; |
415 | } |
416 | |
417 | /// Implementation detail for `intern!` macro. |
418 | #[doc (hidden)] |
419 | pub struct Interned(&'static str, GILOnceCell<Py<PyString>>); |
420 | |
421 | impl Interned { |
422 | /// Creates an empty holder for an interned `str`. |
423 | pub const fn new(value: &'static str) -> Self { |
424 | Interned(value, GILOnceCell::new()) |
425 | } |
426 | |
427 | /// Gets or creates the interned `str` value. |
428 | #[inline ] |
429 | pub fn get<'py>(&self, py: Python<'py>) -> &Bound<'py, PyString> { |
430 | self.1 |
431 | .get_or_init(py, || PyString::intern(py, self.0).into()) |
432 | .bind(py) |
433 | } |
434 | } |
435 | |
436 | /// Executes a closure with a Python critical section held on an object. |
437 | /// |
438 | /// Acquires the per-object lock for the object `op` that is held |
439 | /// until the closure `f` is finished. |
440 | /// |
441 | /// This is structurally equivalent to the use of the paired |
442 | /// Py_BEGIN_CRITICAL_SECTION and Py_END_CRITICAL_SECTION C-API macros. |
443 | /// |
444 | /// A no-op on GIL-enabled builds, where the critical section API is exposed as |
445 | /// a no-op by the Python C API. |
446 | /// |
447 | /// Provides weaker locking guarantees than traditional locks, but can in some |
448 | /// cases be used to provide guarantees similar to the GIL without the risk of |
449 | /// deadlocks associated with traditional locks. |
450 | /// |
451 | /// Many CPython C API functions do not acquire the per-object lock on objects |
452 | /// passed to Python. You should not expect critical sections applied to |
453 | /// built-in types to prevent concurrent modification. This API is most useful |
454 | /// for user-defined types with full control over how the internal state for the |
455 | /// type is managed. |
456 | #[cfg_attr (not(Py_GIL_DISABLED), allow(unused_variables))] |
457 | pub fn with_critical_section<F, R>(object: &Bound<'_, PyAny>, f: F) -> R |
458 | where |
459 | F: FnOnce() -> R, |
460 | { |
461 | #[cfg (Py_GIL_DISABLED)] |
462 | { |
463 | struct Guard(crate::ffi::PyCriticalSection); |
464 | |
465 | impl Drop for Guard { |
466 | fn drop(&mut self) { |
467 | unsafe { |
468 | crate::ffi::PyCriticalSection_End(&mut self.0); |
469 | } |
470 | } |
471 | } |
472 | |
473 | let mut guard = Guard(unsafe { std::mem::zeroed() }); |
474 | unsafe { crate::ffi::PyCriticalSection_Begin(&mut guard.0, object.as_ptr()) }; |
475 | f() |
476 | } |
477 | #[cfg (not(Py_GIL_DISABLED))] |
478 | { |
479 | f() |
480 | } |
481 | } |
482 | |
483 | /// Executes a closure with a Python critical section held on two objects. |
484 | /// |
485 | /// Acquires the per-object lock for the objects `a` and `b` that are held |
486 | /// until the closure `f` is finished. |
487 | /// |
488 | /// This is structurally equivalent to the use of the paired |
489 | /// Py_BEGIN_CRITICAL_SECTION2 and Py_END_CRITICAL_SECTION2 C-API macros. |
490 | /// |
491 | /// A no-op on GIL-enabled builds, where the critical section API is exposed as |
492 | /// a no-op by the Python C API. |
493 | /// |
494 | /// Provides weaker locking guarantees than traditional locks, but can in some |
495 | /// cases be used to provide guarantees similar to the GIL without the risk of |
496 | /// deadlocks associated with traditional locks. |
497 | /// |
498 | /// Many CPython C API functions do not acquire the per-object lock on objects |
499 | /// passed to Python. You should not expect critical sections applied to |
500 | /// built-in types to prevent concurrent modification. This API is most useful |
501 | /// for user-defined types with full control over how the internal state for the |
502 | /// type is managed. |
503 | #[cfg_attr (not(Py_GIL_DISABLED), allow(unused_variables))] |
504 | pub fn with_critical_section2<F, R>(a: &Bound<'_, PyAny>, b: &Bound<'_, PyAny>, f: F) -> R |
505 | where |
506 | F: FnOnce() -> R, |
507 | { |
508 | #[cfg (Py_GIL_DISABLED)] |
509 | { |
510 | struct Guard(crate::ffi::PyCriticalSection2); |
511 | |
512 | impl Drop for Guard { |
513 | fn drop(&mut self) { |
514 | unsafe { |
515 | crate::ffi::PyCriticalSection2_End(&mut self.0); |
516 | } |
517 | } |
518 | } |
519 | |
520 | let mut guard = Guard(unsafe { std::mem::zeroed() }); |
521 | unsafe { crate::ffi::PyCriticalSection2_Begin(&mut guard.0, a.as_ptr(), b.as_ptr()) }; |
522 | f() |
523 | } |
524 | #[cfg (not(Py_GIL_DISABLED))] |
525 | { |
526 | f() |
527 | } |
528 | } |
529 | |
530 | #[cfg (rustc_has_once_lock)] |
531 | mod once_lock_ext_sealed { |
532 | pub trait Sealed {} |
533 | impl<T> Sealed for std::sync::OnceLock<T> {} |
534 | } |
535 | |
536 | /// Helper trait for `Once` to help avoid deadlocking when using a `Once` when attached to a |
537 | /// Python thread. |
538 | pub trait OnceExt: Sealed { |
539 | /// Similar to [`call_once`][Once::call_once], but releases the Python GIL temporarily |
540 | /// if blocking on another thread currently calling this `Once`. |
541 | fn call_once_py_attached(&self, py: Python<'_>, f: impl FnOnce()); |
542 | |
543 | /// Similar to [`call_once_force`][Once::call_once_force], but releases the Python GIL |
544 | /// temporarily if blocking on another thread currently calling this `Once`. |
545 | fn call_once_force_py_attached(&self, py: Python<'_>, f: impl FnOnce(&OnceState)); |
546 | } |
547 | |
548 | /// Extension trait for [`std::sync::OnceLock`] which helps avoid deadlocks between the Python |
549 | /// interpreter and initialization with the `OnceLock`. |
550 | #[cfg (rustc_has_once_lock)] |
551 | pub trait OnceLockExt<T>: once_lock_ext_sealed::Sealed { |
552 | /// Initializes this `OnceLock` with the given closure if it has not been initialized yet. |
553 | /// |
554 | /// If this function would block, this function detaches from the Python interpreter and |
555 | /// reattaches before calling `f`. This avoids deadlocks between the Python interpreter and |
556 | /// the `OnceLock` in cases where `f` can call arbitrary Python code, as calling arbitrary |
557 | /// Python code can lead to `f` itself blocking on the Python interpreter. |
558 | /// |
559 | /// By detaching from the Python interpreter before blocking, this ensures that if `f` blocks |
560 | /// then the Python interpreter cannot be blocked by `f` itself. |
561 | fn get_or_init_py_attached<F>(&self, py: Python<'_>, f: F) -> &T |
562 | where |
563 | F: FnOnce() -> T; |
564 | } |
565 | |
566 | /// Extension trait for [`std::sync::Mutex`] which helps avoid deadlocks between |
567 | /// the Python interpreter and acquiring the `Mutex`. |
568 | pub trait MutexExt<T>: Sealed { |
569 | /// Lock this `Mutex` in a manner that cannot deadlock with the Python interpreter. |
570 | /// |
571 | /// Before attempting to lock the mutex, this function detaches from the |
572 | /// Python runtime. When the lock is acquired, it re-attaches to the Python |
573 | /// runtime before returning the `LockResult`. This avoids deadlocks between |
574 | /// the GIL and other global synchronization events triggered by the Python |
575 | /// interpreter. |
576 | fn lock_py_attached( |
577 | &self, |
578 | py: Python<'_>, |
579 | ) -> std::sync::LockResult<std::sync::MutexGuard<'_, T>>; |
580 | } |
581 | |
582 | impl OnceExt for Once { |
583 | fn call_once_py_attached(&self, py: Python<'_>, f: impl FnOnce()) { |
584 | if self.is_completed() { |
585 | return; |
586 | } |
587 | |
588 | init_once_py_attached(self, py, f) |
589 | } |
590 | |
591 | fn call_once_force_py_attached(&self, py: Python<'_>, f: impl FnOnce(&OnceState)) { |
592 | if self.is_completed() { |
593 | return; |
594 | } |
595 | |
596 | init_once_force_py_attached(self, py, f); |
597 | } |
598 | } |
599 | |
600 | #[cfg (rustc_has_once_lock)] |
601 | impl<T> OnceLockExt<T> for std::sync::OnceLock<T> { |
602 | fn get_or_init_py_attached<F>(&self, py: Python<'_>, f: F) -> &T |
603 | where |
604 | F: FnOnce() -> T, |
605 | { |
606 | // this trait is guarded by a rustc version config |
607 | // so clippy's MSRV check is wrong |
608 | #[allow (clippy::incompatible_msrv)] |
609 | // Use self.get() first to create a fast path when initialized |
610 | self.get() |
611 | .unwrap_or_else(|| init_once_lock_py_attached(self, py, f)) |
612 | } |
613 | } |
614 | |
615 | impl<T> MutexExt<T> for std::sync::Mutex<T> { |
616 | fn lock_py_attached( |
617 | &self, |
618 | _py: Python<'_>, |
619 | ) -> std::sync::LockResult<std::sync::MutexGuard<'_, T>> { |
620 | // If try_lock is successful or returns a poisoned mutex, return them so |
621 | // the caller can deal with them. Otherwise we need to use blocking |
622 | // lock, which requires detaching from the Python runtime to avoid |
623 | // possible deadlocks. |
624 | match self.try_lock() { |
625 | Ok(inner) => return Ok(inner), |
626 | Err(std::sync::TryLockError::Poisoned(inner)) => { |
627 | return std::sync::LockResult::Err(inner) |
628 | } |
629 | Err(std::sync::TryLockError::WouldBlock) => {} |
630 | } |
631 | // SAFETY: detach from the runtime right before a possibly blocking call |
632 | // then reattach when the blocking call completes and before calling |
633 | // into the C API. |
634 | let ts_guard = unsafe { SuspendGIL::new() }; |
635 | let res = self.lock(); |
636 | drop(ts_guard); |
637 | res |
638 | } |
639 | } |
640 | |
641 | #[cold ] |
642 | fn init_once_py_attached<F, T>(once: &Once, _py: Python<'_>, f: F) |
643 | where |
644 | F: FnOnce() -> T, |
645 | { |
646 | // SAFETY: detach from the runtime right before a possibly blocking call |
647 | // then reattach when the blocking call completes and before calling |
648 | // into the C API. |
649 | let ts_guard: SuspendGIL = unsafe { SuspendGIL::new() }; |
650 | |
651 | once.call_once(move || { |
652 | drop(ts_guard); |
653 | f(); |
654 | }); |
655 | } |
656 | |
657 | #[cold ] |
658 | fn init_once_force_py_attached<F, T>(once: &Once, _py: Python<'_>, f: F) |
659 | where |
660 | F: FnOnce(&OnceState) -> T, |
661 | { |
662 | // SAFETY: detach from the runtime right before a possibly blocking call |
663 | // then reattach when the blocking call completes and before calling |
664 | // into the C API. |
665 | let ts_guard: SuspendGIL = unsafe { SuspendGIL::new() }; |
666 | |
667 | once.call_once_force(move |state: &OnceState| { |
668 | drop(ts_guard); |
669 | f(state); |
670 | }); |
671 | } |
672 | |
673 | #[cfg (rustc_has_once_lock)] |
674 | #[cold ] |
675 | fn init_once_lock_py_attached<'a, F, T>( |
676 | lock: &'a std::sync::OnceLock<T>, |
677 | _py: Python<'_>, |
678 | f: F, |
679 | ) -> &'a T |
680 | where |
681 | F: FnOnce() -> T, |
682 | { |
683 | // SAFETY: detach from the runtime right before a possibly blocking call |
684 | // then reattach when the blocking call completes and before calling |
685 | // into the C API. |
686 | let ts_guard: SuspendGIL = unsafe { SuspendGIL::new() }; |
687 | |
688 | // this trait is guarded by a rustc version config |
689 | // so clippy's MSRV check is wrong |
690 | #[allow (clippy::incompatible_msrv)] |
691 | // By having detached here, we guarantee that `.get_or_init` cannot deadlock with |
692 | // the Python interpreter |
693 | let value: &T = lock.get_or_init(move || { |
694 | drop(ts_guard); |
695 | f() |
696 | }); |
697 | |
698 | value |
699 | } |
700 | |
701 | #[cfg (test)] |
702 | mod tests { |
703 | use super::*; |
704 | |
705 | use crate::types::{PyDict, PyDictMethods}; |
706 | #[cfg (not(target_arch = "wasm32" ))] |
707 | use std::sync::Mutex; |
708 | #[cfg (not(target_arch = "wasm32" ))] |
709 | #[cfg (feature = "macros" )] |
710 | use std::sync::{ |
711 | atomic::{AtomicBool, Ordering}, |
712 | Barrier, |
713 | }; |
714 | |
715 | #[cfg (not(target_arch = "wasm32" ))] |
716 | #[cfg (feature = "macros" )] |
717 | #[crate::pyclass (crate = "crate" )] |
718 | struct BoolWrapper(AtomicBool); |
719 | |
720 | #[cfg (not(target_arch = "wasm32" ))] |
721 | #[cfg (feature = "macros" )] |
722 | #[crate::pyclass (crate = "crate" )] |
723 | struct VecWrapper(Vec<isize>); |
724 | |
725 | #[test ] |
726 | fn test_intern() { |
727 | Python::with_gil(|py| { |
728 | let foo1 = "foo" ; |
729 | let foo2 = intern!(py, "foo" ); |
730 | let foo3 = intern!(py, stringify!(foo)); |
731 | |
732 | let dict = PyDict::new(py); |
733 | dict.set_item(foo1, 42_usize).unwrap(); |
734 | assert!(dict.contains(foo2).unwrap()); |
735 | assert_eq!( |
736 | dict.get_item(foo3) |
737 | .unwrap() |
738 | .unwrap() |
739 | .extract::<usize>() |
740 | .unwrap(), |
741 | 42 |
742 | ); |
743 | }); |
744 | } |
745 | |
746 | #[test ] |
747 | fn test_once_cell() { |
748 | Python::with_gil(|py| { |
749 | let mut cell = GILOnceCell::new(); |
750 | |
751 | assert!(cell.get(py).is_none()); |
752 | |
753 | assert_eq!(cell.get_or_try_init(py, || Err(5)), Err(5)); |
754 | assert!(cell.get(py).is_none()); |
755 | |
756 | assert_eq!(cell.get_or_try_init(py, || Ok::<_, ()>(2)), Ok(&2)); |
757 | assert_eq!(cell.get(py), Some(&2)); |
758 | |
759 | assert_eq!(cell.get_or_try_init(py, || Err(5)), Ok(&2)); |
760 | |
761 | assert_eq!(cell.take(), Some(2)); |
762 | assert_eq!(cell.into_inner(), None); |
763 | |
764 | let cell_py = GILOnceCell::new(); |
765 | assert!(cell_py.clone_ref(py).get(py).is_none()); |
766 | cell_py.get_or_init(py, || py.None()); |
767 | assert!(cell_py.clone_ref(py).get(py).unwrap().is_none(py)); |
768 | }) |
769 | } |
770 | |
771 | #[test ] |
772 | fn test_once_cell_drop() { |
773 | #[derive (Debug)] |
774 | struct RecordDrop<'a>(&'a mut bool); |
775 | |
776 | impl Drop for RecordDrop<'_> { |
777 | fn drop(&mut self) { |
778 | *self.0 = true; |
779 | } |
780 | } |
781 | |
782 | Python::with_gil(|py| { |
783 | let mut dropped = false; |
784 | let cell = GILOnceCell::new(); |
785 | cell.set(py, RecordDrop(&mut dropped)).unwrap(); |
786 | let drop_container = cell.get(py).unwrap(); |
787 | |
788 | assert!(!*drop_container.0); |
789 | drop(cell); |
790 | assert!(dropped); |
791 | }); |
792 | } |
793 | |
794 | #[cfg (feature = "macros" )] |
795 | #[cfg (not(target_arch = "wasm32" ))] // We are building wasm Python with pthreads disabled |
796 | #[test ] |
797 | fn test_critical_section() { |
798 | let barrier = Barrier::new(2); |
799 | |
800 | let bool_wrapper = Python::with_gil(|py| -> Py<BoolWrapper> { |
801 | Py::new(py, BoolWrapper(AtomicBool::new(false))).unwrap() |
802 | }); |
803 | |
804 | std::thread::scope(|s| { |
805 | s.spawn(|| { |
806 | Python::with_gil(|py| { |
807 | let b = bool_wrapper.bind(py); |
808 | with_critical_section(b, || { |
809 | barrier.wait(); |
810 | std::thread::sleep(std::time::Duration::from_millis(10)); |
811 | b.borrow().0.store(true, Ordering::Release); |
812 | }) |
813 | }); |
814 | }); |
815 | s.spawn(|| { |
816 | barrier.wait(); |
817 | Python::with_gil(|py| { |
818 | let b = bool_wrapper.bind(py); |
819 | // this blocks until the other thread's critical section finishes |
820 | with_critical_section(b, || { |
821 | assert!(b.borrow().0.load(Ordering::Acquire)); |
822 | }); |
823 | }); |
824 | }); |
825 | }); |
826 | } |
827 | |
828 | #[cfg (feature = "macros" )] |
829 | #[cfg (not(target_arch = "wasm32" ))] // We are building wasm Python with pthreads disabled |
830 | #[test ] |
831 | fn test_critical_section2() { |
832 | let barrier = Barrier::new(3); |
833 | |
834 | let (bool_wrapper1, bool_wrapper2) = Python::with_gil(|py| { |
835 | ( |
836 | Py::new(py, BoolWrapper(AtomicBool::new(false))).unwrap(), |
837 | Py::new(py, BoolWrapper(AtomicBool::new(false))).unwrap(), |
838 | ) |
839 | }); |
840 | |
841 | std::thread::scope(|s| { |
842 | s.spawn(|| { |
843 | Python::with_gil(|py| { |
844 | let b1 = bool_wrapper1.bind(py); |
845 | let b2 = bool_wrapper2.bind(py); |
846 | with_critical_section2(b1, b2, || { |
847 | barrier.wait(); |
848 | std::thread::sleep(std::time::Duration::from_millis(10)); |
849 | b1.borrow().0.store(true, Ordering::Release); |
850 | b2.borrow().0.store(true, Ordering::Release); |
851 | }) |
852 | }); |
853 | }); |
854 | s.spawn(|| { |
855 | barrier.wait(); |
856 | Python::with_gil(|py| { |
857 | let b1 = bool_wrapper1.bind(py); |
858 | // this blocks until the other thread's critical section finishes |
859 | with_critical_section(b1, || { |
860 | assert!(b1.borrow().0.load(Ordering::Acquire)); |
861 | }); |
862 | }); |
863 | }); |
864 | s.spawn(|| { |
865 | barrier.wait(); |
866 | Python::with_gil(|py| { |
867 | let b2 = bool_wrapper2.bind(py); |
868 | // this blocks until the other thread's critical section finishes |
869 | with_critical_section(b2, || { |
870 | assert!(b2.borrow().0.load(Ordering::Acquire)); |
871 | }); |
872 | }); |
873 | }); |
874 | }); |
875 | } |
876 | |
877 | #[cfg (feature = "macros" )] |
878 | #[cfg (not(target_arch = "wasm32" ))] // We are building wasm Python with pthreads disabled |
879 | #[test ] |
880 | fn test_critical_section2_same_object_no_deadlock() { |
881 | let barrier = Barrier::new(2); |
882 | |
883 | let bool_wrapper = Python::with_gil(|py| -> Py<BoolWrapper> { |
884 | Py::new(py, BoolWrapper(AtomicBool::new(false))).unwrap() |
885 | }); |
886 | |
887 | std::thread::scope(|s| { |
888 | s.spawn(|| { |
889 | Python::with_gil(|py| { |
890 | let b = bool_wrapper.bind(py); |
891 | with_critical_section2(b, b, || { |
892 | barrier.wait(); |
893 | std::thread::sleep(std::time::Duration::from_millis(10)); |
894 | b.borrow().0.store(true, Ordering::Release); |
895 | }) |
896 | }); |
897 | }); |
898 | s.spawn(|| { |
899 | barrier.wait(); |
900 | Python::with_gil(|py| { |
901 | let b = bool_wrapper.bind(py); |
902 | // this blocks until the other thread's critical section finishes |
903 | with_critical_section(b, || { |
904 | assert!(b.borrow().0.load(Ordering::Acquire)); |
905 | }); |
906 | }); |
907 | }); |
908 | }); |
909 | } |
910 | |
911 | #[cfg (feature = "macros" )] |
912 | #[cfg (not(target_arch = "wasm32" ))] // We are building wasm Python with pthreads disabled |
913 | #[test ] |
914 | fn test_critical_section2_two_containers() { |
915 | let (vec1, vec2) = Python::with_gil(|py| { |
916 | ( |
917 | Py::new(py, VecWrapper(vec![1, 2, 3])).unwrap(), |
918 | Py::new(py, VecWrapper(vec![4, 5])).unwrap(), |
919 | ) |
920 | }); |
921 | |
922 | std::thread::scope(|s| { |
923 | s.spawn(|| { |
924 | Python::with_gil(|py| { |
925 | let v1 = vec1.bind(py); |
926 | let v2 = vec2.bind(py); |
927 | with_critical_section2(v1, v2, || { |
928 | // v2.extend(v1) |
929 | v2.borrow_mut().0.extend(v1.borrow().0.iter()); |
930 | }) |
931 | }); |
932 | }); |
933 | s.spawn(|| { |
934 | Python::with_gil(|py| { |
935 | let v1 = vec1.bind(py); |
936 | let v2 = vec2.bind(py); |
937 | with_critical_section2(v1, v2, || { |
938 | // v1.extend(v2) |
939 | v1.borrow_mut().0.extend(v2.borrow().0.iter()); |
940 | }) |
941 | }); |
942 | }); |
943 | }); |
944 | |
945 | Python::with_gil(|py| { |
946 | let v1 = vec1.bind(py); |
947 | let v2 = vec2.bind(py); |
948 | // execution order is not guaranteed, so we need to check both |
949 | // NB: extend should be atomic, items must not be interleaved |
950 | // v1.extend(v2) |
951 | // v2.extend(v1) |
952 | let expected1_vec1 = vec![1, 2, 3, 4, 5]; |
953 | let expected1_vec2 = vec![4, 5, 1, 2, 3, 4, 5]; |
954 | // v2.extend(v1) |
955 | // v1.extend(v2) |
956 | let expected2_vec1 = vec![1, 2, 3, 4, 5, 1, 2, 3]; |
957 | let expected2_vec2 = vec![4, 5, 1, 2, 3]; |
958 | |
959 | assert!( |
960 | (v1.borrow().0.eq(&expected1_vec1) && v2.borrow().0.eq(&expected1_vec2)) |
961 | || (v1.borrow().0.eq(&expected2_vec1) && v2.borrow().0.eq(&expected2_vec2)) |
962 | ); |
963 | }); |
964 | } |
965 | |
966 | #[test ] |
967 | #[cfg (not(target_arch = "wasm32" ))] // We are building wasm Python with pthreads disabled |
968 | fn test_once_ext() { |
969 | // adapted from the example in the docs for Once::try_once_force |
970 | let init = Once::new(); |
971 | std::thread::scope(|s| { |
972 | // poison the once |
973 | let handle = s.spawn(|| { |
974 | Python::with_gil(|py| { |
975 | init.call_once_py_attached(py, || panic!()); |
976 | }) |
977 | }); |
978 | assert!(handle.join().is_err()); |
979 | |
980 | // poisoning propagates |
981 | let handle = s.spawn(|| { |
982 | Python::with_gil(|py| { |
983 | init.call_once_py_attached(py, || {}); |
984 | }); |
985 | }); |
986 | |
987 | assert!(handle.join().is_err()); |
988 | |
989 | // call_once_force will still run and reset the poisoned state |
990 | Python::with_gil(|py| { |
991 | init.call_once_force_py_attached(py, |state| { |
992 | assert!(state.is_poisoned()); |
993 | }); |
994 | |
995 | // once any success happens, we stop propagating the poison |
996 | init.call_once_py_attached(py, || {}); |
997 | }); |
998 | }); |
999 | } |
1000 | |
1001 | #[cfg (rustc_has_once_lock)] |
1002 | #[cfg (not(target_arch = "wasm32" ))] // We are building wasm Python with pthreads disabled |
1003 | #[test ] |
1004 | fn test_once_lock_ext() { |
1005 | let cell = std::sync::OnceLock::new(); |
1006 | std::thread::scope(|s| { |
1007 | assert!(cell.get().is_none()); |
1008 | |
1009 | s.spawn(|| { |
1010 | Python::with_gil(|py| { |
1011 | assert_eq!(*cell.get_or_init_py_attached(py, || 12345), 12345); |
1012 | }); |
1013 | }); |
1014 | }); |
1015 | assert_eq!(cell.get(), Some(&12345)); |
1016 | } |
1017 | |
1018 | #[cfg (feature = "macros" )] |
1019 | #[cfg (not(target_arch = "wasm32" ))] // We are building wasm Python with pthreads disabled |
1020 | #[test ] |
1021 | fn test_mutex_ext() { |
1022 | let barrier = Barrier::new(2); |
1023 | |
1024 | let mutex = Python::with_gil(|py| -> Mutex<Py<BoolWrapper>> { |
1025 | Mutex::new(Py::new(py, BoolWrapper(AtomicBool::new(false))).unwrap()) |
1026 | }); |
1027 | |
1028 | std::thread::scope(|s| { |
1029 | s.spawn(|| { |
1030 | Python::with_gil(|py| { |
1031 | let b = mutex.lock_py_attached(py).unwrap(); |
1032 | barrier.wait(); |
1033 | // sleep to ensure the other thread actually blocks |
1034 | std::thread::sleep(std::time::Duration::from_millis(10)); |
1035 | (*b).bind(py).borrow().0.store(true, Ordering::Release); |
1036 | drop(b); |
1037 | }); |
1038 | }); |
1039 | s.spawn(|| { |
1040 | barrier.wait(); |
1041 | Python::with_gil(|py| { |
1042 | // blocks until the other thread releases the lock |
1043 | let b = mutex.lock_py_attached(py).unwrap(); |
1044 | assert!((*b).bind(py).borrow().0.load(Ordering::Acquire)); |
1045 | }); |
1046 | }); |
1047 | }); |
1048 | } |
1049 | |
1050 | #[cfg (not(target_arch = "wasm32" ))] // We are building wasm Python with pthreads disabled |
1051 | #[test ] |
1052 | fn test_mutex_ext_poison() { |
1053 | let mutex = Mutex::new(42); |
1054 | |
1055 | std::thread::scope(|s| { |
1056 | let lock_result = s.spawn(|| { |
1057 | Python::with_gil(|py| { |
1058 | let _unused = mutex.lock_py_attached(py); |
1059 | panic!(); |
1060 | }); |
1061 | }); |
1062 | assert!(lock_result.join().is_err()); |
1063 | assert!(mutex.is_poisoned()); |
1064 | }); |
1065 | let guard = Python::with_gil(|py| { |
1066 | // recover from the poisoning |
1067 | match mutex.lock_py_attached(py) { |
1068 | Ok(guard) => guard, |
1069 | Err(poisoned) => poisoned.into_inner(), |
1070 | } |
1071 | }); |
1072 | assert!(*guard == 42); |
1073 | } |
1074 | } |
1075 | |