From 82aed203156608c3af74f332ab2da46bec0aefdb Mon Sep 17 00:00:00 2001 From: person93 Date: Mon, 18 May 2026 19:04:44 -0400 Subject: [PATCH 01/19] use `Cell` instead of `Mutex` in `err_state.rs` --- src/err/err_state.rs | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/err/err_state.rs b/src/err/err_state.rs index 2efc43ed4f5..e5e0a432df1 100644 --- a/src/err/err_state.rs +++ b/src/err/err_state.rs @@ -2,11 +2,8 @@ #![allow(clippy::undocumented_unsafe_blocks)] use crate::platform::prelude::*; -use core::cell::UnsafeCell; -use std::{ - sync::{Mutex, Once}, - thread::ThreadId, -}; +use core::cell::{Cell, UnsafeCell}; +use std::{sync::Once, thread::ThreadId}; #[cfg(not(Py_3_12))] use crate::sync::MutexExt; @@ -23,7 +20,7 @@ pub(crate) struct PyErrState { // after normalization. normalized: Once, // Guard against re-entrancy when normalizing the exception state. - normalizing_thread: Mutex>, + normalizing_thread: Cell>, inner: UnsafeCell>, } @@ -68,7 +65,7 @@ impl PyErrState { fn from_inner(inner: PyErrStateInner) -> Self { Self { normalized: Once::new(), - normalizing_thread: Mutex::new(None), + normalizing_thread: Cell::new(None), inner: UnsafeCell::new(Some(inner)), } } @@ -96,9 +93,10 @@ impl PyErrState { // Guard against re-entrant normalization, because `Once` does not provide // re-entrancy guarantees. - if let Some(thread) = self.normalizing_thread.lock().unwrap().as_ref() { - assert!( - !(*thread == std::thread::current().id()), + if let Some(thread) = self.normalizing_thread.get() { + assert_ne!( + thread, + std::thread::current().id(), "Re-entrant normalization of PyErrState detected" ); } @@ -107,9 +105,7 @@ impl PyErrState { py.detach(|| { self.normalized.call_once(|| { self.normalizing_thread - .lock() - .unwrap() - .replace(std::thread::current().id()); + .set(Some(std::thread::current().id())); // Safety: no other thread can access the inner value while we are normalizing it. let state = unsafe { From 292a80aedb42aa2b601ad3007b8c288a27c29995 Mon Sep 17 00:00:00 2001 From: person93 Date: Mon, 18 May 2026 19:08:22 -0400 Subject: [PATCH 02/19] add platform impl of `Mutex` and `Once` --- src/lib.rs | 3 +- src/platform.rs | 2 + src/platform/sync.rs | 104 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 src/platform/sync.rs diff --git a/src/lib.rs b/src/lib.rs index b9fce6cf463..1bb0e5af6f3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -364,7 +364,8 @@ pub(crate) mod ffi_ptr_ext; pub(crate) mod py_result_ext; pub(crate) mod sealed; -mod platform; +#[doc(hidden)] +pub mod platform; /// Old module which contained some implementation details of the `#[pyproto]` module. /// diff --git a/src/platform.rs b/src/platform.rs index 924f818dc55..02c222b448b 100644 --- a/src/platform.rs +++ b/src/platform.rs @@ -14,6 +14,8 @@ pub(crate) mod prelude { pub use std::eprintln; } +pub mod sync; + #[cfg(feature = "hashbrown")] pub use hashbrown::{HashMap, HashSet}; diff --git a/src/platform/sync.rs b/src/platform/sync.rs new file mode 100644 index 00000000000..4f0966371f5 --- /dev/null +++ b/src/platform/sync.rs @@ -0,0 +1,104 @@ +// TODO compile_error if parking_lot and std are both disabled + +#[cfg(feature = "parking_lot")] +type OnceInner = parking_lot::Once; + +#[cfg(not(feature = "parking_lot"))] +type OnceInner = std::sync::Once; + +pub struct Once(OnceInner); + +// #[cfg(feature = "parking_lot")] +impl Once { + /// Creates a new `Once` value. + #[inline(always)] + #[must_use] + pub const fn new() -> Once { + Once(OnceInner::new()) + } + + #[inline] + pub fn call_once(&self, f: impl FnOnce()) { + self.0.call_once(f); + } + + #[inline] + pub fn call_once_force(&self, f: impl FnOnce()) { + self.0.call_once_force(move |_| f()); + } + + #[cfg(feature = "parking_lot")] + pub fn is_completed(&self) -> bool { + matches!(self.0.state(), parking_lot::OnceState::Done) + } + + #[cfg(not(feature = "parking_lot"))] + #[inline(always)] + pub fn is_completed(&self) -> bool { + self.0.is_completed() + } +} + +pub mod non_poison { + #[cfg(feature = "parking_lot")] + pub use parking_lot::{Mutex, MutexGuard}; + + #[cfg(not(feature = "parking_lot"))] + pub use std::sync::MutexGuard; + + #[cfg(not(feature = "parking_lot"))] + #[derive(Default, Debug)] + pub struct Mutex { + #[allow(clippy::disallowed_types)] + inner: std::sync::Mutex, + } + + #[cfg(not(feature = "parking_lot"))] + impl Mutex { + #[inline(always)] + pub const fn new(t: T) -> Mutex { + Mutex { + #[allow(clippy::disallowed_types)] + inner: std::sync::Mutex::new(t), + } + } + } + + #[cfg(not(feature = "parking_lot"))] + impl Mutex { + #[inline(always)] + pub fn lock(&self) -> MutexGuard<'_, T> { + self.inner.lock().unwrap_or_else(|e| e.into_inner()) + } + + // TODO try_lock + } + + #[cfg(not(feature = "parking_lot"))] + impl From for Mutex { + #[inline(always)] + fn from(t: T) -> Self { + Mutex { + #[allow(clippy::disallowed_types)] + inner: std::sync::Mutex::new(t), + } + } + } + + #[cfg(not(feature = "parking_lot"))] + impl crate::sealed::Sealed for Mutex {} + + #[cfg(not(feature = "parking_lot"))] + impl crate::sync::MutexExt for Mutex { + type LockResult<'a> + = MutexGuard<'a, T> + where + T: 'a; + + fn lock_py_attached(&self, py: crate::prelude::Python<'_>) -> Self::LockResult<'_> { + self.inner + .lock_py_attached(py) + .unwrap_or_else(|e| e.into_inner()) + } + } +} From fe932501dd049ea6af1485bcfd27bfc1c70c61d0 Mon Sep 17 00:00:00 2001 From: person93 Date: Mon, 18 May 2026 19:10:28 -0400 Subject: [PATCH 03/19] forbid `Mutex` from `std` --- clippy.toml | 3 +++ pyo3-macros-backend/src/pyclass.rs | 6 +++--- src/impl_/pyclass.rs | 7 ++++--- src/impl_/pyclass/lazy_type_object.rs | 9 ++++----- src/internal/state.rs | 9 +++++---- src/sealed.rs | 1 + src/sync.rs | 2 ++ src/types/bytearray.rs | 1 + tests/test_gc.rs | 11 ++++++----- tests/test_proto_methods.rs | 4 ++-- tests/test_utils/mod.rs | 11 ++++------- 11 files changed, 35 insertions(+), 29 deletions(-) create mode 100644 clippy.toml diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 00000000000..18ec15cb228 --- /dev/null +++ b/clippy.toml @@ -0,0 +1,3 @@ +[[disallowed-types]] +path = "std::sync::Mutex" +reason = "use crate::platform::sync::non_poison::Mutex for `no_std` compatibility" diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index ac92290fd1f..7eaa640c0c6 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -3075,9 +3075,9 @@ impl<'a> PyClassImplsBuilder<'a> { quote! { impl #pyo3_path::impl_::pyclass::PyClassWithFreeList for #cls { #[inline] - fn get_free_list(py: #pyo3_path::Python<'_>) -> &'static ::std::sync::Mutex<#pyo3_path::impl_::freelist::PyObjectFreeList> { - static FREELIST: #pyo3_path::sync::PyOnceLock<::std::sync::Mutex<#pyo3_path::impl_::freelist::PyObjectFreeList>> = #pyo3_path::sync::PyOnceLock::new(); - &FREELIST.get_or_init(py, || ::std::sync::Mutex::new(#pyo3_path::impl_::freelist::PyObjectFreeList::with_capacity(#freelist))) + fn get_free_list(py: #pyo3_path::Python<'_>) -> &'static #pyo3_path::platform::sync::non_poison::Mutex<#pyo3_path::impl_::freelist::PyObjectFreeList> { + static FREELIST: #pyo3_path::sync::PyOnceLock<#pyo3_path::platform::sync::non_poison::Mutex<#pyo3_path::impl_::freelist::PyObjectFreeList>> = #pyo3_path::sync::PyOnceLock::new(); + &FREELIST.get_or_init(py, || #pyo3_path::platform::sync::non_poison::Mutex::new(#pyo3_path::impl_::freelist::PyObjectFreeList::with_capacity(#freelist))) } } } diff --git a/src/impl_/pyclass.rs b/src/impl_/pyclass.rs index 273bf25c3be..1a48f464f97 100644 --- a/src/impl_/pyclass.rs +++ b/src/impl_/pyclass.rs @@ -3,6 +3,7 @@ #[allow(unused_imports, reason = "conditionally used")] use crate::platform::prelude::*; +use crate::platform::sync::non_poison::Mutex; use crate::{ exceptions::{PyAttributeError, PyNotImplementedError, PyRuntimeError}, ffi, @@ -24,7 +25,7 @@ use core::{ marker::PhantomData, ptr::{self, NonNull}, }; -use std::{sync::Mutex, thread}; +use std::thread; mod assertions; pub mod doc; @@ -951,7 +952,7 @@ pub unsafe extern "C" fn alloc_with_freelist( // If this type is a variable type or the subtype is not equal to this type, we cannot use the // freelist if nitems == 0 && ptr::eq(subtype, self_type) { - let mut free_list = T::get_free_list(py).lock().unwrap(); + let mut free_list = T::get_free_list(py).lock(); if let Some(obj) = free_list.pop() { drop(free_list); unsafe { ffi::PyObject_Init(obj.as_ptr(), subtype) }; @@ -976,7 +977,7 @@ pub unsafe extern "C" fn free_with_freelist(obj: *mut c_ T::type_object_raw(Python::assume_attached()), ffi::Py_TYPE(obj.as_ptr()) ); - let mut free_list = T::get_free_list(Python::assume_attached()).lock().unwrap(); + let mut free_list = T::get_free_list(Python::assume_attached()).lock(); if let Some(obj) = free_list.insert(obj) { drop(free_list); let ty = ffi::Py_TYPE(obj.as_ptr()); diff --git a/src/impl_/pyclass/lazy_type_object.rs b/src/impl_/pyclass/lazy_type_object.rs index 2142b1d4568..55f1663d8be 100644 --- a/src/impl_/pyclass/lazy_type_object.rs +++ b/src/impl_/pyclass/lazy_type_object.rs @@ -7,6 +7,7 @@ use std::thread::{self, ThreadId}; #[cfg(Py_3_14)] use crate::err::error_on_minusone; +use crate::platform::sync::non_poison::Mutex; #[allow(deprecated)] use crate::sync::GILOnceCell; #[cfg(Py_3_14)] @@ -20,8 +21,6 @@ use crate::{ Bound, Py, PyAny, PyClass, PyErr, PyResult, Python, }; -use std::sync::Mutex; - use super::PyClassItemsIter; /// Lazy type object for PyClass. @@ -136,7 +135,7 @@ impl LazyTypeObjectInner { let thread_id = thread::current().id(); { - let mut threads = self.initializing_threads.lock().unwrap(); + let mut threads = self.initializing_threads.lock(); if threads.contains(&thread_id) { // Reentrant call: just return the type object, even if the // `tp_dict` is not filled yet. @@ -151,7 +150,7 @@ impl LazyTypeObjectInner { } impl Drop for InitializationGuard<'_> { fn drop(&mut self) { - let mut threads = self.initializing_threads.lock().unwrap(); + let mut threads = self.initializing_threads.lock(); threads.retain(|id| *id != self.thread_id); } } @@ -218,7 +217,7 @@ impl LazyTypeObjectInner { // (No further calls to get_or_init() will try to init, on any thread.) let mut threads = { drop(guard); - self.initializing_threads.lock().unwrap() + self.initializing_threads.lock() }; threads.clear(); Ok(type_object.clone().unbind()) diff --git a/src/internal/state.rs b/src/internal/state.rs index 51dcb75a9f8..8b302052a07 100644 --- a/src/internal/state.rs +++ b/src/internal/state.rs @@ -8,11 +8,13 @@ use crate::impl_::panic::PanicTrap; use crate::platform::prelude::*; use crate::{ffi, Python}; +#[cfg(not(pyo3_disable_reference_pool))] +use crate::platform::sync::non_poison::Mutex; use core::cell::Cell; #[cfg_attr(pyo3_disable_reference_pool, allow(unused_imports))] use core::{mem, ptr::NonNull}; #[cfg(not(pyo3_disable_reference_pool))] -use std::sync::{Mutex, OnceLock}; +use std::sync::OnceLock; std::thread_local! { /// This is an internal counter in pyo3 monitoring whether this thread is attached to the interpreter. @@ -202,11 +204,11 @@ impl ReferencePool { } fn register_decref(&self, obj: NonNull) { - self.pending_decrefs.lock().unwrap().push(obj); + self.pending_decrefs.lock().push(obj); } fn drop_deferred_references(&self, _py: Python<'_>) { - let mut pending_decrefs = self.pending_decrefs.lock().unwrap(); + let mut pending_decrefs = self.pending_decrefs.lock(); if pending_decrefs.is_empty() { return; } @@ -382,7 +384,6 @@ mod tests { !get_pool() .pending_decrefs .lock() - .unwrap() .contains(&unsafe { NonNull::new_unchecked(obj.as_ptr()) }) } diff --git a/src/sealed.rs b/src/sealed.rs index 0155aa93349..30db27f585d 100644 --- a/src/sealed.rs +++ b/src/sealed.rs @@ -62,6 +62,7 @@ impl Sealed for PyNativeTypeInitializer {} impl Sealed for PyClassInitializer {} impl Sealed for std::sync::Once {} +#[allow(clippy::disallowed_types)] impl Sealed for std::sync::Mutex {} #[cfg(feature = "lock_api")] impl Sealed for lock_api::Mutex {} diff --git a/src/sync.rs b/src/sync.rs index 0ec45ff9c2b..12f180a0ac5 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -404,6 +404,7 @@ impl OnceLockExt for std::sync::OnceLock { } } +#[allow(clippy::disallowed_types)] impl MutexExt for std::sync::Mutex { type LockResult<'a> = std::sync::LockResult> @@ -723,6 +724,7 @@ mod rwlock_ext_sealed { impl Sealed for alloc::sync::Arc> {} } +#[allow(clippy::disallowed_types, reason = "tests")] #[cfg(test)] mod tests { use super::*; diff --git a/src/types/bytearray.rs b/src/types/bytearray.rs index 56ec3a82b29..bdd187fb03e 100644 --- a/src/types/bytearray.rs +++ b/src/types/bytearray.rs @@ -335,6 +335,7 @@ impl<'py> TryFrom<&Bound<'py, PyAny>> for Bound<'py, PyByteArray> { } } +#[allow(clippy::disallowed_types, reason = "tests")] #[cfg(test)] mod tests { use crate::types::{PyAnyMethods, PyByteArray, PyByteArrayMethods}; diff --git a/tests/test_gc.rs b/tests/test_gc.rs index 822168096a5..39be3810e51 100644 --- a/tests/test_gc.rs +++ b/tests/test_gc.rs @@ -5,14 +5,15 @@ use pyo3::class::PyTraverseError; use pyo3::class::PyVisit; use pyo3::ffi; +use pyo3::platform::sync::non_poison::Mutex; use pyo3::prelude::*; #[cfg(not(Py_GIL_DISABLED))] use pyo3::py_run; #[cfg(not(target_arch = "wasm32"))] use std::cell::Cell; use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; use std::sync::Once; -use std::sync::{Arc, Mutex}; mod test_utils; @@ -498,7 +499,7 @@ struct DropDuringTraversal { impl DropDuringTraversal { #[expect(clippy::unnecessary_wraps)] fn __traverse__(&self, _visit: PyVisit<'_>) -> Result<(), PyTraverseError> { - let mut cycle_ref = self.cycle.lock().unwrap(); + let mut cycle_ref = self.cycle.lock(); *cycle_ref = None; Ok(()) } @@ -520,7 +521,7 @@ fn drop_during_traversal_with_gil() { ) .unwrap(); - *inst.borrow_mut(py).cycle.lock().unwrap() = Some(inst.clone_ref(py)); + *inst.borrow_mut(py).cycle.lock() = Some(inst.clone_ref(py)); check.assert_not_dropped(); let ptr = inst.as_ptr(); @@ -554,7 +555,7 @@ fn drop_during_traversal_without_gil() { ) .unwrap(); - *inst.borrow_mut(py).cycle.lock().unwrap() = Some(inst.clone_ref(py)); + *inst.borrow_mut(py).cycle.lock() = Some(inst.clone_ref(py)); check.assert_not_dropped(); inst @@ -756,7 +757,7 @@ fn test_drop_buffer_during_traversal_without_gil() { impl BufferDropDuringTraversal { #[expect(clippy::unnecessary_wraps)] fn __traverse__(&self, _visit: PyVisit<'_>) -> Result<(), PyTraverseError> { - self.inner.lock().unwrap().take(); + self.inner.lock().take(); Ok(()) } diff --git a/tests/test_proto_methods.rs b/tests/test_proto_methods.rs index c172d954a7a..20468787b90 100644 --- a/tests/test_proto_methods.rs +++ b/tests/test_proto_methods.rs @@ -1,10 +1,10 @@ #![cfg(feature = "macros")] use pyo3::exceptions::{PyAttributeError, PyIndexError, PyValueError}; +use pyo3::platform::sync::non_poison::Mutex; use pyo3::types::{PyDict, PyList, PyMapping, PySequence, PySlice, PyType}; use pyo3::{prelude::*, py_run}; use std::iter; -use std::sync::Mutex; mod test_utils; @@ -429,7 +429,7 @@ impl Iterator { } fn __next__(slf: PyRefMut<'_, Self>) -> Option { - slf.iter.lock().unwrap().next() + slf.iter.lock().next() } } diff --git a/tests/test_utils/mod.rs b/tests/test_utils/mod.rs index 5a4605cbb07..94df14a0149 100644 --- a/tests/test_utils/mod.rs +++ b/tests/test_utils/mod.rs @@ -33,7 +33,7 @@ mod inner { use pyo3::types::{IntoPyDict, PyList}; #[cfg(any(not(all(Py_GIL_DISABLED, Py_3_14)), feature = "macros"))] - use std::sync::{Mutex, PoisonError}; + use pyo3::platform::sync::non_poison::Mutex; use uuid::Uuid; @@ -150,10 +150,7 @@ mod inner { // unraisablehook is a global, so only one thread can be using this struct at a time. static UNRAISABLE_HOOK_MUTEX: Mutex<()> = Mutex::new(()); - // NB this is best-effort, other tests could always modify sys.unraisablehook directly. - let mutex_guard = UNRAISABLE_HOOK_MUTEX - .lock_py_attached(py) - .unwrap_or_else(PoisonError::into_inner); + let mutex_guard = UNRAISABLE_HOOK_MUTEX.lock_py_attached(py); let guard = Self { hook: UnraisableCaptureHook::install(py), @@ -169,7 +166,7 @@ mod inner { /// Takes the captured unraisable error, if any. pub fn take_capture(&self) -> Option<(PyErr, Bound<'py, PyAny>)> { - let mut guard = self.hook.get().capture.lock().unwrap(); + let mut guard = self.hook.get().capture.lock(); guard.take().map(|(e, o)| (e, o.into_bound(self.hook.py()))) } } @@ -195,7 +192,7 @@ mod inner { pub fn hook(&self, unraisable: Bound<'_, PyAny>) { let err = PyErr::from_value(unraisable.getattr("exc_value").unwrap()); let instance = unraisable.getattr("object").unwrap(); - self.capture.lock().unwrap().replace((err, instance.into())); + self.capture.lock().replace((err, instance.into())); } } From c6f96ef0c44c5cf4937bc83fbe7e4b7cf8db06b7 Mon Sep 17 00:00:00 2001 From: person93 Date: Mon, 18 May 2026 19:54:40 -0400 Subject: [PATCH 04/19] replace `std::sync::Once` with `platform::sync::Once` --- src/err/err_state.rs | 3 ++- src/interpreter_lifecycle.rs | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/err/err_state.rs b/src/err/err_state.rs index e5e0a432df1..e1ffb973a90 100644 --- a/src/err/err_state.rs +++ b/src/err/err_state.rs @@ -2,8 +2,9 @@ #![allow(clippy::undocumented_unsafe_blocks)] use crate::platform::prelude::*; +use crate::platform::sync::Once; use core::cell::{Cell, UnsafeCell}; -use std::{sync::Once, thread::ThreadId}; +use std::thread::ThreadId; #[cfg(not(Py_3_12))] use crate::sync::MutexExt; diff --git a/src/interpreter_lifecycle.rs b/src/interpreter_lifecycle.rs index f596da343cf..41ea5d2ebf4 100644 --- a/src/interpreter_lifecycle.rs +++ b/src/interpreter_lifecycle.rs @@ -1,17 +1,19 @@ // TODO https://github.com/PyO3/pyo3/issues/5487 #![allow(clippy::undocumented_unsafe_blocks)] +use crate::platform::sync::Once; + #[cfg(not(any(PyPy, GraalPy)))] use crate::{ffi, internal::state::AttachGuard, Python}; -static START: std::sync::Once = std::sync::Once::new(); +static START: Once = Once::new(); #[cfg(not(any(PyPy, GraalPy)))] pub(crate) fn initialize() { // Protect against race conditions when Python is not yet initialized and multiple threads // concurrently call 'initialize()'. Note that we do not protect against // concurrent initialization of the Python runtime by other users of the Python C API. - START.call_once_force(|_| unsafe { + START.call_once_force(|| unsafe { // Use call_once_force because if initialization panics, it's okay to try again. if ffi::Py_IsInitialized() == 0 { ffi::Py_InitializeEx(0); From e8d3e07ddd017b6a76f847e5c315fb291ebe4f18 Mon Sep 17 00:00:00 2001 From: person93 Date: Mon, 18 May 2026 20:15:06 -0400 Subject: [PATCH 05/19] fix conditional compile errors caused by `Mutex` and `Once` --- src/err/err_state.rs | 2 +- src/internal/state.rs | 1 - src/interpreter_lifecycle.rs | 2 +- tests/test_utils/mod.rs | 4 +--- 4 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/err/err_state.rs b/src/err/err_state.rs index e1ffb973a90..15f8bb1a037 100644 --- a/src/err/err_state.rs +++ b/src/err/err_state.rs @@ -288,7 +288,7 @@ impl PyErrStateNormalized { ptype: self.ptype.clone_ref(py), pvalue: self.pvalue.clone_ref(py), #[cfg(not(Py_3_12))] - ptraceback: std::sync::Mutex::new( + ptraceback: Mutex::new( self.ptraceback .lock_py_attached(py) .unwrap() diff --git a/src/internal/state.rs b/src/internal/state.rs index 8b302052a07..7340d1bfbb2 100644 --- a/src/internal/state.rs +++ b/src/internal/state.rs @@ -394,7 +394,6 @@ mod tests { get_pool() .pending_decrefs .lock() - .unwrap() .contains(&unsafe { NonNull::new_unchecked(obj.as_ptr()) }) } diff --git a/src/interpreter_lifecycle.rs b/src/interpreter_lifecycle.rs index 41ea5d2ebf4..77223142c97 100644 --- a/src/interpreter_lifecycle.rs +++ b/src/interpreter_lifecycle.rs @@ -129,7 +129,7 @@ pub(crate) fn ensure_initialized() { initialize(); } - START.call_once_force(|_| unsafe { + START.call_once_force(|| unsafe { // Use call_once_force because if there is a panic because the interpreter is // not initialized, it's fine for the user to initialize the interpreter and // retry. diff --git a/tests/test_utils/mod.rs b/tests/test_utils/mod.rs index 94df14a0149..5b8fa0e90cc 100644 --- a/tests/test_utils/mod.rs +++ b/tests/test_utils/mod.rs @@ -234,9 +234,7 @@ mod inner { ) -> PyResult { // NB this is best-effort, other tests could always call the warnings API directly. #[cfg(not(all(Py_GIL_DISABLED, Py_3_14)))] - let _mutex_guard = CATCH_WARNINGS_MUTEX - .lock_py_attached(py) - .unwrap_or_else(PoisonError::into_inner); + let _mutex_guard = CATCH_WARNINGS_MUTEX.lock_py_attached(py); let warnings = py.import("warnings")?; let kwargs = [("record", true)].into_py_dict(py)?; let catch_warnings = warnings From 144773543887413f7b9a23cec6b47b85b065ddb2 Mon Sep 17 00:00:00 2001 From: person93 Date: Mon, 18 May 2026 20:22:25 -0400 Subject: [PATCH 06/19] add default impl for `Once` --- src/platform/sync.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/platform/sync.rs b/src/platform/sync.rs index 4f0966371f5..e30599ba279 100644 --- a/src/platform/sync.rs +++ b/src/platform/sync.rs @@ -8,6 +8,12 @@ type OnceInner = std::sync::Once; pub struct Once(OnceInner); +impl Default for Once { + fn default() -> Self { + Self::new() + } +} + // #[cfg(feature = "parking_lot")] impl Once { /// Creates a new `Once` value. From c9d25b6875e9f2c7924e00da426bcf40c2d2d243 Mon Sep 17 00:00:00 2001 From: person93 Date: Mon, 18 May 2026 21:02:40 -0400 Subject: [PATCH 07/19] use platform Mutex in coroutine --- src/coroutine/cancel.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/coroutine/cancel.rs b/src/coroutine/cancel.rs index 8607076b9e5..a345997448f 100644 --- a/src/coroutine/cancel.rs +++ b/src/coroutine/cancel.rs @@ -1,8 +1,8 @@ +use crate::platform::sync::non_poison::Mutex; use crate::{Py, PyAny}; use alloc::sync::Arc; use core::future::poll_fn; use core::task::{Context, Poll, Waker}; -use std::sync::Mutex; #[derive(Debug, Default)] struct Inner { @@ -24,12 +24,12 @@ impl CancelHandle { /// Returns whether the associated coroutine has been cancelled. pub fn is_cancelled(&self) -> bool { - self.0.lock().unwrap().exception.is_some() + self.0.lock().exception.is_some() } /// Poll to retrieve the exception thrown in the associated coroutine. pub fn poll_cancelled(&mut self, cx: &mut Context<'_>) -> Poll> { - let mut inner = self.0.lock().unwrap(); + let mut inner = self.0.lock(); if let Some(exc) = inner.exception.take() { return Poll::Ready(exc); } @@ -58,7 +58,7 @@ pub struct ThrowCallback(Arc>); impl ThrowCallback { pub(super) fn throw(&self, exc: Py) { - let mut inner = self.0.lock().unwrap(); + let mut inner = self.0.lock(); inner.exception = Some(exc); if let Some(waker) = inner.waker.take() { waker.wake(); From d084f227d3bbd203c25c26cce6be357d4af56d56 Mon Sep 17 00:00:00 2001 From: person93 Date: Mon, 18 May 2026 21:16:15 -0400 Subject: [PATCH 08/19] forbid `std::sync::Once` --- clippy.toml | 4 ++++ src/platform/sync.rs | 21 +++++++++++++++++++++ src/sealed.rs | 1 + src/sync.rs | 20 ++++++++++++-------- tests/test_gc.rs | 3 +-- 5 files changed, 39 insertions(+), 10 deletions(-) diff --git a/clippy.toml b/clippy.toml index 18ec15cb228..c9f313f2603 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1,3 +1,7 @@ [[disallowed-types]] path = "std::sync::Mutex" reason = "use crate::platform::sync::non_poison::Mutex for `no_std` compatibility" + +[[disallowed-types]] +path = "std::sync::Once" +reason = "use crate::platform::sync::Once for no_std compatibility" diff --git a/src/platform/sync.rs b/src/platform/sync.rs index e30599ba279..4dc4dc87c4c 100644 --- a/src/platform/sync.rs +++ b/src/platform/sync.rs @@ -1,9 +1,13 @@ // TODO compile_error if parking_lot and std are both disabled +use crate::sealed; +use crate::sync::OnceExt; + #[cfg(feature = "parking_lot")] type OnceInner = parking_lot::Once; #[cfg(not(feature = "parking_lot"))] +#[allow(clippy::disallowed_types)] type OnceInner = std::sync::Once; pub struct Once(OnceInner); @@ -45,6 +49,23 @@ impl Once { } } +impl sealed::Sealed for Once {} +impl OnceExt for Once { + type OnceState = (); + + fn call_once_py_attached(&self, py: crate::prelude::Python<'_>, f: impl FnOnce()) { + self.0.call_once_force_py_attached(py, move |_| f()); + } + + fn call_once_force_py_attached( + &self, + py: crate::prelude::Python<'_>, + f: impl FnOnce(&Self::OnceState), + ) { + self.0.call_once_force_py_attached(py, |_| f(&())); + } +} + pub mod non_poison { #[cfg(feature = "parking_lot")] pub use parking_lot::{Mutex, MutexGuard}; diff --git a/src/sealed.rs b/src/sealed.rs index 30db27f585d..f6d15d946d9 100644 --- a/src/sealed.rs +++ b/src/sealed.rs @@ -61,6 +61,7 @@ impl Sealed for ModuleDef {} impl Sealed for PyNativeTypeInitializer {} impl Sealed for PyClassInitializer {} +#[allow(clippy::disallowed_types)] impl Sealed for std::sync::Once {} #[allow(clippy::disallowed_types)] impl Sealed for std::sync::Mutex {} diff --git a/src/sync.rs b/src/sync.rs index 12f180a0ac5..ef2b7e9e6a7 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -12,6 +12,7 @@ //! interpreter. //! //! This module provides synchronization primitives which are able to synchronize under these conditions. +use crate::platform::sync::Once; use crate::{ internal::state::SuspendAttach, sealed::Sealed, @@ -19,7 +20,6 @@ use crate::{ Bound, Py, Python, }; use core::{cell::UnsafeCell, marker::PhantomData, mem::MaybeUninit}; -use std::sync::{Once, OnceState}; pub mod critical_section; pub(crate) mod once_lock; @@ -163,7 +163,7 @@ impl GILOnceCell { // NB this can block, but since this is only writing a single value and // does not call arbitrary python code, we don't need to worry about // deadlocks with the GIL. - self.once.call_once_force(|_| { + self.once.call_once_force(|| { // SAFETY: no other threads can be writing this value, because we are // inside the `call_once_force` closure. unsafe { @@ -338,8 +338,9 @@ pub trait RwLockExt: rwlock_ext_sealed::Sealed { fn write_py_attached(&self, py: Python<'_>) -> Self::WriteLockResult<'_>; } -impl OnceExt for Once { - type OnceState = OnceState; +#[allow(clippy::disallowed_types)] +impl OnceExt for std::sync::Once { + type OnceState = std::sync::OnceState; fn call_once_py_attached(&self, py: Python<'_>, f: impl FnOnce()) { if self.is_completed() { @@ -349,7 +350,7 @@ impl OnceExt for Once { init_once_py_attached(self, py, f) } - fn call_once_force_py_attached(&self, py: Python<'_>, f: impl FnOnce(&OnceState)) { + fn call_once_force_py_attached(&self, py: Python<'_>, f: impl FnOnce(&std::sync::OnceState)) { if self.is_completed() { return; } @@ -655,7 +656,8 @@ where } #[cold] -fn init_once_py_attached(once: &Once, _py: Python<'_>, f: F) +#[allow(clippy::disallowed_types)] +fn init_once_py_attached(once: &std::sync::Once, _py: Python<'_>, f: F) where F: FnOnce() -> T, { @@ -671,9 +673,10 @@ where } #[cold] -fn init_once_force_py_attached(once: &Once, _py: Python<'_>, f: F) +#[allow(clippy::disallowed_types)] +fn init_once_force_py_attached(once: &std::sync::Once, _py: Python<'_>, f: F) where - F: FnOnce(&OnceState) -> T, + F: FnOnce(&std::sync::OnceState) -> T, { // SAFETY: detach from the runtime right before a possibly blocking call // then reattach when the blocking call completes and before calling @@ -738,6 +741,7 @@ mod tests { use std::sync::Barrier; #[cfg(not(target_arch = "wasm32"))] use std::sync::Mutex; + use std::sync::{Once, OnceState}; #[cfg(not(target_arch = "wasm32"))] #[cfg(feature = "macros")] diff --git a/tests/test_gc.rs b/tests/test_gc.rs index 39be3810e51..d471d962e33 100644 --- a/tests/test_gc.rs +++ b/tests/test_gc.rs @@ -5,7 +5,7 @@ use pyo3::class::PyTraverseError; use pyo3::class::PyVisit; use pyo3::ffi; -use pyo3::platform::sync::non_poison::Mutex; +use pyo3::platform::sync::{non_poison::Mutex, Once}; use pyo3::prelude::*; #[cfg(not(Py_GIL_DISABLED))] use pyo3::py_run; @@ -13,7 +13,6 @@ use pyo3::py_run; use std::cell::Cell; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; -use std::sync::Once; mod test_utils; From 6482bbe18646bef78ff6b0aa1db53600a625036b Mon Sep 17 00:00:00 2001 From: person93 Date: Mon, 18 May 2026 21:27:35 -0400 Subject: [PATCH 09/19] fix more condtional comilation errors --- src/sync.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sync.rs b/src/sync.rs index ef2b7e9e6a7..bb17cc959ca 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -741,6 +741,7 @@ mod tests { use std::sync::Barrier; #[cfg(not(target_arch = "wasm32"))] use std::sync::Mutex; + #[cfg(not(target_arch = "wasm32"))] use std::sync::{Once, OnceState}; #[cfg(not(target_arch = "wasm32"))] From b91a4fb0841d31d7b8b668e97b6c5cff71ba4363 Mon Sep 17 00:00:00 2001 From: person93 Date: Mon, 18 May 2026 22:08:21 -0400 Subject: [PATCH 10/19] add `into_inner` method to Mutex in platform mod --- src/platform/sync.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/platform/sync.rs b/src/platform/sync.rs index 4dc4dc87c4c..bd8490770ff 100644 --- a/src/platform/sync.rs +++ b/src/platform/sync.rs @@ -89,6 +89,17 @@ pub mod non_poison { inner: std::sync::Mutex::new(t), } } + + #[cfg(feature = "parking_lot")] + #[inline(always)] + pub fn into_inner(self) -> T { + self.inner.into_inner() + } + + #[cfg(not(feature = "parking_lot"))] + pub fn into_inner(self) -> T { + self.inner.into_inner().unwrap_or_else(|e| e.into_inner()) + } } #[cfg(not(feature = "parking_lot"))] From 647a143fb7c0056dbaad2349af7434d4a982ec76 Mon Sep 17 00:00:00 2001 From: person93 Date: Tue, 26 May 2026 13:49:22 -0400 Subject: [PATCH 11/19] assert that `CancelHandle` is unwind-safe --- src/coroutine/cancel.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/coroutine/cancel.rs b/src/coroutine/cancel.rs index a345997448f..98c10e4961d 100644 --- a/src/coroutine/cancel.rs +++ b/src/coroutine/cancel.rs @@ -2,6 +2,7 @@ use crate::platform::sync::non_poison::Mutex; use crate::{Py, PyAny}; use alloc::sync::Arc; use core::future::poll_fn; +use core::panic::AssertUnwindSafe; use core::task::{Context, Poll, Waker}; #[derive(Debug, Default)] @@ -14,7 +15,7 @@ struct Inner { /// /// Only the last exception thrown can be retrieved. #[derive(Debug, Default)] -pub struct CancelHandle(Arc>); +pub struct CancelHandle(Arc>>); impl CancelHandle { /// Create a new `CoroutineCancel`. @@ -54,7 +55,7 @@ impl CancelHandle { } #[doc(hidden)] -pub struct ThrowCallback(Arc>); +pub struct ThrowCallback(Arc>>); impl ThrowCallback { pub(super) fn throw(&self, exc: Py) { From b73f65becbe22ed0a2bc0d30c26a6f8952f98803 Mon Sep 17 00:00:00 2001 From: person93 Date: Mon, 15 Jun 2026 13:26:10 -0400 Subject: [PATCH 12/19] fix error introduced in rebase --- src/err/err_state.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/err/err_state.rs b/src/err/err_state.rs index 15f8bb1a037..c1059ef8ba9 100644 --- a/src/err/err_state.rs +++ b/src/err/err_state.rs @@ -2,6 +2,8 @@ #![allow(clippy::undocumented_unsafe_blocks)] use crate::platform::prelude::*; +#[cfg(not(Py_3_12))] +use crate::platform::sync::non_poison::Mutex; use crate::platform::sync::Once; use core::cell::{Cell, UnsafeCell}; use std::thread::ThreadId; From 605fb8cda2e46aa09b1409fa38b3413b2ca59307 Mon Sep 17 00:00:00 2001 From: person93 Date: Mon, 15 Jun 2026 14:04:34 -0400 Subject: [PATCH 13/19] add cfg `wip_feature_std` in build script --- build.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/build.rs b/build.rs index 1077d45e3b7..75ebec2d115 100644 --- a/build.rs +++ b/build.rs @@ -53,7 +53,19 @@ fn configure_pyo3() -> Result<()> { Ok(()) } +/// Enables a faux `std` feature by default. +/// +/// Set env var `PYO3_WIP_NO_STD` to `1` to disable it. +fn configure_wip_no_std() { + println!("cargo:rustc-check-cfg=cfg(wip_feature_std)"); + match cargo_env_var("PYO3_WIP_NO_STD") { + Some(no_std) if no_std.trim() == "1" || no_std.trim().eq_ignore_ascii_case("true") => (), + _ => println!("cargo:rustc-cfg=wip_feature_std"), + } +} + fn main() { + configure_wip_no_std(); pyo3_build_config::print_expected_cfgs(); if let Err(e) = configure_pyo3() { eprintln!("error: {}", e.report()); From 98f628c9cbdda1af271b68dc72d3e8b76ff4784e Mon Sep 17 00:00:00 2001 From: person93 Date: Mon, 15 Jun 2026 14:04:56 -0400 Subject: [PATCH 14/19] prefer mutex from `std` --- src/platform/sync.rs | 71 +++++++++++++++++++++++++------------------- 1 file changed, 40 insertions(+), 31 deletions(-) diff --git a/src/platform/sync.rs b/src/platform/sync.rs index bd8490770ff..22f5c6260fe 100644 --- a/src/platform/sync.rs +++ b/src/platform/sync.rs @@ -3,12 +3,18 @@ use crate::sealed; use crate::sync::OnceExt; -#[cfg(feature = "parking_lot")] -type OnceInner = parking_lot::Once; - -#[cfg(not(feature = "parking_lot"))] -#[allow(clippy::disallowed_types)] -type OnceInner = std::sync::Once; +cfg_select! { + wip_feature_std => { + #[allow(clippy::disallowed_types)] + type OnceInner = std::sync::Once; + }, + feature = "parking_lot" => { + type OnceInner = parking_lot::Once; + }, + _ => { + compile_error!("Please enable at least one of the following features: std, parking_lot"); + }, +} pub struct Once(OnceInner); @@ -37,15 +43,19 @@ impl Once { self.0.call_once_force(move |_| f()); } - #[cfg(feature = "parking_lot")] - pub fn is_completed(&self) -> bool { - matches!(self.0.state(), parking_lot::OnceState::Done) - } - - #[cfg(not(feature = "parking_lot"))] #[inline(always)] pub fn is_completed(&self) -> bool { - self.0.is_completed() + cfg_select! { + wip_feature_std => { + self.0.is_completed() + }, + feature = "parking_lot" => { + matches!(self.0.state(), parking_lot::OnceState::Done) + }, + _ => { + compile_error!("Please enable at least one of the following features: std, parking_lot") + } + } } } @@ -67,20 +77,26 @@ impl OnceExt for Once { } pub mod non_poison { - #[cfg(feature = "parking_lot")] - pub use parking_lot::{Mutex, MutexGuard}; - - #[cfg(not(feature = "parking_lot"))] - pub use std::sync::MutexGuard; + cfg_select! { + wip_feature_std => { + pub use std::sync::MutexGuard; + }, + feature = "parking_lot" => { + pub use parking_lot::{Mutex, MutexGuard}; + }, + _ => { + compile_error!("Please enable at least one of the following features: std, parking_lot"); + }, + } - #[cfg(not(feature = "parking_lot"))] + #[cfg(wip_feature_std)] #[derive(Default, Debug)] pub struct Mutex { #[allow(clippy::disallowed_types)] inner: std::sync::Mutex, } - #[cfg(not(feature = "parking_lot"))] + #[cfg(wip_feature_std)] impl Mutex { #[inline(always)] pub const fn new(t: T) -> Mutex { @@ -90,19 +106,12 @@ pub mod non_poison { } } - #[cfg(feature = "parking_lot")] - #[inline(always)] - pub fn into_inner(self) -> T { - self.inner.into_inner() - } - - #[cfg(not(feature = "parking_lot"))] pub fn into_inner(self) -> T { self.inner.into_inner().unwrap_or_else(|e| e.into_inner()) } } - #[cfg(not(feature = "parking_lot"))] + #[cfg(wip_feature_std)] impl Mutex { #[inline(always)] pub fn lock(&self) -> MutexGuard<'_, T> { @@ -112,7 +121,7 @@ pub mod non_poison { // TODO try_lock } - #[cfg(not(feature = "parking_lot"))] + #[cfg(wip_feature_std)] impl From for Mutex { #[inline(always)] fn from(t: T) -> Self { @@ -123,10 +132,10 @@ pub mod non_poison { } } - #[cfg(not(feature = "parking_lot"))] + #[cfg(wip_feature_std)] impl crate::sealed::Sealed for Mutex {} - #[cfg(not(feature = "parking_lot"))] + #[cfg(wip_feature_std)] impl crate::sync::MutexExt for Mutex { type LockResult<'a> = MutexGuard<'a, T> From 9584a8a8b3cd7181c700a4337554c7505f775626 Mon Sep 17 00:00:00 2001 From: person93 Date: Mon, 15 Jun 2026 14:40:19 -0400 Subject: [PATCH 15/19] fix conditional compilation errors --- src/err/err_state.rs | 9 +++------ src/platform/sync.rs | 2 ++ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/err/err_state.rs b/src/err/err_state.rs index c1059ef8ba9..eece359c3d5 100644 --- a/src/err/err_state.rs +++ b/src/err/err_state.rs @@ -142,7 +142,7 @@ pub(crate) struct PyErrStateNormalized { ptype: Py, pub pvalue: Py, #[cfg(not(Py_3_12))] - ptraceback: std::sync::Mutex>>, + ptraceback: Mutex>>, } impl PyErrStateNormalized { @@ -176,7 +176,6 @@ impl PyErrStateNormalized { pub(crate) fn ptraceback<'py>(&self, py: Python<'py>) -> Option> { self.ptraceback .lock_py_attached(py) - .unwrap() .as_ref() .map(|traceback| traceback.bind(py).clone()) } @@ -192,7 +191,7 @@ impl PyErrStateNormalized { #[cfg(not(Py_3_12))] pub(crate) fn set_ptraceback<'py>(&self, py: Python<'py>, tb: Option>) { - *self.ptraceback.lock_py_attached(py).unwrap() = tb.map(Bound::unbind); + *self.ptraceback.lock_py_attached(py) = tb.map(Bound::unbind); } #[cfg(Py_3_12)] @@ -250,7 +249,7 @@ impl PyErrStateNormalized { ptype.map(|ptype| PyErrStateNormalized { ptype: ptype.unbind(), pvalue: pvalue.expect("normalized exception value missing").unbind(), - ptraceback: std::sync::Mutex::new(ptraceback.map(Bound::unbind)), + ptraceback: Mutex::new(ptraceback.map(Bound::unbind)), }) } } @@ -293,7 +292,6 @@ impl PyErrStateNormalized { ptraceback: Mutex::new( self.ptraceback .lock_py_attached(py) - .unwrap() .as_ref() .map(|ptraceback| ptraceback.clone_ref(py)), ), @@ -349,7 +347,6 @@ impl PyErrStateInner { pvalue.into_ptr(), ptraceback .into_inner() - .unwrap() .map_or(core::ptr::null_mut(), Py::into_ptr), ), }; diff --git a/src/platform/sync.rs b/src/platform/sync.rs index 22f5c6260fe..243e4181db7 100644 --- a/src/platform/sync.rs +++ b/src/platform/sync.rs @@ -1,5 +1,7 @@ // TODO compile_error if parking_lot and std are both disabled +#[cfg(not(cfg_select))] +use crate::internal::macros::cfg_select; use crate::sealed; use crate::sync::OnceExt; From b20f91a75eea3d92916c5c4aced2a7231d9d2a9e Mon Sep 17 00:00:00 2001 From: person93 Date: Mon, 15 Jun 2026 17:08:11 -0400 Subject: [PATCH 16/19] remove old TODO --- src/platform/sync.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/platform/sync.rs b/src/platform/sync.rs index 243e4181db7..317374011a7 100644 --- a/src/platform/sync.rs +++ b/src/platform/sync.rs @@ -1,5 +1,3 @@ -// TODO compile_error if parking_lot and std are both disabled - #[cfg(not(cfg_select))] use crate::internal::macros::cfg_select; use crate::sealed; From 8d2ab06baeef3f4d3fbd230ab106ade321400a2b Mon Sep 17 00:00:00 2001 From: person93 Date: Mon, 15 Jun 2026 18:12:33 -0400 Subject: [PATCH 17/19] remove cfg_select type aliases --- src/platform/sync.rs | 48 +++++++++++++++++++------------------------- 1 file changed, 21 insertions(+), 27 deletions(-) diff --git a/src/platform/sync.rs b/src/platform/sync.rs index 317374011a7..18bcbf7e11e 100644 --- a/src/platform/sync.rs +++ b/src/platform/sync.rs @@ -1,20 +1,17 @@ -#[cfg(not(cfg_select))] -use crate::internal::macros::cfg_select; use crate::sealed; use crate::sync::OnceExt; -cfg_select! { - wip_feature_std => { - #[allow(clippy::disallowed_types)] - type OnceInner = std::sync::Once; - }, - feature = "parking_lot" => { - type OnceInner = parking_lot::Once; - }, - _ => { - compile_error!("Please enable at least one of the following features: std, parking_lot"); - }, -} +// TODO replace with cfg_select when MSRV >= 1.95.0 + +#[cfg(wip_feature_std)] +#[allow(clippy::disallowed_types)] +type OnceInner = std::sync::Once; + +#[cfg(all(not(wip_feature_std), feature = "parking_lot"))] +type OnceInner = parking_lot::Once; + +#[cfg(all(not(wip_feature_std), not(feature = "parking_lot")))] +compile_error!("Please enable at least one of the following features: std, parking_lot"); pub struct Once(OnceInner); @@ -24,7 +21,6 @@ impl Default for Once { } } -// #[cfg(feature = "parking_lot")] impl Once { /// Creates a new `Once` value. #[inline(always)] @@ -77,17 +73,15 @@ impl OnceExt for Once { } pub mod non_poison { - cfg_select! { - wip_feature_std => { - pub use std::sync::MutexGuard; - }, - feature = "parking_lot" => { - pub use parking_lot::{Mutex, MutexGuard}; - }, - _ => { - compile_error!("Please enable at least one of the following features: std, parking_lot"); - }, - } + // TODO replace with cfg_select when MSRV >= 1.95.0 + #[cfg(wip_feature_std)] + pub use std::sync::MutexGuard; + + #[cfg(all(not(wip_feature_std), feature = "parking_lot"))] + pub use parking_lot::{Mutex, MutexGuard}; + + #[cfg(all(not(wip_feature_std), not(feature = "parking_lot")))] + compile_error!("Please enable at least one of the following features: std, parking_lot"); #[cfg(wip_feature_std)] #[derive(Default, Debug)] @@ -114,7 +108,7 @@ pub mod non_poison { #[cfg(wip_feature_std)] impl Mutex { #[inline(always)] - pub fn lock(&self) -> MutexGuard<'_, T> { + pub fn lock(&self) -> std::sync::MutexGuard<'_, T> { self.inner.lock().unwrap_or_else(|e| e.into_inner()) } From 1b4d23a8fda85984cba0058673ddf04f664d58b1 Mon Sep 17 00:00:00 2001 From: person93 Date: Mon, 15 Jun 2026 19:03:22 -0400 Subject: [PATCH 18/19] reorder imports so that internal macros can be used in platform --- src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 1bb0e5af6f3..175766556b1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -364,9 +364,6 @@ pub(crate) mod ffi_ptr_ext; pub(crate) mod py_result_ext; pub(crate) mod sealed; -#[doc(hidden)] -pub mod platform; - /// Old module which contained some implementation details of the `#[pyproto]` module. /// /// Prefer using the same content from `pyo3::pyclass`, e.g. `use pyo3::pyclass::CompareOp` instead @@ -416,6 +413,9 @@ mod internal; #[macro_use] mod internal_tricks; +#[doc(hidden)] +pub mod platform; + // Macro dependencies, also contains macros exported for use across the codebase and // in expanded macros. #[doc(hidden)] From 0ff46ba9fe7d36f5c914eccf31f70608e205807d Mon Sep 17 00:00:00 2001 From: person93 Date: Mon, 15 Jun 2026 22:08:40 -0400 Subject: [PATCH 19/19] remove more cfg_select from platform/sync.rs --- src/platform/sync.rs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/platform/sync.rs b/src/platform/sync.rs index 18bcbf7e11e..8c2417e12ec 100644 --- a/src/platform/sync.rs +++ b/src/platform/sync.rs @@ -41,16 +41,17 @@ impl Once { #[inline(always)] pub fn is_completed(&self) -> bool { - cfg_select! { - wip_feature_std => { - self.0.is_completed() - }, - feature = "parking_lot" => { - matches!(self.0.state(), parking_lot::OnceState::Done) - }, - _ => { - compile_error!("Please enable at least one of the following features: std, parking_lot") - } + #[cfg(wip_feature_std)] + { + self.0.is_completed() + } + #[cfg(all(not(wip_feature_std), feature = "parking_lot"))] + { + matches!(self.0.state(), parking_lot::OnceState::Done) + } + #[cfg(all(not(wip_feature_std), feature = "parking_lot"))] + { + compile_error!("Please enable at least one of the following features: std, parking_lot") } } }