From 996b1a5f8927f8edd8fbb7cc57d34749bff0fc00 Mon Sep 17 00:00:00 2001 From: Ariel Ben-Yehuda Date: Mon, 27 Jan 2025 21:48:49 +0000 Subject: [PATCH] new attempt --- pyo3-ffi/Cargo.toml | 2 +- pyo3-ffi/build.rs | 15 ----------- pyo3-ffi/src/acquire_gil.c | 48 ----------------------------------- pyo3-ffi/src/pystate.rs | 51 ++++++++++++++++++++++++++++++++------ 4 files changed, 44 insertions(+), 72 deletions(-) delete mode 100644 pyo3-ffi/src/acquire_gil.c diff --git a/pyo3-ffi/Cargo.toml b/pyo3-ffi/Cargo.toml index c89acef0573..80d94294dac 100644 --- a/pyo3-ffi/Cargo.toml +++ b/pyo3-ffi/Cargo.toml @@ -14,6 +14,7 @@ rust-version = "1.63" [dependencies] libc = "0.2.62" +rustversion = "1" [features] @@ -42,7 +43,6 @@ 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] diff --git a/pyo3-ffi/build.rs b/pyo3-ffi/build.rs index 79ba1795fb3..ea023de75fa 100644 --- a/pyo3-ffi/build.rs +++ b/pyo3-ffi/build.rs @@ -182,19 +182,6 @@ fn emit_link_config(interpreter_config: &InterpreterConfig) -> Result<()> { Ok(()) } -fn do_cc(interpreter_config: &InterpreterConfig) { - 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() - .file("src/acquire_gil.c") - .define(implementation_def, None) - .compile("acquire_gil"); -} - /// Prepares the PyO3 crate for compilation. /// /// This loads the config from pyo3-build-config and then makes some additional checks to improve UX @@ -231,8 +218,6 @@ fn configure_pyo3() -> Result<()> { // Emit cfgs like `invalid_from_utf8_lint` print_feature_cfgs(); - do_cc(&interpreter_config); - Ok(()) } diff --git a/pyo3-ffi/src/acquire_gil.c b/pyo3-ffi/src/acquire_gil.c deleted file mode 100644 index 3becd0bc70b..00000000000 --- a/pyo3-ffi/src/acquire_gil.c +++ /dev/null @@ -1,48 +0,0 @@ -#if defined(_WIN32) -#else -#include -#include -#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 - -int wrapped_func_name(void); -int gil_func_name(void); - -#if defined(_WIN32) -int wrapped_func_name(void) { - // In MSVC, PyThread_exit_thread calls _endthreadex(0), which does not use SEH. This can - // cause Rust-level UB if there is pinned memory, but AFAICT there's not much we can do about it. - return gil_func_name(); -} -#else -static void hang_thread(void *ignore) { - (void)ignore; - while(1) { -#if defined(__wasi__) - sleep(9999999); // WASI doesn't have pause() ?! -#else - pause(); -#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. - int ret; - pthread_cleanup_push(hang_thread, NULL); - ret = gil_func_name(); - pthread_cleanup_pop(0); - return ret; -} -#endif \ No newline at end of file diff --git a/pyo3-ffi/src/pystate.rs b/pyo3-ffi/src/pystate.rs index e493b51da4e..af36cc964b7 100644 --- a/pyo3-ffi/src/pystate.rs +++ b/pyo3-ffi/src/pystate.rs @@ -73,15 +73,50 @@ pub enum PyGILState_STATE { PyGILState_UNLOCKED, } +struct HangThread; + +impl Drop for HangThread { + fn drop(&mut self) { + loop { + #[cfg(target_family = "unix")] + unsafe { + libc::pause(); + } + #[cfg(not(target_family = "unix"))] + std::thread::sleep(std::time::Duration::from_secs(9_999_999)); + } + } +} + +// 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 + +// C-unwind only supported (and necessary) since 1.71 +mod raw { + #[rustversion::since(1.71)] + extern "C-unwind" { + #[cfg_attr(PyPy, link_name = "PyPyGILState_Ensure")] + pub fn PyGILState_Ensure() -> super::PyGILState_STATE; + } + + #[rustversion::before(1.71)] + extern "C" { + #[cfg_attr(PyPy, link_name = "PyPyGILState_Ensure")] + pub fn PyGILState_Ensure() -> super::PyGILState_STATE; + } +} + +pub unsafe extern "C" fn PyGILState_Ensure() -> PyGILState_STATE { + let guard = HangThread; + let ret: PyGILState_STATE = raw::PyGILState_Ensure(); + std::mem::forget(guard); + ret +} + extern "C" { - // 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); #[cfg(not(PyPy))]