diff --git a/newsfragments/2975.added.md b/newsfragments/2975.added.md new file mode 100644 index 00000000000..7a3da624f2a --- /dev/null +++ b/newsfragments/2975.added.md @@ -0,0 +1 @@ +Add `GILProtected` to mediate concurrent access to a value using Python's global interpreter lock (GIL). diff --git a/src/gil.rs b/src/gil.rs index 678f12eecad..b898800856b 100644 --- a/src/gil.rs +++ b/src/gil.rs @@ -494,6 +494,29 @@ impl EnsureGIL { } } +/// Value with concurrent access protected by the GIL. +/// +/// This is similar to [`Mutex`][std::sync::Mutex] but the lock used is Python's global interpreter lock (GIL). +pub struct GILProtected { + value: T, +} + +impl GILProtected { + /// Place the given value under the protection of the GIL. + pub const fn new(value: T) -> Self { + Self { value } + } + + /// Gain access to the inner value by giving proof of having acquired the GIL. + pub fn with_gil<'py>(&'py self, _py: Python<'py>) -> &'py T { + &self.value + } +} + +unsafe impl Send for GILProtected where T: Send {} + +unsafe impl Sync for GILProtected where T: Send {} + #[cfg(test)] mod tests { use super::{gil_is_acquired, GILPool, GIL_COUNT, OWNED_OBJECTS, POOL}; diff --git a/src/impl_/pyclass/lazy_type_object.rs b/src/impl_/pyclass/lazy_type_object.rs index 8990bba3717..d7eadc02696 100644 --- a/src/impl_/pyclass/lazy_type_object.rs +++ b/src/impl_/pyclass/lazy_type_object.rs @@ -1,16 +1,15 @@ use std::{ borrow::Cow, + cell::RefCell, ffi::CStr, marker::PhantomData, thread::{self, ThreadId}, }; -use parking_lot::{const_mutex, Mutex}; - use crate::{ - exceptions::PyRuntimeError, ffi, once_cell::GILOnceCell, pyclass::create_type_object, - types::PyType, AsPyPointer, IntoPyPointer, Py, PyClass, PyErr, PyMethodDefType, PyObject, - PyResult, Python, + exceptions::PyRuntimeError, ffi, gil::GILProtected, once_cell::GILOnceCell, + pyclass::create_type_object, types::PyType, AsPyPointer, IntoPyPointer, Py, PyClass, PyErr, + PyMethodDefType, PyObject, PyResult, Python, }; use super::PyClassItemsIter; @@ -24,7 +23,7 @@ struct LazyTypeObjectInner { value: GILOnceCell>, // Threads which have begun initialization of the `tp_dict`. Used for // reentrant initialization detection. - initializing_threads: Mutex>, + initializing_threads: GILProtected>>, tp_dict_filled: GILOnceCell<()>, } @@ -34,7 +33,7 @@ impl LazyTypeObject { LazyTypeObject( LazyTypeObjectInner { value: GILOnceCell::new(), - initializing_threads: const_mutex(Vec::new()), + initializing_threads: GILProtected::new(RefCell::new(Vec::new())), tp_dict_filled: GILOnceCell::new(), }, PhantomData, @@ -109,7 +108,7 @@ impl LazyTypeObjectInner { let thread_id = thread::current().id(); { - let mut threads = self.initializing_threads.lock(); + let mut threads = self.initializing_threads.with_gil(py).borrow_mut(); if threads.contains(&thread_id) { // Reentrant call: just return the type object, even if the // `tp_dict` is not filled yet. @@ -119,18 +118,20 @@ impl LazyTypeObjectInner { } struct InitializationGuard<'a> { - initializing_threads: &'a Mutex>, + initializing_threads: &'a GILProtected>>, + py: Python<'a>, thread_id: ThreadId, } impl Drop for InitializationGuard<'_> { fn drop(&mut self) { - let mut threads = self.initializing_threads.lock(); + let mut threads = self.initializing_threads.with_gil(self.py).borrow_mut(); threads.retain(|id| *id != self.thread_id); } } let guard = InitializationGuard { initializing_threads: &self.initializing_threads, + py, thread_id, }; @@ -170,7 +171,7 @@ impl LazyTypeObjectInner { // Initialization successfully complete, can clear the thread list. // (No further calls to get_or_init() will try to init, on any thread.) std::mem::forget(guard); - *self.initializing_threads.lock() = Vec::new(); + self.initializing_threads.with_gil(py).replace(Vec::new()); result }); diff --git a/src/lib.rs b/src/lib.rs index 940f061e6b3..cfa08a6df3c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -304,7 +304,7 @@ pub use crate::conversion::{ pub use crate::err::{PyDowncastError, PyErr, PyErrArguments, PyResult}; #[cfg(not(PyPy))] pub use crate::gil::{prepare_freethreaded_python, with_embedded_python_interpreter}; -pub use crate::gil::{GILGuard, GILPool}; +pub use crate::gil::{GILGuard, GILPool, GILProtected}; pub use crate::instance::{Py, PyNativeType, PyObject}; pub use crate::marker::Python; pub use crate::pycell::{PyCell, PyRef, PyRefMut}; diff --git a/src/pyclass/create_type_object.rs b/src/pyclass/create_type_object.rs index 2409a0f1ff2..882f3a5dbe0 100644 --- a/src/pyclass/create_type_object.rs +++ b/src/pyclass/create_type_object.rs @@ -335,9 +335,10 @@ impl PyTypeBuilder { unsafe { self.push_slot(ffi::Py_tp_new, no_constructor_defined as *mut c_void) } } - if !self.has_dealloc { - panic!("PyTypeBuilder requires you to specify slot ffi::Py_tp_dealloc"); - } + assert!( + self.has_dealloc, + "PyTypeBuilder requires you to specify slot ffi::Py_tp_dealloc" + ); if self.has_clear && !self.has_traverse { return Err(PyTypeError::new_err(format!(