Skip to content

Commit 502a6cc

Browse files
author
Ariel Ben-Yehuda
committed
hang instead of pthread_exit during interpreter shutdown
see python/cpython#87135 and rust-lang/rust#135929
1 parent 2c732a7 commit 502a6cc

File tree

4 files changed

+71
-1
lines changed

4 files changed

+71
-1
lines changed

pyo3-ffi/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ generate-import-lib = ["pyo3-build-config/python3-dll-a"]
4242
paste = "1"
4343

4444
[build-dependencies]
45+
cc = "1.2"
4546
pyo3-build-config = { path = "../pyo3-build-config", version = "=0.23.3", features = ["resolve-config"] }
4647

4748
[lints]

pyo3-ffi/build.rs

+18
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,22 @@ fn emit_link_config(interpreter_config: &InterpreterConfig) -> Result<()> {
182182
Ok(())
183183
}
184184

185+
fn do_cc(interpreter_config: &InterpreterConfig) -> Result<()> {
186+
let implementation_def = match interpreter_config.implementation {
187+
PythonImplementation::CPython => "PYTHON_IS_CPYTHON",
188+
PythonImplementation::PyPy => "PYTHON_IS_PYPY",
189+
PythonImplementation::GraalPy => "PYTHON_IS_GRAALPY",
190+
};
191+
println!("cargo:rerun-if-changed=src/acquire_gil.cpp");
192+
cc::Build::new()
193+
.cpp(true)
194+
.file("src/acquire_gil.cpp")
195+
.define(implementation_def, None)
196+
.cpp_link_stdlib("stdc++")
197+
.compile("acquire_gil");
198+
Ok(())
199+
}
200+
185201
/// Prepares the PyO3 crate for compilation.
186202
///
187203
/// This loads the config from pyo3-build-config and then makes some additional checks to improve UX
@@ -218,6 +234,8 @@ fn configure_pyo3() -> Result<()> {
218234
// Emit cfgs like `invalid_from_utf8_lint`
219235
print_feature_cfgs();
220236

237+
do_cc(&interpreter_config)?;
238+
221239
Ok(())
222240
}
223241

pyo3-ffi/src/acquire_gil.cpp

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#if defined(_WIN32)
2+
#include <windows.h>
3+
#include <synchapi.h>
4+
#else
5+
#include <unistd.h>
6+
#endif
7+
8+
#if defined(PYTHON_IS_PYPY)
9+
#define gil_func_name PyPyGILState_Ensure
10+
#define wrapped_func_name PyPyGILState_Ensure_Safe
11+
#else
12+
#define gil_func_name PyGILState_Ensure
13+
#define wrapped_func_name PyGILState_Ensure_Safe
14+
#endif
15+
16+
extern "C" {
17+
int wrapped_func_name(void);
18+
int gil_func_name(void);
19+
};
20+
21+
#if !defined(_WIN32)
22+
// mark the wrapped function as visibility("hidden") to avoid causing namespace pollution
23+
__attribute__((visibility("hidden")))
24+
#endif
25+
int wrapped_func_name(void) {
26+
// Do the equivalent of https://github.com/python/cpython/issues/87135 (included
27+
// in Python 3.14) to avoid pthread_exit unwinding the current thread, which tends
28+
// to cause undefined behavior in Rust.
29+
//
30+
// Unfortunately, I don't know of a way to do a catch(...) from Rust.
31+
try {
32+
return gil_func_name();
33+
} catch(...) {
34+
while(1) {
35+
#if defined(_WIN32)
36+
SleepEx(INFINITE, TRUE);
37+
#elif defined(__wasi__)
38+
sleep(9999999); // WASI doesn't have pause() ?!
39+
#else
40+
pause();
41+
#endif
42+
}
43+
}
44+
}
45+

pyo3-ffi/src/pystate.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,13 @@ pub enum PyGILState_STATE {
7474
}
7575

7676
extern "C" {
77-
#[cfg_attr(PyPy, link_name = "PyPyGILState_Ensure")]
77+
// The PyGILState_Ensure function will call pthread_exit during interpreter shutdown,
78+
// which causes undefined behavior. Redirect to the "safe" version that hangs instead,
79+
// as Python 3.14 does.
80+
//
81+
// See https://github.com/rust-lang/rust/issues/135929
82+
#[cfg_attr(PyPy, link_name = "PyPyGILState_Ensure_Safe")]
83+
#[cfg_attr(not(PyPy), link_name = "PyGILState_Ensure_Safe")]
7884
pub fn PyGILState_Ensure() -> PyGILState_STATE;
7985
#[cfg_attr(PyPy, link_name = "PyPyGILState_Release")]
8086
pub fn PyGILState_Release(arg1: PyGILState_STATE);

0 commit comments

Comments
 (0)