Skip to content

Commit

Permalink
hang instead of pthread_exit during interpreter shutdown
Browse files Browse the repository at this point in the history
  • Loading branch information
Ariel Ben-Yehuda committed Jan 26, 2025
1 parent 5c363b5 commit 346b4de
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 1 deletion.
1 change: 1 addition & 0 deletions pyo3-ffi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ generate-import-lib = ["pyo3-build-config/python3-dll-a"]
paste = "1"

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

[lints]
Expand Down
18 changes: 18 additions & 0 deletions pyo3-ffi/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,22 @@ fn emit_link_config(interpreter_config: &InterpreterConfig) -> Result<()> {
Ok(())
}

fn do_cc(interpreter_config: &InterpreterConfig) -> Result<()> {
let implementation_def = match interpreter_config.implementation {
PythonImplementation::CPython => "PYTHON_IS_CPYTHON",
PythonImplementation::PyPy => "PYTHON_IS_PYPY",
PythonImplementation::GraalPy => "PYTHON_IS_GRAALPY",
};
println!("cargo:rerun-if-changed=src/acquire_gil.cpp");
cc::Build::new()
.cpp(true)
.file("src/acquire_gil.cpp")
.define(implementation_def, None)
.cpp_link_stdlib("stdc++")
.compile("acquire_gil");
Ok(())
}

/// Prepares the PyO3 crate for compilation.
///
/// This loads the config from pyo3-build-config and then makes some additional checks to improve UX
Expand Down Expand Up @@ -218,6 +234,8 @@ fn configure_pyo3() -> Result<()> {
// Emit cfgs like `invalid_from_utf8_lint`
print_feature_cfgs();

do_cc(&interpreter_config)?;

Ok(())
}

Expand Down
45 changes: 45 additions & 0 deletions pyo3-ffi/src/acquire_gil.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#if defined(_WIN32)
#include <windows.h>
#include <synchapi.h>
#else
#include <unistd.h>
#endif

#if defined(PYTHON_IS_PYPY)
#define gil_func_name PyPyGILState_Ensure
#define wrapped_func_name PyPyGILState_Ensure_Safe
#else
#define gil_func_name PyGILState_Ensure
#define wrapped_func_name PyGILState_Ensure_Safe
#endif

extern "C" {
int wrapped_func_name(void);
int gil_func_name(void);
};

#if !defined(_WIN32)
// mark the wrapped function as visibility("hidden") to avoid causing namespace pollution
__attribute__((visibility("hidden")))
#endif
int wrapped_func_name(void) {
// Do the equivalent of https://github.com/python/cpython/issues/87135 (included
// in Python 3.14) to avoid pthread_exit unwinding the current thread, which tends
// to cause undefined behavior in Rust.
//
// Unfortunately, I don't know of a way to do a catch(...) from Rust.
try {
return gil_func_name();
} catch(...) {
while(1) {
#if defined(_WIN32)
SleepEx(INFINITE, TRUE);
#elif defined(__wasi__)
sleep(9999999); // WASI doesn't have pause() ?!
#else
pause();
#endif
}
}
}

8 changes: 7 additions & 1 deletion pyo3-ffi/src/pystate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,13 @@ pub enum PyGILState_STATE {
}

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

0 comments on commit 346b4de

Please sign in to comment.