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 gil::SuspendGIL,
9 sealed::Sealed,
10 types::{any::PyAnyMethods, PyAny, PyString},
11 Bound, Py, PyResult, PyTypeCheck, Python,
12};
13use std::{
14 cell::UnsafeCell,
15 marker::PhantomData,
16 mem::MaybeUninit,
17 sync::{Once, OnceState},
18};
19
20#[cfg(not(Py_GIL_DISABLED))]
21use 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))]
47pub struct GILProtected<T> {
48 value: T,
49}
50
51#[cfg(not(Py_GIL_DISABLED))]
52impl<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))]
70unsafe 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/// ```
122pub 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
149impl<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).
158unsafe impl<T: Send + Sync> Sync for GILOnceCell<T> {}
159unsafe impl<T: Send> Send for GILOnceCell<T> {}
160
161impl<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
294impl<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 = Self {
300 once: Once::new(),
301 data: UnsafeCell::new(MaybeUninit::uninit()),
302 _marker: PhantomData,
303 };
304 if let Some(value) = self.get(py) {
305 let _ = cloned.set(py, value.clone_ref(py));
306 }
307 cloned
308 }
309}
310
311impl<T> GILOnceCell<Py<T>>
312where
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
363impl<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]
410macro_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)]
419pub struct Interned(&'static str, GILOnceCell<Py<PyString>>);
420
421impl 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))]
457pub fn with_critical_section<F, R>(object: &Bound<'_, PyAny>, f: F) -> R
458where
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#[cfg(rustc_has_once_lock)]
484mod once_lock_ext_sealed {
485 pub trait Sealed {}
486 impl<T> Sealed for std::sync::OnceLock<T> {}
487}
488
489/// Helper trait for `Once` to help avoid deadlocking when using a `Once` when attached to a
490/// Python thread.
491pub trait OnceExt: Sealed {
492 /// Similar to [`call_once`][Once::call_once], but releases the Python GIL temporarily
493 /// if blocking on another thread currently calling this `Once`.
494 fn call_once_py_attached(&self, py: Python<'_>, f: impl FnOnce());
495
496 /// Similar to [`call_once_force`][Once::call_once_force], but releases the Python GIL
497 /// temporarily if blocking on another thread currently calling this `Once`.
498 fn call_once_force_py_attached(&self, py: Python<'_>, f: impl FnOnce(&OnceState));
499}
500
501/// Extension trait for [`std::sync::OnceLock`] which helps avoid deadlocks between the Python
502/// interpreter and initialization with the `OnceLock`.
503#[cfg(rustc_has_once_lock)]
504pub trait OnceLockExt<T>: once_lock_ext_sealed::Sealed {
505 /// Initializes this `OnceLock` with the given closure if it has not been initialized yet.
506 ///
507 /// If this function would block, this function detaches from the Python interpreter and
508 /// reattaches before calling `f`. This avoids deadlocks between the Python interpreter and
509 /// the `OnceLock` in cases where `f` can call arbitrary Python code, as calling arbitrary
510 /// Python code can lead to `f` itself blocking on the Python interpreter.
511 ///
512 /// By detaching from the Python interpreter before blocking, this ensures that if `f` blocks
513 /// then the Python interpreter cannot be blocked by `f` itself.
514 fn get_or_init_py_attached<F>(&self, py: Python<'_>, f: F) -> &T
515 where
516 F: FnOnce() -> T;
517}
518
519/// Extension trait for [`std::sync::Mutex`] which helps avoid deadlocks between
520/// the Python interpreter and acquiring the `Mutex`.
521pub trait MutexExt<T>: Sealed {
522 /// Lock this `Mutex` in a manner that cannot deadlock with the Python interpreter.
523 ///
524 /// Before attempting to lock the mutex, this function detaches from the
525 /// Python runtime. When the lock is acquired, it re-attaches to the Python
526 /// runtime before returning the `LockResult`. This avoids deadlocks between
527 /// the GIL and other global synchronization events triggered by the Python
528 /// interpreter.
529 fn lock_py_attached(
530 &self,
531 py: Python<'_>,
532 ) -> std::sync::LockResult<std::sync::MutexGuard<'_, T>>;
533}
534
535impl OnceExt for Once {
536 fn call_once_py_attached(&self, py: Python<'_>, f: impl FnOnce()) {
537 if self.is_completed() {
538 return;
539 }
540
541 init_once_py_attached(self, py, f)
542 }
543
544 fn call_once_force_py_attached(&self, py: Python<'_>, f: impl FnOnce(&OnceState)) {
545 if self.is_completed() {
546 return;
547 }
548
549 init_once_force_py_attached(self, py, f);
550 }
551}
552
553#[cfg(rustc_has_once_lock)]
554impl<T> OnceLockExt<T> for std::sync::OnceLock<T> {
555 fn get_or_init_py_attached<F>(&self, py: Python<'_>, f: F) -> &T
556 where
557 F: FnOnce() -> T,
558 {
559 // this trait is guarded by a rustc version config
560 // so clippy's MSRV check is wrong
561 #[allow(clippy::incompatible_msrv)]
562 // Use self.get() first to create a fast path when initialized
563 self.get()
564 .unwrap_or_else(|| init_once_lock_py_attached(self, py, f))
565 }
566}
567
568impl<T> MutexExt<T> for std::sync::Mutex<T> {
569 fn lock_py_attached(
570 &self,
571 _py: Python<'_>,
572 ) -> std::sync::LockResult<std::sync::MutexGuard<'_, T>> {
573 // If try_lock is successful or returns a poisoned mutex, return them so
574 // the caller can deal with them. Otherwise we need to use blocking
575 // lock, which requires detaching from the Python runtime to avoid
576 // possible deadlocks.
577 match self.try_lock() {
578 Ok(inner) => return Ok(inner),
579 Err(std::sync::TryLockError::Poisoned(inner)) => {
580 return std::sync::LockResult::Err(inner)
581 }
582 Err(std::sync::TryLockError::WouldBlock) => {}
583 }
584 // SAFETY: detach from the runtime right before a possibly blocking call
585 // then reattach when the blocking call completes and before calling
586 // into the C API.
587 let ts_guard = unsafe { SuspendGIL::new() };
588 let res = self.lock();
589 drop(ts_guard);
590 res
591 }
592}
593
594#[cold]
595fn init_once_py_attached<F, T>(once: &Once, _py: Python<'_>, f: F)
596where
597 F: FnOnce() -> T,
598{
599 // SAFETY: detach from the runtime right before a possibly blocking call
600 // then reattach when the blocking call completes and before calling
601 // into the C API.
602 let ts_guard = unsafe { SuspendGIL::new() };
603
604 once.call_once(move || {
605 drop(ts_guard);
606 f();
607 });
608}
609
610#[cold]
611fn init_once_force_py_attached<F, T>(once: &Once, _py: Python<'_>, f: F)
612where
613 F: FnOnce(&OnceState) -> T,
614{
615 // SAFETY: detach from the runtime right before a possibly blocking call
616 // then reattach when the blocking call completes and before calling
617 // into the C API.
618 let ts_guard = unsafe { SuspendGIL::new() };
619
620 once.call_once_force(move |state| {
621 drop(ts_guard);
622 f(state);
623 });
624}
625
626#[cfg(rustc_has_once_lock)]
627#[cold]
628fn init_once_lock_py_attached<'a, F, T>(
629 lock: &'a std::sync::OnceLock<T>,
630 _py: Python<'_>,
631 f: F,
632) -> &'a T
633where
634 F: FnOnce() -> T,
635{
636 // SAFETY: detach from the runtime right before a possibly blocking call
637 // then reattach when the blocking call completes and before calling
638 // into the C API.
639 let ts_guard = unsafe { SuspendGIL::new() };
640
641 // this trait is guarded by a rustc version config
642 // so clippy's MSRV check is wrong
643 #[allow(clippy::incompatible_msrv)]
644 // By having detached here, we guarantee that `.get_or_init` cannot deadlock with
645 // the Python interpreter
646 let value = lock.get_or_init(move || {
647 drop(ts_guard);
648 f()
649 });
650
651 value
652}
653
654#[cfg(test)]
655mod tests {
656 use super::*;
657
658 use crate::types::{PyDict, PyDictMethods};
659 #[cfg(not(target_arch = "wasm32"))]
660 use std::sync::Mutex;
661 #[cfg(not(target_arch = "wasm32"))]
662 #[cfg(feature = "macros")]
663 use std::sync::{
664 atomic::{AtomicBool, Ordering},
665 Barrier,
666 };
667
668 #[cfg(not(target_arch = "wasm32"))]
669 #[cfg(feature = "macros")]
670 #[crate::pyclass(crate = "crate")]
671 struct BoolWrapper(AtomicBool);
672
673 #[test]
674 fn test_intern() {
675 Python::with_gil(|py| {
676 let foo1 = "foo";
677 let foo2 = intern!(py, "foo");
678 let foo3 = intern!(py, stringify!(foo));
679
680 let dict = PyDict::new(py);
681 dict.set_item(foo1, 42_usize).unwrap();
682 assert!(dict.contains(foo2).unwrap());
683 assert_eq!(
684 dict.get_item(foo3)
685 .unwrap()
686 .unwrap()
687 .extract::<usize>()
688 .unwrap(),
689 42
690 );
691 });
692 }
693
694 #[test]
695 fn test_once_cell() {
696 Python::with_gil(|py| {
697 let mut cell = GILOnceCell::new();
698
699 assert!(cell.get(py).is_none());
700
701 assert_eq!(cell.get_or_try_init(py, || Err(5)), Err(5));
702 assert!(cell.get(py).is_none());
703
704 assert_eq!(cell.get_or_try_init(py, || Ok::<_, ()>(2)), Ok(&2));
705 assert_eq!(cell.get(py), Some(&2));
706
707 assert_eq!(cell.get_or_try_init(py, || Err(5)), Ok(&2));
708
709 assert_eq!(cell.take(), Some(2));
710 assert_eq!(cell.into_inner(), None);
711
712 let cell_py = GILOnceCell::new();
713 assert!(cell_py.clone_ref(py).get(py).is_none());
714 cell_py.get_or_init(py, || py.None());
715 assert!(cell_py.clone_ref(py).get(py).unwrap().is_none(py));
716 })
717 }
718
719 #[test]
720 fn test_once_cell_drop() {
721 #[derive(Debug)]
722 struct RecordDrop<'a>(&'a mut bool);
723
724 impl Drop for RecordDrop<'_> {
725 fn drop(&mut self) {
726 *self.0 = true;
727 }
728 }
729
730 Python::with_gil(|py| {
731 let mut dropped = false;
732 let cell = GILOnceCell::new();
733 cell.set(py, RecordDrop(&mut dropped)).unwrap();
734 let drop_container = cell.get(py).unwrap();
735
736 assert!(!*drop_container.0);
737 drop(cell);
738 assert!(dropped);
739 });
740 }
741
742 #[cfg(feature = "macros")]
743 #[cfg(not(target_arch = "wasm32"))] // We are building wasm Python with pthreads disabled
744 #[test]
745 fn test_critical_section() {
746 let barrier = Barrier::new(2);
747
748 let bool_wrapper = Python::with_gil(|py| -> Py<BoolWrapper> {
749 Py::new(py, BoolWrapper(AtomicBool::new(false))).unwrap()
750 });
751
752 std::thread::scope(|s| {
753 s.spawn(|| {
754 Python::with_gil(|py| {
755 let b = bool_wrapper.bind(py);
756 with_critical_section(b, || {
757 barrier.wait();
758 std::thread::sleep(std::time::Duration::from_millis(10));
759 b.borrow().0.store(true, Ordering::Release);
760 })
761 });
762 });
763 s.spawn(|| {
764 barrier.wait();
765 Python::with_gil(|py| {
766 let b = bool_wrapper.bind(py);
767 // this blocks until the other thread's critical section finishes
768 with_critical_section(b, || {
769 assert!(b.borrow().0.load(Ordering::Acquire));
770 });
771 });
772 });
773 });
774 }
775
776 #[test]
777 #[cfg(not(target_arch = "wasm32"))] // We are building wasm Python with pthreads disabled
778 fn test_once_ext() {
779 // adapted from the example in the docs for Once::try_once_force
780 let init = Once::new();
781 std::thread::scope(|s| {
782 // poison the once
783 let handle = s.spawn(|| {
784 Python::with_gil(|py| {
785 init.call_once_py_attached(py, || panic!());
786 })
787 });
788 assert!(handle.join().is_err());
789
790 // poisoning propagates
791 let handle = s.spawn(|| {
792 Python::with_gil(|py| {
793 init.call_once_py_attached(py, || {});
794 });
795 });
796
797 assert!(handle.join().is_err());
798
799 // call_once_force will still run and reset the poisoned state
800 Python::with_gil(|py| {
801 init.call_once_force_py_attached(py, |state| {
802 assert!(state.is_poisoned());
803 });
804
805 // once any success happens, we stop propagating the poison
806 init.call_once_py_attached(py, || {});
807 });
808 });
809 }
810
811 #[cfg(rustc_has_once_lock)]
812 #[cfg(not(target_arch = "wasm32"))] // We are building wasm Python with pthreads disabled
813 #[test]
814 fn test_once_lock_ext() {
815 let cell = std::sync::OnceLock::new();
816 std::thread::scope(|s| {
817 assert!(cell.get().is_none());
818
819 s.spawn(|| {
820 Python::with_gil(|py| {
821 assert_eq!(*cell.get_or_init_py_attached(py, || 12345), 12345);
822 });
823 });
824 });
825 assert_eq!(cell.get(), Some(&12345));
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_mutex_ext() {
832 let barrier = Barrier::new(2);
833
834 let mutex = Python::with_gil(|py| -> Mutex<Py<BoolWrapper>> {
835 Mutex::new(Py::new(py, BoolWrapper(AtomicBool::new(false))).unwrap())
836 });
837
838 std::thread::scope(|s| {
839 s.spawn(|| {
840 Python::with_gil(|py| {
841 let b = mutex.lock_py_attached(py).unwrap();
842 barrier.wait();
843 // sleep to ensure the other thread actually blocks
844 std::thread::sleep(std::time::Duration::from_millis(10));
845 (*b).bind(py).borrow().0.store(true, Ordering::Release);
846 drop(b);
847 });
848 });
849 s.spawn(|| {
850 barrier.wait();
851 Python::with_gil(|py| {
852 // blocks until the other thread releases the lock
853 let b = mutex.lock_py_attached(py).unwrap();
854 assert!((*b).bind(py).borrow().0.load(Ordering::Acquire));
855 });
856 });
857 });
858 }
859
860 #[cfg(not(target_arch = "wasm32"))] // We are building wasm Python with pthreads disabled
861 #[test]
862 fn test_mutex_ext_poison() {
863 let mutex = Mutex::new(42);
864
865 std::thread::scope(|s| {
866 let lock_result = s.spawn(|| {
867 Python::with_gil(|py| {
868 let _unused = mutex.lock_py_attached(py);
869 panic!();
870 });
871 });
872 assert!(lock_result.join().is_err());
873 assert!(mutex.is_poisoned());
874 });
875 let guard = Python::with_gil(|py| {
876 // recover from the poisoning
877 match mutex.lock_py_attached(py) {
878 Ok(guard) => guard,
879 Err(poisoned) => poisoned.into_inner(),
880 }
881 });
882 assert!(*guard == 42);
883 }
884}