From 08bd56983d88821ee4de9aaab6acda7e6c0dea09 Mon Sep 17 00:00:00 2001 From: person93 Date: Mon, 18 May 2026 10:57:56 -0400 Subject: [PATCH 1/6] add `PyThread_get_thread_ident` to ffi crate --- pyo3-ffi/src/lib.rs | 3 ++- pyo3-ffi/src/pythread.rs | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 pyo3-ffi/src/pythread.rs diff --git a/pyo3-ffi/src/lib.rs b/pyo3-ffi/src/lib.rs index 04048f287eb..8d075cb39fc 100644 --- a/pyo3-ffi/src/lib.rs +++ b/pyo3-ffi/src/lib.rs @@ -486,6 +486,7 @@ pub use self::pyport::*; pub use self::pystate::*; pub use self::pystrtod::*; pub use self::pythonrun::*; +pub use self::pythread::*; pub use self::pytypedefs::*; pub use self::rangeobject::*; pub use self::refcount::*; @@ -578,7 +579,7 @@ mod pythonrun; // skipped pystrhex.h // skipped pystrcmp.h mod pystrtod; -// skipped pythread.h +mod pythread; // skipped pytime.h mod pytypedefs; mod rangeobject; diff --git a/pyo3-ffi/src/pythread.rs b/pyo3-ffi/src/pythread.rs new file mode 100644 index 00000000000..551770a107a --- /dev/null +++ b/pyo3-ffi/src/pythread.rs @@ -0,0 +1,5 @@ +use core::num::NonZero; + +extern_libpython! { + pub fn PyThread_get_thread_ident() -> NonZero; +} From 6bd9d197270cb81adab1bb6f92a062eebde91a50 Mon Sep 17 00:00:00 2001 From: person93 Date: Mon, 18 May 2026 10:58:27 -0400 Subject: [PATCH 2/6] re-implement `ThreadId` using `PyThread_get_thread_ident` --- src/err/err_state.rs | 10 ++++------ src/impl_/pyclass.rs | 3 ++- src/impl_/pyclass/lazy_type_object.rs | 2 +- src/platform.rs | 2 ++ src/platform/thread.rs | 23 +++++++++++++++++++++++ 5 files changed, 32 insertions(+), 8 deletions(-) create mode 100644 src/platform/thread.rs diff --git a/src/err/err_state.rs b/src/err/err_state.rs index 2efc43ed4f5..4666cc188af 100644 --- a/src/err/err_state.rs +++ b/src/err/err_state.rs @@ -3,11 +3,9 @@ use crate::platform::prelude::*; use core::cell::UnsafeCell; -use std::{ - sync::{Mutex, Once}, - thread::ThreadId, -}; +use std::sync::{Mutex, Once}; +use crate::platform::thread::{self, ThreadId}; #[cfg(not(Py_3_12))] use crate::sync::MutexExt; use crate::{ @@ -98,7 +96,7 @@ impl PyErrState { // re-entrancy guarantees. if let Some(thread) = self.normalizing_thread.lock().unwrap().as_ref() { assert!( - !(*thread == std::thread::current().id()), + !(*thread == thread::current().id()), "Re-entrant normalization of PyErrState detected" ); } @@ -109,7 +107,7 @@ impl PyErrState { self.normalizing_thread .lock() .unwrap() - .replace(std::thread::current().id()); + .replace(thread::current().id()); // Safety: no other thread can access the inner value while we are normalizing it. let state = unsafe { diff --git a/src/impl_/pyclass.rs b/src/impl_/pyclass.rs index 273bf25c3be..6e932015296 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::thread; 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::sync::Mutex; mod assertions; pub mod doc; diff --git a/src/impl_/pyclass/lazy_type_object.rs b/src/impl_/pyclass/lazy_type_object.rs index 2142b1d4568..cf9bcf23767 100644 --- a/src/impl_/pyclass/lazy_type_object.rs +++ b/src/impl_/pyclass/lazy_type_object.rs @@ -2,8 +2,8 @@ #![allow(clippy::undocumented_unsafe_blocks)] use crate::platform::prelude::*; +use crate::platform::thread::{self, ThreadId}; use core::{ffi::CStr, marker::PhantomData}; -use std::thread::{self, ThreadId}; #[cfg(Py_3_14)] use crate::err::error_on_minusone; diff --git a/src/platform.rs b/src/platform.rs index 924f818dc55..a254a57be32 100644 --- a/src/platform.rs +++ b/src/platform.rs @@ -20,3 +20,5 @@ pub use hashbrown::{HashMap, HashSet}; // TODO conditionally import these based on "std" feature #[cfg(not(feature = "hashbrown"))] pub use std::collections::{HashMap, HashSet}; + +pub mod thread; diff --git a/src/platform/thread.rs b/src/platform/thread.rs new file mode 100644 index 00000000000..b74532ce49b --- /dev/null +++ b/src/platform/thread.rs @@ -0,0 +1,23 @@ +use core::num::NonZero; + +use pyo3_ffi::PyThread_get_thread_ident; + +#[must_use] +pub fn current() -> Thread { + Thread { + id: ThreadId(unsafe { PyThread_get_thread_ident() }), + } +} + +pub struct Thread { + id: ThreadId, +} + +impl Thread { + pub fn id(&self) -> ThreadId { + self.id + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub struct ThreadId(NonZero); From eb87ab77bfb2781c3a887d516b8d65a1a65b0661 Mon Sep 17 00:00:00 2001 From: person93 Date: Mon, 18 May 2026 11:01:50 -0400 Subject: [PATCH 3/6] warn on use of `std::thread::ThreadId` --- clippy.toml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 clippy.toml diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 00000000000..fda23657697 --- /dev/null +++ b/clippy.toml @@ -0,0 +1,3 @@ +[[disallowed-types]] +path = "std::thread::ThreadId" +reason = "Use crate::platform::ThreadId for `no_std` compatibility" From 3ff024580a0de0c030839a5e85e7eca7b1ffba54 Mon Sep 17 00:00:00 2001 From: person93 Date: Mon, 18 May 2026 11:07:48 -0400 Subject: [PATCH 4/6] add newsfragment --- newsfragments/6056.added.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 newsfragments/6056.added.md diff --git a/newsfragments/6056.added.md b/newsfragments/6056.added.md new file mode 100644 index 00000000000..b54c3f32018 --- /dev/null +++ b/newsfragments/6056.added.md @@ -0,0 +1 @@ +Add `PyThread_get_thread_ident` to the ffi crate From 82628f4b53dca5a65acc9307e5435d9938cb7bd9 Mon Sep 17 00:00:00 2001 From: person93 Date: Fri, 22 May 2026 10:35:43 -0400 Subject: [PATCH 5/6] use `c_ulong` instead of `NonZero` --- pyo3-ffi/src/pythread.rs | 4 ++-- src/platform/thread.rs | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/pyo3-ffi/src/pythread.rs b/pyo3-ffi/src/pythread.rs index 551770a107a..39d10b59ebe 100644 --- a/pyo3-ffi/src/pythread.rs +++ b/pyo3-ffi/src/pythread.rs @@ -1,5 +1,5 @@ -use core::num::NonZero; +use core::ffi::c_ulong; extern_libpython! { - pub fn PyThread_get_thread_ident() -> NonZero; + pub fn PyThread_get_thread_ident() -> c_ulong; } diff --git a/src/platform/thread.rs b/src/platform/thread.rs index b74532ce49b..7cc097d894a 100644 --- a/src/platform/thread.rs +++ b/src/platform/thread.rs @@ -1,3 +1,4 @@ +use core::ffi::c_ulong; use core::num::NonZero; use pyo3_ffi::PyThread_get_thread_ident; @@ -5,7 +6,9 @@ use pyo3_ffi::PyThread_get_thread_ident; #[must_use] pub fn current() -> Thread { Thread { - id: ThreadId(unsafe { PyThread_get_thread_ident() }), + // SAFETY: PyThread_get_thread_ident never returns zero + // https://docs.python.org/3/c-api/threads.html#c.PyThread_get_thread_ident + id: ThreadId(unsafe { NonZero::new_unchecked(PyThread_get_thread_ident()) }), } } @@ -20,4 +23,4 @@ impl Thread { } #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] -pub struct ThreadId(NonZero); +pub struct ThreadId(NonZero); From bd3610760e36831f435db7a990e278061ad7c4c9 Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Tue, 9 Jun 2026 09:40:59 +0100 Subject: [PATCH 6/6] Update pyo3-ffi/src/pythread.rs --- pyo3-ffi/src/pythread.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/pyo3-ffi/src/pythread.rs b/pyo3-ffi/src/pythread.rs index 39d10b59ebe..898d55bb5d6 100644 --- a/pyo3-ffi/src/pythread.rs +++ b/pyo3-ffi/src/pythread.rs @@ -1,5 +1,6 @@ use core::ffi::c_ulong; extern_libpython! { + #[cfg_attr(PyPy, link_name = "PyPyThread_get_thread_ident")] pub fn PyThread_get_thread_ident() -> c_ulong; }