1//! Interaction with Python's global interpreter lock
2
3use crate::impl_::not_send::{NotSend, NOT_SEND};
4use crate::{ffi, Python};
5use parking_lot::{const_mutex, Mutex, Once};
6use std::cell::Cell;
7#[cfg(debug_assertions)]
8use std::cell::RefCell;
9#[cfg(not(debug_assertions))]
10use std::cell::UnsafeCell;
11use std::{mem, ptr::NonNull};
12
13static START: Once = Once::new();
14
15cfg_if::cfg_if! {
16 if #[cfg(thread_local_const_init)] {
17 use std::thread_local as thread_local_const_init;
18 } else {
19 macro_rules! thread_local_const_init {
20 ($($(#[$attr:meta])* static $name:ident: $ty:ty = const { $init:expr };)*) => (
21 thread_local! { $($(#[$attr])* static $name: $ty = $init;)* }
22 )
23 }
24 }
25}
26
27thread_local_const_init! {
28 /// This is an internal counter in pyo3 monitoring whether this thread has the GIL.
29 ///
30 /// It will be incremented whenever a GILGuard or GILPool is created, and decremented whenever
31 /// they are dropped.
32 ///
33 /// As a result, if this thread has the GIL, GIL_COUNT is greater than zero.
34 ///
35 /// Additionally, we sometimes need to prevent safe access to the GIL,
36 /// e.g. when implementing `__traverse__`, which is represented by a negative value.
37 static GIL_COUNT: Cell<isize> = const { Cell::new(0) };
38
39 /// Temporarily hold objects that will be released when the GILPool drops.
40 #[cfg(debug_assertions)]
41 static OWNED_OBJECTS: RefCell<PyObjVec> = const { RefCell::new(Vec::new()) };
42 #[cfg(not(debug_assertions))]
43 static OWNED_OBJECTS: UnsafeCell<PyObjVec> = const { UnsafeCell::new(Vec::new()) };
44}
45
46const GIL_LOCKED_DURING_TRAVERSE: isize = -1;
47
48/// Checks whether the GIL is acquired.
49///
50/// Note: This uses pyo3's internal count rather than PyGILState_Check for two reasons:
51/// 1) for performance
52/// 2) PyGILState_Check always returns 1 if the sub-interpreter APIs have ever been called,
53/// which could lead to incorrect conclusions that the GIL is held.
54#[inline(always)]
55fn gil_is_acquired() -> bool {
56 GIL_COUNT.try_with(|c| c.get() > 0).unwrap_or(default:false)
57}
58
59/// Prepares the use of Python in a free-threaded context.
60///
61/// If the Python interpreter is not already initialized, this function will initialize it with
62/// signal handling disabled (Python will not raise the `KeyboardInterrupt` exception). Python
63/// signal handling depends on the notion of a 'main thread', which must be the thread that
64/// initializes the Python interpreter.
65///
66/// If the Python interpreter is already initialized, this function has no effect.
67///
68/// This function is unavailable under PyPy because PyPy cannot be embedded in Rust (or any other
69/// software). Support for this is tracked on the
70/// [PyPy issue tracker](https://foss.heptapod.net/pypy/pypy/-/issues/3286).
71///
72/// # Examples
73/// ```rust
74/// use pyo3::prelude::*;
75///
76/// # fn main() -> PyResult<()> {
77/// pyo3::prepare_freethreaded_python();
78/// Python::with_gil(|py| py.run("print('Hello World')", None, None))
79/// # }
80/// ```
81#[cfg(not(PyPy))]
82pub fn prepare_freethreaded_python() {
83 // Protect against race conditions when Python is not yet initialized and multiple threads
84 // concurrently call 'prepare_freethreaded_python()'. Note that we do not protect against
85 // concurrent initialization of the Python runtime by other users of the Python C API.
86 START.call_once_force(|_| unsafe {
87 // Use call_once_force because if initialization panics, it's okay to try again.
88 if ffi::Py_IsInitialized() == 0 {
89 ffi::Py_InitializeEx(arg1:0);
90
91 // Release the GIL.
92 ffi::PyEval_SaveThread();
93 }
94 });
95}
96
97/// Executes the provided closure with an embedded Python interpreter.
98///
99/// This function initializes the Python interpreter, executes the provided closure, and then
100/// finalizes the Python interpreter.
101///
102/// After execution all Python resources are cleaned up, and no further Python APIs can be called.
103/// Because many Python modules implemented in C do not support multiple Python interpreters in a
104/// single process, it is not safe to call this function more than once. (Many such modules will not
105/// initialize correctly on the second run.)
106///
107/// # Panics
108/// - If the Python interpreter is already initialized before calling this function.
109///
110/// # Safety
111/// - This function should only ever be called once per process (usually as part of the `main`
112/// function). It is also not thread-safe.
113/// - No Python APIs can be used after this function has finished executing.
114/// - The return value of the closure must not contain any Python value, _including_ `PyResult`.
115///
116/// # Examples
117///
118/// ```rust
119/// unsafe {
120/// pyo3::with_embedded_python_interpreter(|py| {
121/// if let Err(e) = py.run("print('Hello World')", None, None) {
122/// // We must make sure to not return a `PyErr`!
123/// e.print(py);
124/// }
125/// });
126/// }
127/// ```
128#[cfg(not(PyPy))]
129pub unsafe fn with_embedded_python_interpreter<F, R>(f: F) -> R
130where
131 F: for<'p> FnOnce(Python<'p>) -> R,
132{
133 assert_eq!(
134 ffi::Py_IsInitialized(),
135 0,
136 "called `with_embedded_python_interpreter` but a Python interpreter is already running."
137 );
138
139 ffi::Py_InitializeEx(0);
140
141 // Safety: the GIL is already held because of the Py_IntializeEx call.
142 let pool = GILPool::new();
143
144 // Import the threading module - this ensures that it will associate this thread as the "main"
145 // thread, which is important to avoid an `AssertionError` at finalization.
146 pool.python().import("threading").unwrap();
147
148 // Execute the closure.
149 let result = f(pool.python());
150
151 // Drop the pool before finalizing.
152 drop(pool);
153
154 // Finalize the Python interpreter.
155 ffi::Py_Finalize();
156
157 result
158}
159
160/// RAII type that represents the Global Interpreter Lock acquisition.
161pub(crate) struct GILGuard {
162 gstate: ffi::PyGILState_STATE,
163 pool: mem::ManuallyDrop<GILPool>,
164}
165
166impl GILGuard {
167 /// PyO3 internal API for acquiring the GIL. The public API is Python::with_gil.
168 ///
169 /// If the GIL was already acquired via PyO3, this returns `None`. Otherwise,
170 /// the GIL will be acquired and a new `GILPool` created.
171 pub(crate) fn acquire() -> Option<Self> {
172 if gil_is_acquired() {
173 return None;
174 }
175
176 // Maybe auto-initialize the GIL:
177 // - If auto-initialize feature set and supported, try to initialize the interpreter.
178 // - If the auto-initialize feature is set but unsupported, emit hard errors only when the
179 // extension-module feature is not activated - extension modules don't care about
180 // auto-initialize so this avoids breaking existing builds.
181 // - Otherwise, just check the GIL is initialized.
182 cfg_if::cfg_if! {
183 if #[cfg(all(feature = "auto-initialize", not(PyPy)))] {
184 prepare_freethreaded_python();
185 } else {
186 // This is a "hack" to make running `cargo test` for PyO3 convenient (i.e. no need
187 // to specify `--features auto-initialize` manually. Tests within the crate itself
188 // all depend on the auto-initialize feature for conciseness but Cargo does not
189 // provide a mechanism to specify required features for tests.
190 #[cfg(not(PyPy))]
191 if option_env!("CARGO_PRIMARY_PACKAGE").is_some() {
192 prepare_freethreaded_python();
193 }
194
195 START.call_once_force(|_| unsafe {
196 // Use call_once_force because if there is a panic because the interpreter is
197 // not initialized, it's fine for the user to initialize the interpreter and
198 // retry.
199 assert_ne!(
200 ffi::Py_IsInitialized(),
201 0,
202 "The Python interpreter is not initialized and the `auto-initialize` \
203 feature is not enabled.\n\n\
204 Consider calling `pyo3::prepare_freethreaded_python()` before attempting \
205 to use Python APIs."
206 );
207 });
208 }
209 }
210
211 Self::acquire_unchecked()
212 }
213
214 /// Acquires the `GILGuard` without performing any state checking.
215 ///
216 /// This can be called in "unsafe" contexts where the normal interpreter state
217 /// checking performed by `GILGuard::acquire` may fail. This includes calling
218 /// as part of multi-phase interpreter initialization.
219 pub(crate) fn acquire_unchecked() -> Option<Self> {
220 if gil_is_acquired() {
221 return None;
222 }
223
224 let gstate = unsafe { ffi::PyGILState_Ensure() }; // acquire GIL
225 let pool = unsafe { mem::ManuallyDrop::new(GILPool::new()) };
226
227 Some(GILGuard { gstate, pool })
228 }
229}
230
231/// The Drop implementation for `GILGuard` will release the GIL.
232impl Drop for GILGuard {
233 fn drop(&mut self) {
234 unsafe {
235 // Drop the objects in the pool before attempting to release the thread state
236 mem::ManuallyDrop::drop(&mut self.pool);
237
238 ffi::PyGILState_Release(self.gstate);
239 }
240 }
241}
242
243// Vector of PyObject
244type PyObjVec = Vec<NonNull<ffi::PyObject>>;
245
246/// Thread-safe storage for objects which were inc_ref / dec_ref while the GIL was not held.
247struct ReferencePool {
248 // .0 is INCREFs, .1 is DECREFs
249 pointer_ops: Mutex<(PyObjVec, PyObjVec)>,
250}
251
252impl ReferencePool {
253 const fn new() -> Self {
254 Self {
255 pointer_ops: const_mutex((Vec::new(), Vec::new())),
256 }
257 }
258
259 fn register_incref(&self, obj: NonNull<ffi::PyObject>) {
260 self.pointer_ops.lock().0.push(obj);
261 }
262
263 fn register_decref(&self, obj: NonNull<ffi::PyObject>) {
264 self.pointer_ops.lock().1.push(obj);
265 }
266
267 fn update_counts(&self, _py: Python<'_>) {
268 let mut ops = self.pointer_ops.lock();
269 if ops.0.is_empty() && ops.1.is_empty() {
270 return;
271 }
272
273 let (increfs, decrefs) = mem::take(&mut *ops);
274 drop(ops);
275
276 // Always increase reference counts first - as otherwise objects which have a
277 // nonzero total reference count might be incorrectly dropped by Python during
278 // this update.
279 for ptr in increfs {
280 unsafe { ffi::Py_INCREF(ptr.as_ptr()) };
281 }
282
283 for ptr in decrefs {
284 unsafe { ffi::Py_DECREF(ptr.as_ptr()) };
285 }
286 }
287}
288
289unsafe impl Sync for ReferencePool {}
290
291static POOL: ReferencePool = ReferencePool::new();
292
293/// A guard which can be used to temporarily release the GIL and restore on `Drop`.
294pub(crate) struct SuspendGIL {
295 count: isize,
296 tstate: *mut ffi::PyThreadState,
297}
298
299impl SuspendGIL {
300 pub(crate) unsafe fn new() -> Self {
301 let count: isize = GIL_COUNT.with(|c: &Cell| c.replace(val:0));
302 let tstate: *mut PyThreadState = ffi::PyEval_SaveThread();
303
304 Self { count, tstate }
305 }
306}
307
308impl Drop for SuspendGIL {
309 fn drop(&mut self) {
310 GIL_COUNT.with(|c: &Cell| c.set(self.count));
311 unsafe {
312 ffi::PyEval_RestoreThread(self.tstate);
313
314 // Update counts of PyObjects / Py that were cloned or dropped while the GIL was released.
315 POOL.update_counts(_py:Python::assume_gil_acquired());
316 }
317 }
318}
319
320/// Used to lock safe access to the GIL
321pub(crate) struct LockGIL {
322 count: isize,
323}
324
325impl LockGIL {
326 /// Lock access to the GIL while an implementation of `__traverse__` is running
327 pub fn during_traverse() -> Self {
328 Self::new(GIL_LOCKED_DURING_TRAVERSE)
329 }
330
331 fn new(reason: isize) -> Self {
332 let count: isize = GIL_COUNT.with(|c: &Cell| c.replace(val:reason));
333
334 Self { count }
335 }
336
337 #[cold]
338 fn bail(current: isize) {
339 match current {
340 GIL_LOCKED_DURING_TRAVERSE => panic!(
341 "Access to the GIL is prohibited while a __traverse__ implmentation is running."
342 ),
343 _ => panic!("Access to the GIL is currently prohibited."),
344 }
345 }
346}
347
348impl Drop for LockGIL {
349 fn drop(&mut self) {
350 GIL_COUNT.with(|c: &Cell| c.set(self.count));
351 }
352}
353
354/// A RAII pool which PyO3 uses to store owned Python references.
355///
356/// See the [Memory Management] chapter of the guide for more information about how PyO3 uses
357/// [`GILPool`] to manage memory.
358
359///
360/// [Memory Management]: https://pyo3.rs/main/memory.html#gil-bound-memory
361pub struct GILPool {
362 /// Initial length of owned objects and anys.
363 /// `Option` is used since TSL can be broken when `new` is called from `atexit`.
364 start: Option<usize>,
365 _not_send: NotSend,
366}
367
368impl GILPool {
369 /// Creates a new [`GILPool`]. This function should only ever be called with the GIL held.
370 ///
371 /// It is recommended not to use this API directly, but instead to use [`Python::new_pool`], as
372 /// that guarantees the GIL is held.
373 ///
374 /// # Safety
375 ///
376 /// As well as requiring the GIL, see the safety notes on [`Python::new_pool`].
377 #[inline]
378 pub unsafe fn new() -> GILPool {
379 increment_gil_count();
380 // Update counts of PyObjects / Py that have been cloned or dropped since last acquisition
381 POOL.update_counts(Python::assume_gil_acquired());
382 GILPool {
383 start: OWNED_OBJECTS
384 .try_with(|owned_objects| {
385 #[cfg(debug_assertions)]
386 let len = owned_objects.borrow().len();
387 #[cfg(not(debug_assertions))]
388 // SAFETY: This is not re-entrant.
389 let len = unsafe { (*owned_objects.get()).len() };
390 len
391 })
392 .ok(),
393 _not_send: NOT_SEND,
394 }
395 }
396
397 /// Gets the Python token associated with this [`GILPool`].
398 #[inline]
399 pub fn python(&self) -> Python<'_> {
400 unsafe { Python::assume_gil_acquired() }
401 }
402}
403
404impl Drop for GILPool {
405 fn drop(&mut self) {
406 if let Some(start) = self.start {
407 let owned_objects = OWNED_OBJECTS.with(|owned_objects| {
408 #[cfg(debug_assertions)]
409 let mut owned_objects = owned_objects.borrow_mut();
410 #[cfg(not(debug_assertions))]
411 // SAFETY: `OWNED_OBJECTS` is released before calling Py_DECREF,
412 // or Py_DECREF may call `GILPool::drop` recursively, resulting in invalid borrowing.
413 let owned_objects = unsafe { &mut *owned_objects.get() };
414 if start < owned_objects.len() {
415 owned_objects.split_off(start)
416 } else {
417 Vec::new()
418 }
419 });
420 for obj in owned_objects {
421 unsafe {
422 ffi::Py_DECREF(obj.as_ptr());
423 }
424 }
425 }
426 decrement_gil_count();
427 }
428}
429
430/// Registers a Python object pointer inside the release pool, to have its reference count increased
431/// the next time the GIL is acquired in pyo3.
432///
433/// If the GIL is held, the reference count will be increased immediately instead of being queued
434/// for later.
435///
436/// # Safety
437/// The object must be an owned Python reference.
438pub unsafe fn register_incref(obj: NonNull<ffi::PyObject>) {
439 if gil_is_acquired() {
440 ffi::Py_INCREF(op:obj.as_ptr())
441 } else {
442 POOL.register_incref(obj);
443 }
444}
445
446/// Registers a Python object pointer inside the release pool, to have its reference count decreased
447/// the next time the GIL is acquired in pyo3.
448///
449/// If the GIL is held, the reference count will be decreased immediately instead of being queued
450/// for later.
451///
452/// # Safety
453/// The object must be an owned Python reference.
454pub unsafe fn register_decref(obj: NonNull<ffi::PyObject>) {
455 if gil_is_acquired() {
456 ffi::Py_DECREF(op:obj.as_ptr())
457 } else {
458 POOL.register_decref(obj);
459 }
460}
461
462/// Registers an owned object inside the GILPool, to be released when the GILPool drops.
463///
464/// # Safety
465/// The object must be an owned Python reference.
466pub unsafe fn register_owned(_py: Python<'_>, obj: NonNull<ffi::PyObject>) {
467 debug_assert!(gil_is_acquired());
468 // Ignores the error in case this function called from `atexit`.
469 let _ = OWNED_OBJECTS.try_with(|owned_objects: &RefCell>>| {
470 #[cfg(debug_assertions)]
471 owned_objects.borrow_mut().push(obj);
472 #[cfg(not(debug_assertions))]
473 // SAFETY: This is not re-entrant.
474 unsafe {
475 (*owned_objects.get()).push(obj);
476 }
477 });
478}
479
480/// Increments pyo3's internal GIL count - to be called whenever GILPool or GILGuard is created.
481#[inline(always)]
482fn increment_gil_count() {
483 // Ignores the error in case this function called from `atexit`.
484 let _ = GIL_COUNT.try_with(|c: &Cell| {
485 let current: isize = c.get();
486 if current < 0 {
487 LockGIL::bail(current);
488 }
489 c.set(val:current + 1);
490 });
491}
492
493/// Decrements pyo3's internal GIL count - to be called whenever GILPool or GILGuard is dropped.
494#[inline(always)]
495fn decrement_gil_count() {
496 // Ignores the error in case this function called from `atexit`.
497 let _ = GIL_COUNT.try_with(|c: &Cell| {
498 let current: isize = c.get();
499 debug_assert!(
500 current > 0,
501 "Negative GIL count detected. Please report this error to the PyO3 repo as a bug."
502 );
503 c.set(val:current - 1);
504 });
505}
506
507#[cfg(test)]
508mod tests {
509 use super::{gil_is_acquired, GILPool, GIL_COUNT, OWNED_OBJECTS, POOL};
510 use crate::{ffi, gil, PyObject, Python, ToPyObject};
511 #[cfg(not(target_arch = "wasm32"))]
512 use parking_lot::{const_mutex, Condvar, Mutex};
513 use std::ptr::NonNull;
514
515 fn get_object(py: Python<'_>) -> PyObject {
516 // Convenience function for getting a single unique object, using `new_pool` so as to leave
517 // the original pool state unchanged.
518 let pool = unsafe { py.new_pool() };
519 let py = pool.python();
520
521 let obj = py.eval("object()", None, None).unwrap();
522 obj.to_object(py)
523 }
524
525 fn owned_object_count() -> usize {
526 #[cfg(debug_assertions)]
527 let len = OWNED_OBJECTS.with(|owned_objects| owned_objects.borrow().len());
528 #[cfg(not(debug_assertions))]
529 let len = OWNED_OBJECTS.with(|owned_objects| unsafe { (*owned_objects.get()).len() });
530 len
531 }
532
533 fn pool_inc_refs_does_not_contain(obj: &PyObject) -> bool {
534 !POOL
535 .pointer_ops
536 .lock()
537 .0
538 .contains(&unsafe { NonNull::new_unchecked(obj.as_ptr()) })
539 }
540
541 fn pool_dec_refs_does_not_contain(obj: &PyObject) -> bool {
542 !POOL
543 .pointer_ops
544 .lock()
545 .1
546 .contains(&unsafe { NonNull::new_unchecked(obj.as_ptr()) })
547 }
548
549 #[cfg(not(target_arch = "wasm32"))]
550 fn pool_dirty_with(
551 inc_refs: Vec<NonNull<ffi::PyObject>>,
552 dec_refs: Vec<NonNull<ffi::PyObject>>,
553 ) -> bool {
554 *POOL.pointer_ops.lock() == (inc_refs, dec_refs)
555 }
556
557 #[test]
558 fn test_owned() {
559 Python::with_gil(|py| {
560 let obj = get_object(py);
561 let obj_ptr = obj.as_ptr();
562 // Ensure that obj does not get freed
563 let _ref = obj.clone_ref(py);
564
565 unsafe {
566 {
567 let pool = py.new_pool();
568 gil::register_owned(pool.python(), NonNull::new_unchecked(obj.into_ptr()));
569
570 assert_eq!(owned_object_count(), 1);
571 assert_eq!(ffi::Py_REFCNT(obj_ptr), 2);
572 }
573 {
574 let _pool = py.new_pool();
575 assert_eq!(owned_object_count(), 0);
576 assert_eq!(ffi::Py_REFCNT(obj_ptr), 1);
577 }
578 }
579 })
580 }
581
582 #[test]
583 fn test_owned_nested() {
584 Python::with_gil(|py| {
585 let obj = get_object(py);
586 // Ensure that obj does not get freed
587 let _ref = obj.clone_ref(py);
588 let obj_ptr = obj.as_ptr();
589
590 unsafe {
591 {
592 let _pool = py.new_pool();
593 assert_eq!(owned_object_count(), 0);
594
595 gil::register_owned(py, NonNull::new_unchecked(obj.into_ptr()));
596
597 assert_eq!(owned_object_count(), 1);
598 assert_eq!(ffi::Py_REFCNT(obj_ptr), 2);
599 {
600 let _pool = py.new_pool();
601 let obj = get_object(py);
602 gil::register_owned(py, NonNull::new_unchecked(obj.into_ptr()));
603 assert_eq!(owned_object_count(), 2);
604 }
605 assert_eq!(owned_object_count(), 1);
606 }
607 {
608 assert_eq!(owned_object_count(), 0);
609 assert_eq!(ffi::Py_REFCNT(obj_ptr), 1);
610 }
611 }
612 });
613 }
614
615 #[test]
616 fn test_pyobject_drop_with_gil_decreases_refcnt() {
617 Python::with_gil(|py| {
618 let obj = get_object(py);
619
620 // Create a reference to drop with the GIL.
621 let reference = obj.clone_ref(py);
622
623 assert_eq!(obj.get_refcnt(py), 2);
624 assert!(pool_inc_refs_does_not_contain(&obj));
625
626 // With the GIL held, reference cound will be decreased immediately.
627 drop(reference);
628
629 assert_eq!(obj.get_refcnt(py), 1);
630 assert!(pool_dec_refs_does_not_contain(&obj));
631 });
632 }
633
634 #[test]
635 #[cfg(not(target_arch = "wasm32"))] // We are building wasm Python with pthreads disabled
636 fn test_pyobject_drop_without_gil_doesnt_decrease_refcnt() {
637 let obj = Python::with_gil(|py| {
638 let obj = get_object(py);
639 // Create a reference to drop without the GIL.
640 let reference = obj.clone_ref(py);
641
642 assert_eq!(obj.get_refcnt(py), 2);
643 assert!(pool_inc_refs_does_not_contain(&obj));
644
645 // Drop reference in a separate thread which doesn't have the GIL.
646 std::thread::spawn(move || drop(reference)).join().unwrap();
647
648 // The reference count should not have changed (the GIL has always
649 // been held by this thread), it is remembered to release later.
650 assert_eq!(obj.get_refcnt(py), 2);
651 assert!(pool_dirty_with(
652 vec![],
653 vec![NonNull::new(obj.as_ptr()).unwrap()]
654 ));
655 obj
656 });
657
658 // Next time the GIL is acquired, the reference is released
659 Python::with_gil(|py| {
660 assert_eq!(obj.get_refcnt(py), 1);
661 let non_null = unsafe { NonNull::new_unchecked(obj.as_ptr()) };
662 assert!(!POOL.pointer_ops.lock().0.contains(&non_null));
663 assert!(!POOL.pointer_ops.lock().1.contains(&non_null));
664 });
665 }
666
667 #[test]
668 fn test_gil_counts() {
669 // Check with_gil and GILPool both increase counts correctly
670 let get_gil_count = || GIL_COUNT.with(|c| c.get());
671
672 assert_eq!(get_gil_count(), 0);
673 Python::with_gil(|_| {
674 assert_eq!(get_gil_count(), 1);
675
676 let pool = unsafe { GILPool::new() };
677 assert_eq!(get_gil_count(), 2);
678
679 let pool2 = unsafe { GILPool::new() };
680 assert_eq!(get_gil_count(), 3);
681
682 drop(pool);
683 assert_eq!(get_gil_count(), 2);
684
685 Python::with_gil(|_| {
686 // nested with_gil doesn't update gil count
687 assert_eq!(get_gil_count(), 2);
688 });
689 assert_eq!(get_gil_count(), 2);
690
691 drop(pool2);
692 assert_eq!(get_gil_count(), 1);
693 });
694 assert_eq!(get_gil_count(), 0);
695 }
696
697 #[test]
698 fn test_allow_threads() {
699 assert!(!gil_is_acquired());
700
701 Python::with_gil(|py| {
702 assert!(gil_is_acquired());
703
704 py.allow_threads(move || {
705 assert!(!gil_is_acquired());
706
707 Python::with_gil(|_| assert!(gil_is_acquired()));
708
709 assert!(!gil_is_acquired());
710 });
711
712 assert!(gil_is_acquired());
713 });
714
715 assert!(!gil_is_acquired());
716 }
717
718 #[test]
719 fn test_allow_threads_updates_refcounts() {
720 Python::with_gil(|py| {
721 // Make a simple object with 1 reference
722 let obj = get_object(py);
723 assert!(obj.get_refcnt(py) == 1);
724 // Clone the object without the GIL to use internal tracking
725 let escaped_ref = py.allow_threads(|| obj.clone());
726 // But after the block the refcounts are updated
727 assert!(obj.get_refcnt(py) == 2);
728 drop(escaped_ref);
729 assert!(obj.get_refcnt(py) == 1);
730 drop(obj);
731 });
732 }
733
734 #[test]
735 fn dropping_gil_does_not_invalidate_references() {
736 // Acquiring GIL for the second time should be safe - see #864
737 Python::with_gil(|py| {
738 let obj = Python::with_gil(|_| py.eval("object()", None, None).unwrap());
739
740 // After gil2 drops, obj should still have a reference count of one
741 assert_eq!(obj.get_refcnt(), 1);
742 })
743 }
744
745 #[test]
746 fn test_clone_with_gil() {
747 Python::with_gil(|py| {
748 let obj = get_object(py);
749 let count = obj.get_refcnt(py);
750
751 // Cloning with the GIL should increase reference count immediately
752 #[allow(clippy::redundant_clone)]
753 let c = obj.clone();
754 assert_eq!(count + 1, c.get_refcnt(py));
755 })
756 }
757
758 #[cfg(not(target_arch = "wasm32"))]
759 struct Event {
760 set: Mutex<bool>,
761 wait: Condvar,
762 }
763
764 #[cfg(not(target_arch = "wasm32"))]
765 impl Event {
766 const fn new() -> Self {
767 Self {
768 set: const_mutex(false),
769 wait: Condvar::new(),
770 }
771 }
772
773 fn set(&self) {
774 *self.set.lock() = true;
775 self.wait.notify_all();
776 }
777
778 fn wait(&self) {
779 let mut set = self.set.lock();
780 while !*set {
781 self.wait.wait(&mut set);
782 }
783 }
784 }
785
786 #[test]
787 #[cfg(not(target_arch = "wasm32"))] // We are building wasm Python with pthreads disabled
788 fn test_clone_without_gil() {
789 use crate::{Py, PyAny};
790 use std::{sync::Arc, thread};
791
792 // Some events for synchronizing
793 static GIL_ACQUIRED: Event = Event::new();
794 static OBJECT_CLONED: Event = Event::new();
795 static REFCNT_CHECKED: Event = Event::new();
796
797 Python::with_gil(|py| {
798 let obj: Arc<Py<PyAny>> = Arc::new(get_object(py));
799 let thread_obj = Arc::clone(&obj);
800
801 let count = obj.get_refcnt(py);
802 println!(
803 "1: The object has been created and its reference count is {}",
804 count
805 );
806
807 let handle = thread::spawn(move || {
808 Python::with_gil(move |py| {
809 println!("3. The GIL has been acquired on another thread.");
810 GIL_ACQUIRED.set();
811
812 // Wait while the main thread registers obj in POOL
813 OBJECT_CLONED.wait();
814 println!("5. Checking refcnt");
815 assert_eq!(thread_obj.get_refcnt(py), count);
816
817 REFCNT_CHECKED.set();
818 })
819 });
820
821 let cloned = py.allow_threads(|| {
822 println!("2. The GIL has been released.");
823
824 // Wait until the GIL has been acquired on the thread.
825 GIL_ACQUIRED.wait();
826
827 println!("4. The other thread is now hogging the GIL, we clone without it held");
828 // Cloning without GIL should not update reference count
829 let cloned = Py::clone(&*obj);
830 OBJECT_CLONED.set();
831 cloned
832 });
833
834 REFCNT_CHECKED.wait();
835
836 println!("6. The main thread has acquired the GIL again and processed the pool.");
837
838 // Total reference count should be one higher
839 assert_eq!(obj.get_refcnt(py), count + 1);
840
841 // Clone dropped
842 drop(cloned);
843 // Ensure refcount of the arc is 1
844 handle.join().unwrap();
845
846 // Overall count is now back to the original, and should be no pending change
847 assert_eq!(Arc::try_unwrap(obj).unwrap().get_refcnt(py), count);
848 });
849 }
850
851 #[test]
852 #[cfg(not(target_arch = "wasm32"))] // We are building wasm Python with pthreads disabled
853 fn test_clone_in_other_thread() {
854 use crate::Py;
855 use std::{sync::Arc, thread};
856
857 // Some events for synchronizing
858 static OBJECT_CLONED: Event = Event::new();
859
860 let (obj, count, ptr) = Python::with_gil(|py| {
861 let obj = Arc::new(get_object(py));
862 let count = obj.get_refcnt(py);
863 let thread_obj = Arc::clone(&obj);
864
865 // Start a thread which does not have the GIL, and clone it
866 let t = thread::spawn(move || {
867 // Cloning without GIL should not update reference count
868 #[allow(clippy::redundant_clone)]
869 let _ = Py::clone(&*thread_obj);
870 OBJECT_CLONED.set();
871 });
872
873 OBJECT_CLONED.wait();
874 assert_eq!(count, obj.get_refcnt(py));
875
876 t.join().unwrap();
877 let ptr = NonNull::new(obj.as_ptr()).unwrap();
878
879 // The pointer should appear once in the incref pool, and once in the
880 // decref pool (for the clone being created and also dropped)
881 assert!(POOL.pointer_ops.lock().0.contains(&ptr));
882 assert!(POOL.pointer_ops.lock().1.contains(&ptr));
883
884 (obj, count, ptr)
885 });
886
887 Python::with_gil(|py| {
888 // Acquiring the gil clears the pool
889 assert!(!POOL.pointer_ops.lock().0.contains(&ptr));
890 assert!(!POOL.pointer_ops.lock().1.contains(&ptr));
891
892 // Overall count is still unchanged
893 assert_eq!(count, obj.get_refcnt(py));
894 });
895 }
896
897 #[test]
898 fn test_update_counts_does_not_deadlock() {
899 // update_counts can run arbitrary Python code during Py_DECREF.
900 // if the locking is implemented incorrectly, it will deadlock.
901
902 Python::with_gil(|py| {
903 let obj = get_object(py);
904
905 unsafe extern "C" fn capsule_drop(capsule: *mut ffi::PyObject) {
906 // This line will implicitly call update_counts
907 // -> and so cause deadlock if update_counts is not handling recursion correctly.
908 let pool = GILPool::new();
909
910 // Rebuild obj so that it can be dropped
911 PyObject::from_owned_ptr(
912 pool.python(),
913 ffi::PyCapsule_GetPointer(capsule, std::ptr::null()) as _,
914 );
915 }
916
917 let ptr = obj.into_ptr();
918
919 let capsule =
920 unsafe { ffi::PyCapsule_New(ptr as _, std::ptr::null(), Some(capsule_drop)) };
921
922 POOL.register_decref(NonNull::new(capsule).unwrap());
923
924 // Updating the counts will call decref on the capsule, which calls capsule_drop
925 POOL.update_counts(py);
926 })
927 }
928}
929