Skip to content

Commit 1e84d7b

Browse files
committed
move use of std::thread from ffi crate to main
1 parent 323dc56 commit 1e84d7b

2 files changed

Lines changed: 50 additions & 57 deletions

File tree

pyo3-ffi/src/pystate.rs

Lines changed: 8 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -79,68 +79,19 @@ pub enum PyGILState_STATE {
7979
PyGILState_UNLOCKED,
8080
}
8181

82-
#[cfg(not(any(Py_3_14, target_arch = "wasm32")))]
83-
struct HangThread;
84-
85-
#[cfg(not(any(Py_3_14, target_arch = "wasm32")))]
86-
impl Drop for HangThread {
87-
fn drop(&mut self) {
88-
loop {
89-
core::thread::park(); // Block forever.
90-
}
91-
}
92-
}
93-
94-
// The PyGILState_Ensure function will call pthread_exit during interpreter shutdown,
95-
// which causes undefined behavior. Redirect to the "safe" version that hangs instead,
96-
// as Python 3.14 does.
97-
//
98-
// See https://github.com/rust-lang/rust/issues/135929
99-
10082
// C-unwind only supported (and necessary) since 1.71. Python 3.14+ does not do
10183
// pthread_exit from PyGILState_Ensure (https://github.com/python/cpython/issues/87135).
102-
mod raw {
103-
#[cfg(not(any(Py_3_14, target_arch = "wasm32")))]
104-
extern_libpython! { "C-unwind" {
105-
#[cfg_attr(PyPy, link_name = "PyPyGILState_Ensure")]
106-
pub fn PyGILState_Ensure() -> super::PyGILState_STATE;
107-
}}
108-
109-
#[cfg(any(Py_3_14, target_arch = "wasm32"))]
110-
extern_libpython! {
111-
#[cfg_attr(PyPy, link_name = "PyPyGILState_Ensure")]
112-
pub fn PyGILState_Ensure() -> super::PyGILState_STATE;
113-
}
114-
}
115-
11684
#[cfg(not(any(Py_3_14, target_arch = "wasm32")))]
117-
pub unsafe extern "C" fn PyGILState_Ensure() -> PyGILState_STATE {
118-
let guard = HangThread;
119-
// If `PyGILState_Ensure` calls `pthread_exit`, which it does on Python < 3.14
120-
// when the interpreter is shutting down, this will cause a forced unwind.
121-
// doing a forced unwind through a function with a Rust destructor is unspecified
122-
// behavior.
123-
//
124-
// However, currently it runs the destructor, which will cause the thread to
125-
// hang as it should.
126-
//
127-
// And if we don't catch the unwinding here, then one of our callers probably has a destructor,
128-
// so it's unspecified behavior anyway, and on many configurations causes the process to abort.
129-
//
130-
// The alternative is for pyo3 to contain custom C or C++ code that catches the `pthread_exit`,
131-
// but that's also annoying from a portability point of view.
132-
//
133-
// On Windows, `PyGILState_Ensure` calls `_endthreadex` instead, which AFAICT can't be caught
134-
// and therefore will cause unsafety if there are pinned objects on the stack. AFAICT there's
135-
// nothing we can do it other than waiting for Python 3.14 or not using Windows. At least,
136-
// if there is nothing pinned on the stack, it won't cause the process to crash.
137-
let ret: PyGILState_STATE = raw::PyGILState_Ensure();
138-
core::mem::forget(guard);
139-
ret
140-
}
85+
extern_libpython! { "C-unwind" {
86+
#[cfg_attr(PyPy, link_name = "PyPyGILState_Ensure")]
87+
pub fn PyGILState_Ensure() -> super::PyGILState_STATE;
88+
}}
14189

14290
#[cfg(any(Py_3_14, target_arch = "wasm32"))]
143-
pub use self::raw::PyGILState_Ensure;
91+
extern_libpython! {
92+
#[cfg_attr(PyPy, link_name = "PyPyGILState_Ensure")]
93+
pub fn PyGILState_Ensure() -> super::PyGILState_STATE;
94+
}
14495

14596
extern_libpython! {
14697
#[cfg_attr(PyPy, link_name = "PyPyGILState_Release")]

src/internal/state.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,50 @@ impl AttachGuard {
141141
/// Attach to the interpreter, without a fast-path to check if the thread is already attached.
142142
#[cold]
143143
unsafe fn do_attach_unchecked() -> Self {
144+
// The PyGILState_Ensure function will call pthread_exit during interpreter shutdown,
145+
// which causes undefined behavior. Hang the thread instead, as Python 3.14 does.
146+
//
147+
// See https://github.com/rust-lang/rust/issues/135929
148+
149+
#[cfg(not(any(Py_3_14, target_arch = "wasm32")))]
150+
struct HangThread;
151+
152+
#[cfg(not(any(Py_3_14, target_arch = "wasm32")))]
153+
impl Drop for HangThread {
154+
fn drop(&mut self) {
155+
loop {
156+
std::thread::park(); // Block forever.
157+
}
158+
}
159+
}
160+
161+
// If `PyGILState_Ensure` calls `pthread_exit`, which it does on Python < 3.14
162+
// when the interpreter is shutting down, this will cause a forced unwind.
163+
// doing a forced unwind through a function with a Rust destructor is unspecified
164+
// behavior.
165+
//
166+
// However, currently it runs the destructor, which will cause the thread to
167+
// hang as it should.
168+
//
169+
// And if we don't catch the unwinding here, then one of our callers probably has a destructor,
170+
// so it's unspecified behavior anyway, and on many configurations causes the process to abort.
171+
//
172+
// The alternative is for pyo3 to contain custom C or C++ code that catches the `pthread_exit`,
173+
// but that's also annoying from a portability point of view.
174+
//
175+
// On Windows, `PyGILState_Ensure` calls `_endthreadex` instead, which AFAICT can't be caught
176+
// and therefore will cause unsafety if there are pinned objects on the stack. AFAICT there's
177+
// nothing we can do it other than waiting for Python 3.14 or not using Windows. At least,
178+
// if there is nothing pinned on the stack, it won't cause the process to crash.
179+
#[cfg(not(any(Py_3_14, target_arch = "wasm32")))]
180+
let guard = HangThread;
181+
144182
// SAFETY: interpreter is sufficiently initialized to attach a thread.
145183
let gstate = unsafe { ffi::PyGILState_Ensure() };
184+
185+
#[cfg(not(any(Py_3_14, target_arch = "wasm32")))]
186+
core::mem::forget(guard);
187+
146188
increment_attach_count();
147189
// SAFETY: just attached to the interpreter
148190
drop_deferred_references(unsafe { Python::assume_attached() });

0 commit comments

Comments
 (0)