1#[cfg(feature = "gil-refs")]
4use crate::impl_::not_send::{NotSend, NOT_SEND};
5#[cfg(pyo3_disable_reference_pool)]
6use crate::impl_::panic::PanicTrap;
7use crate::{ffi, Python};
8#[cfg(not(pyo3_disable_reference_pool))]
9use once_cell::sync::Lazy;
10use std::cell::Cell;
11#[cfg(all(feature = "gil-refs", debug_assertions))]
12use std::cell::RefCell;
13#[cfg(all(feature = "gil-refs", not(debug_assertions)))]
14use std::cell::UnsafeCell;
15use std::{mem, ptr::NonNull, sync};
16
17static START: sync::Once = sync::Once::new();
18
19std::thread_local! {
20 static GIL_COUNT: Cell<isize> = const { Cell::new(0) };
30
31 #[cfg(all(feature = "gil-refs", debug_assertions))]
33 static OWNED_OBJECTS: RefCell<PyObjVec> = const { RefCell::new(Vec::new()) };
34 #[cfg(all(feature = "gil-refs", not(debug_assertions)))]
35 static OWNED_OBJECTS: UnsafeCell<PyObjVec> = const { UnsafeCell::new(Vec::new()) };
36}
37
38const GIL_LOCKED_DURING_TRAVERSE: isize = -1;
39
40#[inline(always)]
47fn gil_is_acquired() -> bool {
48 GIL_COUNT.try_with(|c| c.get() > 0).unwrap_or(false)
49}
50
51#[cfg(not(any(PyPy, GraalPy)))]
74pub fn prepare_freethreaded_python() {
75 START.call_once_force(|_| unsafe {
79 if ffi::Py_IsInitialized() == 0 {
81 ffi::Py_InitializeEx(0);
82
83 ffi::PyEval_SaveThread();
85 }
86 });
87}
88
89#[cfg(not(any(PyPy, GraalPy)))]
121pub unsafe fn with_embedded_python_interpreter<F, R>(f: F) -> R
122where
123 F: for<'p> FnOnce(Python<'p>) -> R,
124{
125 assert_eq!(
126 ffi::Py_IsInitialized(),
127 0,
128 "called `with_embedded_python_interpreter` but a Python interpreter is already running."
129 );
130
131 ffi::Py_InitializeEx(0);
132
133 let result = {
134 let guard = GILGuard::assume();
135 let py = guard.python();
136 py.import_bound("threading").unwrap();
139
140 f(py)
142 };
143
144 ffi::Py_Finalize();
146
147 result
148}
149
150pub(crate) enum GILGuard {
152 Assumed,
154 Ensured {
156 gstate: ffi::PyGILState_STATE,
157 #[cfg(feature = "gil-refs")]
158 #[allow(deprecated)]
159 pool: mem::ManuallyDrop<GILPool>,
160 },
161}
162
163impl GILGuard {
164 pub(crate) fn acquire() -> Self {
170 if gil_is_acquired() {
171 return unsafe { Self::assume() };
173 }
174
175 cfg_if::cfg_if! {
182 if #[cfg(all(feature = "auto-initialize", not(any(PyPy, GraalPy))))] {
183 prepare_freethreaded_python();
184 } else {
185 #[cfg(not(any(PyPy, GraalPy)))]
190 if option_env!("CARGO_PRIMARY_PACKAGE").is_some() {
191 prepare_freethreaded_python();
192 }
193
194 START.call_once_force(|_| unsafe {
195 assert_ne!(
199 ffi::Py_IsInitialized(),
200 0,
201 "The Python interpreter is not initialized and the `auto-initialize` \
202 feature is not enabled.\n\n\
203 Consider calling `pyo3::prepare_freethreaded_python()` before attempting \
204 to use Python APIs."
205 );
206 });
207 }
208 }
209
210 unsafe { Self::acquire_unchecked() }
212 }
213
214 pub(crate) unsafe fn acquire_unchecked() -> Self {
220 if gil_is_acquired() {
221 return Self::assume();
222 }
223
224 let gstate = ffi::PyGILState_Ensure(); increment_gil_count();
226
227 #[cfg(feature = "gil-refs")]
228 #[allow(deprecated)]
229 let pool = mem::ManuallyDrop::new(GILPool::new());
230
231 #[cfg(not(pyo3_disable_reference_pool))]
232 if let Some(pool) = Lazy::get(&POOL) {
233 pool.update_counts(Python::assume_gil_acquired());
234 }
235 GILGuard::Ensured {
236 gstate,
237 #[cfg(feature = "gil-refs")]
238 pool,
239 }
240 }
241
242 pub(crate) unsafe fn assume() -> Self {
244 increment_gil_count();
245 let guard = GILGuard::Assumed;
246 #[cfg(not(pyo3_disable_reference_pool))]
247 if let Some(pool) = Lazy::get(&POOL) {
248 pool.update_counts(guard.python());
249 }
250 guard
251 }
252
253 #[inline]
255 pub fn python(&self) -> Python<'_> {
256 unsafe { Python::assume_gil_acquired() }
257 }
258}
259
260impl Drop for GILGuard {
262 fn drop(&mut self) {
263 match self {
264 GILGuard::Assumed => {}
265 GILGuard::Ensured {
266 gstate,
267 #[cfg(feature = "gil-refs")]
268 pool,
269 } => unsafe {
270 #[cfg(feature = "gil-refs")]
272 mem::ManuallyDrop::drop(pool);
273 ffi::PyGILState_Release(*gstate);
274 },
275 }
276 decrement_gil_count();
277 }
278}
279
280type PyObjVec = Vec<NonNull<ffi::PyObject>>;
282
283#[cfg(not(pyo3_disable_reference_pool))]
284struct ReferencePool {
286 pending_decrefs: sync::Mutex<PyObjVec>,
287}
288
289#[cfg(not(pyo3_disable_reference_pool))]
290impl ReferencePool {
291 const fn new() -> Self {
292 Self {
293 pending_decrefs: sync::Mutex::new(Vec::new()),
294 }
295 }
296
297 fn register_decref(&self, obj: NonNull<ffi::PyObject>) {
298 self.pending_decrefs.lock().unwrap().push(obj);
299 }
300
301 fn update_counts(&self, _py: Python<'_>) {
302 let mut pending_decrefs = self.pending_decrefs.lock().unwrap();
303 if pending_decrefs.is_empty() {
304 return;
305 }
306
307 let decrefs = mem::take(&mut *pending_decrefs);
308 drop(pending_decrefs);
309
310 for ptr in decrefs {
311 unsafe { ffi::Py_DECREF(ptr.as_ptr()) };
312 }
313 }
314}
315
316#[cfg(not(pyo3_disable_reference_pool))]
317unsafe impl Send for ReferencePool {}
318
319#[cfg(not(pyo3_disable_reference_pool))]
320unsafe impl Sync for ReferencePool {}
321
322#[cfg(not(pyo3_disable_reference_pool))]
323static POOL: Lazy<ReferencePool> = Lazy::new(ReferencePool::new);
324
325pub(crate) struct SuspendGIL {
327 count: isize,
328 tstate: *mut ffi::PyThreadState,
329}
330
331impl SuspendGIL {
332 pub(crate) unsafe fn new() -> Self {
333 let count = GIL_COUNT.with(|c| c.replace(0));
334 let tstate = ffi::PyEval_SaveThread();
335
336 Self { count, tstate }
337 }
338}
339
340impl Drop for SuspendGIL {
341 fn drop(&mut self) {
342 GIL_COUNT.with(|c| c.set(self.count));
343 unsafe {
344 ffi::PyEval_RestoreThread(self.tstate);
345
346 #[cfg(not(pyo3_disable_reference_pool))]
348 if let Some(pool) = Lazy::get(&POOL) {
349 pool.update_counts(Python::assume_gil_acquired());
350 }
351 }
352 }
353}
354
355pub(crate) struct LockGIL {
357 count: isize,
358}
359
360impl LockGIL {
361 pub fn during_traverse() -> Self {
363 Self::new(GIL_LOCKED_DURING_TRAVERSE)
364 }
365
366 fn new(reason: isize) -> Self {
367 let count = GIL_COUNT.with(|c| c.replace(reason));
368
369 Self { count }
370 }
371
372 #[cold]
373 fn bail(current: isize) {
374 match current {
375 GIL_LOCKED_DURING_TRAVERSE => panic!(
376 "Access to the GIL is prohibited while a __traverse__ implmentation is running."
377 ),
378 _ => panic!("Access to the GIL is currently prohibited."),
379 }
380 }
381}
382
383impl Drop for LockGIL {
384 fn drop(&mut self) {
385 GIL_COUNT.with(|c| c.set(self.count));
386 }
387}
388
389#[cfg(feature = "gil-refs")]
397#[deprecated(
398 since = "0.21.0",
399 note = "`GILPool` has no function if PyO3's deprecated GIL Refs API is not used"
400)]
401pub struct GILPool {
402 start: Option<usize>,
405 _not_send: NotSend,
406}
407
408#[cfg(feature = "gil-refs")]
409#[allow(deprecated)]
410impl GILPool {
411 #[inline]
420 pub unsafe fn new() -> GILPool {
421 #[cfg(not(pyo3_disable_reference_pool))]
423 if let Some(pool) = Lazy::get(&POOL) {
424 pool.update_counts(Python::assume_gil_acquired());
425 }
426 GILPool {
427 start: OWNED_OBJECTS
428 .try_with(|owned_objects| {
429 #[cfg(debug_assertions)]
430 let len = owned_objects.borrow().len();
431 #[cfg(not(debug_assertions))]
432 let len = unsafe { (*owned_objects.get()).len() };
434 len
435 })
436 .ok(),
437 _not_send: NOT_SEND,
438 }
439 }
440
441 #[inline]
443 pub fn python(&self) -> Python<'_> {
444 unsafe { Python::assume_gil_acquired() }
445 }
446}
447
448#[cfg(feature = "gil-refs")]
449#[allow(deprecated)]
450impl Drop for GILPool {
451 fn drop(&mut self) {
452 if let Some(start) = self.start {
453 let owned_objects = OWNED_OBJECTS.with(|owned_objects| {
454 #[cfg(debug_assertions)]
455 let mut owned_objects = owned_objects.borrow_mut();
456 #[cfg(not(debug_assertions))]
457 let owned_objects = unsafe { &mut *owned_objects.get() };
460 if start < owned_objects.len() {
461 owned_objects.split_off(start)
462 } else {
463 Vec::new()
464 }
465 });
466 for obj in owned_objects {
467 unsafe {
468 ffi::Py_DECREF(obj.as_ptr());
469 }
470 }
471 }
472 }
473}
474
475#[cfg(feature = "py-clone")]
481#[track_caller]
482pub unsafe fn register_incref(obj: NonNull<ffi::PyObject>) {
483 if gil_is_acquired() {
484 ffi::Py_INCREF(obj.as_ptr())
485 } else {
486 panic!("Cannot clone pointer into Python heap without the GIL being held.");
487 }
488}
489
490#[track_caller]
499pub unsafe fn register_decref(obj: NonNull<ffi::PyObject>) {
500 if gil_is_acquired() {
501 ffi::Py_DECREF(obj.as_ptr())
502 } else {
503 #[cfg(not(pyo3_disable_reference_pool))]
504 POOL.register_decref(obj);
505 #[cfg(all(
506 pyo3_disable_reference_pool,
507 not(pyo3_leak_on_drop_without_reference_pool)
508 ))]
509 {
510 let _trap = PanicTrap::new("Aborting the process to avoid panic-from-drop.");
511 panic!("Cannot drop pointer into Python heap without the GIL being held.");
512 }
513 }
514}
515
516#[cfg(feature = "gil-refs")]
521pub unsafe fn register_owned(_py: Python<'_>, obj: NonNull<ffi::PyObject>) {
522 debug_assert!(gil_is_acquired());
523 let _ = OWNED_OBJECTS.try_with(|owned_objects| {
525 #[cfg(debug_assertions)]
526 owned_objects.borrow_mut().push(obj);
527 #[cfg(not(debug_assertions))]
528 unsafe {
530 (*owned_objects.get()).push(obj);
531 }
532 });
533}
534
535#[inline(always)]
537fn increment_gil_count() {
538 let _ = GIL_COUNT.try_with(|c| {
540 let current = c.get();
541 if current < 0 {
542 LockGIL::bail(current);
543 }
544 c.set(current + 1);
545 });
546}
547
548#[inline(always)]
550fn decrement_gil_count() {
551 let _ = GIL_COUNT.try_with(|c| {
553 let current = c.get();
554 debug_assert!(
555 current > 0,
556 "Negative GIL count detected. Please report this error to the PyO3 repo as a bug."
557 );
558 c.set(current - 1);
559 });
560}
561
562#[cfg(test)]
563mod tests {
564 use super::GIL_COUNT;
565 #[cfg(feature = "gil-refs")]
566 #[allow(deprecated)]
567 use super::OWNED_OBJECTS;
568 #[cfg(not(pyo3_disable_reference_pool))]
569 use super::{gil_is_acquired, POOL};
570 #[cfg(feature = "gil-refs")]
571 use crate::{ffi, gil};
572 use crate::{gil::GILGuard, types::any::PyAnyMethods};
573 use crate::{PyObject, Python};
574 use std::ptr::NonNull;
575
576 fn get_object(py: Python<'_>) -> PyObject {
577 py.eval_bound("object()", None, None).unwrap().unbind()
578 }
579
580 #[cfg(feature = "gil-refs")]
581 fn owned_object_count() -> usize {
582 #[cfg(debug_assertions)]
583 let len = OWNED_OBJECTS.with(|owned_objects| owned_objects.borrow().len());
584 #[cfg(not(debug_assertions))]
585 let len = OWNED_OBJECTS.with(|owned_objects| unsafe { (*owned_objects.get()).len() });
586 len
587 }
588
589 #[cfg(not(pyo3_disable_reference_pool))]
590 fn pool_dec_refs_does_not_contain(obj: &PyObject) -> bool {
591 !POOL
592 .pending_decrefs
593 .lock()
594 .unwrap()
595 .contains(&unsafe { NonNull::new_unchecked(obj.as_ptr()) })
596 }
597
598 #[cfg(not(pyo3_disable_reference_pool))]
599 fn pool_dec_refs_contains(obj: &PyObject) -> bool {
600 POOL.pending_decrefs
601 .lock()
602 .unwrap()
603 .contains(&unsafe { NonNull::new_unchecked(obj.as_ptr()) })
604 }
605
606 #[test]
607 #[cfg(feature = "gil-refs")]
608 #[allow(deprecated)]
609 fn test_owned() {
610 Python::with_gil(|py| {
611 let obj = get_object(py);
612 let obj_ptr = obj.as_ptr();
613 let _ref = obj.clone_ref(py);
615
616 unsafe {
617 {
618 let pool = py.new_pool();
619 gil::register_owned(pool.python(), NonNull::new_unchecked(obj.into_ptr()));
620
621 assert_eq!(owned_object_count(), 1);
622 assert_eq!(ffi::Py_REFCNT(obj_ptr), 2);
623 }
624 {
625 let _pool = py.new_pool();
626 assert_eq!(owned_object_count(), 0);
627 assert_eq!(ffi::Py_REFCNT(obj_ptr), 1);
628 }
629 }
630 })
631 }
632
633 #[test]
634 #[cfg(feature = "gil-refs")]
635 #[allow(deprecated)]
636 fn test_owned_nested() {
637 Python::with_gil(|py| {
638 let obj = get_object(py);
639 let _ref = obj.clone_ref(py);
641 let obj_ptr = obj.as_ptr();
642
643 unsafe {
644 {
645 let _pool = py.new_pool();
646 assert_eq!(owned_object_count(), 0);
647
648 gil::register_owned(py, NonNull::new_unchecked(obj.into_ptr()));
649
650 assert_eq!(owned_object_count(), 1);
651 assert_eq!(ffi::Py_REFCNT(obj_ptr), 2);
652 {
653 let _pool = py.new_pool();
654 let obj = get_object(py);
655 gil::register_owned(py, NonNull::new_unchecked(obj.into_ptr()));
656 assert_eq!(owned_object_count(), 2);
657 }
658 assert_eq!(owned_object_count(), 1);
659 }
660 {
661 assert_eq!(owned_object_count(), 0);
662 assert_eq!(ffi::Py_REFCNT(obj_ptr), 1);
663 }
664 }
665 });
666 }
667
668 #[test]
669 fn test_pyobject_drop_with_gil_decreases_refcnt() {
670 Python::with_gil(|py| {
671 let obj = get_object(py);
672
673 let reference = obj.clone_ref(py);
675
676 assert_eq!(obj.get_refcnt(py), 2);
677 #[cfg(not(pyo3_disable_reference_pool))]
678 assert!(pool_dec_refs_does_not_contain(&obj));
679
680 drop(reference);
682
683 assert_eq!(obj.get_refcnt(py), 1);
684 #[cfg(not(pyo3_disable_reference_pool))]
685 assert!(pool_dec_refs_does_not_contain(&obj));
686 });
687 }
688
689 #[test]
690 #[cfg(all(not(pyo3_disable_reference_pool), not(target_arch = "wasm32")))] fn test_pyobject_drop_without_gil_doesnt_decrease_refcnt() {
692 let obj = Python::with_gil(|py| {
693 let obj = get_object(py);
694 let reference = obj.clone_ref(py);
696
697 assert_eq!(obj.get_refcnt(py), 2);
698 assert!(pool_dec_refs_does_not_contain(&obj));
699
700 std::thread::spawn(move || drop(reference)).join().unwrap();
702
703 assert_eq!(obj.get_refcnt(py), 2);
706 assert!(pool_dec_refs_contains(&obj));
707 obj
708 });
709
710 Python::with_gil(|py| {
712 assert_eq!(obj.get_refcnt(py), 1);
713 assert!(pool_dec_refs_does_not_contain(&obj));
714 });
715 }
716
717 #[test]
718 #[allow(deprecated)]
719 fn test_gil_counts() {
720 let get_gil_count = || GIL_COUNT.with(|c| c.get());
722
723 assert_eq!(get_gil_count(), 0);
724 Python::with_gil(|_| {
725 assert_eq!(get_gil_count(), 1);
726
727 let pool = unsafe { GILGuard::assume() };
728 assert_eq!(get_gil_count(), 2);
729
730 let pool2 = unsafe { GILGuard::assume() };
731 assert_eq!(get_gil_count(), 3);
732
733 drop(pool);
734 assert_eq!(get_gil_count(), 2);
735
736 Python::with_gil(|_| {
737 assert_eq!(get_gil_count(), 3);
739 });
740 assert_eq!(get_gil_count(), 2);
741
742 drop(pool2);
743 assert_eq!(get_gil_count(), 1);
744 });
745 assert_eq!(get_gil_count(), 0);
746 }
747
748 #[test]
749 fn test_allow_threads() {
750 assert!(!gil_is_acquired());
751
752 Python::with_gil(|py| {
753 assert!(gil_is_acquired());
754
755 py.allow_threads(move || {
756 assert!(!gil_is_acquired());
757
758 Python::with_gil(|_| assert!(gil_is_acquired()));
759
760 assert!(!gil_is_acquired());
761 });
762
763 assert!(gil_is_acquired());
764 });
765
766 assert!(!gil_is_acquired());
767 }
768
769 #[cfg(feature = "py-clone")]
770 #[test]
771 #[should_panic]
772 fn test_allow_threads_updates_refcounts() {
773 Python::with_gil(|py| {
774 let obj = get_object(py);
776 assert!(obj.get_refcnt(py) == 1);
777 py.allow_threads(|| obj.clone());
779 });
780 }
781
782 #[test]
783 fn dropping_gil_does_not_invalidate_references() {
784 Python::with_gil(|py| {
786 let obj = Python::with_gil(|_| py.eval_bound("object()", None, None).unwrap());
787
788 assert_eq!(obj.get_refcnt(), 1);
790 })
791 }
792
793 #[cfg(feature = "py-clone")]
794 #[test]
795 fn test_clone_with_gil() {
796 Python::with_gil(|py| {
797 let obj = get_object(py);
798 let count = obj.get_refcnt(py);
799
800 #[allow(clippy::redundant_clone)]
802 let c = obj.clone();
803 assert_eq!(count + 1, c.get_refcnt(py));
804 })
805 }
806
807 #[test]
808 #[cfg(not(pyo3_disable_reference_pool))]
809 fn test_update_counts_does_not_deadlock() {
810 use crate::ffi;
814 use crate::gil::GILGuard;
815
816 Python::with_gil(|py| {
817 let obj = get_object(py);
818
819 unsafe extern "C" fn capsule_drop(capsule: *mut ffi::PyObject) {
820 let pool = GILGuard::assume();
823
824 PyObject::from_owned_ptr(
826 pool.python(),
827 ffi::PyCapsule_GetPointer(capsule, std::ptr::null()) as _,
828 );
829 }
830
831 let ptr = obj.into_ptr();
832
833 let capsule =
834 unsafe { ffi::PyCapsule_New(ptr as _, std::ptr::null(), Some(capsule_drop)) };
835
836 POOL.register_decref(NonNull::new(capsule).unwrap());
837
838 POOL.update_counts(py);
840 })
841 }
842
843 #[test]
844 #[cfg(not(pyo3_disable_reference_pool))]
845 fn test_gil_guard_update_counts() {
846 use crate::gil::GILGuard;
847
848 Python::with_gil(|py| {
849 let obj = get_object(py);
850
851 POOL.register_decref(NonNull::new(obj.clone_ref(py).into_ptr()).unwrap());
854 assert!(pool_dec_refs_contains(&obj));
855 let _guard = GILGuard::acquire();
856 assert!(pool_dec_refs_does_not_contain(&obj));
857
858 POOL.register_decref(NonNull::new(obj.clone_ref(py).into_ptr()).unwrap());
861 assert!(pool_dec_refs_contains(&obj));
862 let _guard2 = unsafe { GILGuard::assume() };
863 assert!(pool_dec_refs_does_not_contain(&obj));
864 })
865 }
866}