pyo3_ffi/
pystate.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
#[cfg(all(Py_3_10, not(PyPy), not(Py_LIMITED_API)))]
use crate::frameobject::PyFrameObject;
use crate::moduleobject::PyModuleDef;
use crate::object::PyObject;
use std::os::raw::c_int;

#[cfg(not(PyPy))]
use std::os::raw::c_long;

pub const MAX_CO_EXTRA_USERS: c_int = 255;

opaque_struct!(PyThreadState);
opaque_struct!(PyInterpreterState);

extern "C" {
    #[cfg(not(PyPy))]
    pub fn PyInterpreterState_New() -> *mut PyInterpreterState;
    #[cfg(not(PyPy))]
    pub fn PyInterpreterState_Clear(arg1: *mut PyInterpreterState);
    #[cfg(not(PyPy))]
    pub fn PyInterpreterState_Delete(arg1: *mut PyInterpreterState);

    #[cfg(all(Py_3_9, not(PyPy)))]
    pub fn PyInterpreterState_Get() -> *mut PyInterpreterState;

    #[cfg(all(Py_3_8, not(PyPy)))]
    pub fn PyInterpreterState_GetDict(arg1: *mut PyInterpreterState) -> *mut PyObject;

    #[cfg(not(PyPy))]
    pub fn PyInterpreterState_GetID(arg1: *mut PyInterpreterState) -> i64;

    #[cfg_attr(PyPy, link_name = "PyPyState_AddModule")]
    pub fn PyState_AddModule(arg1: *mut PyObject, arg2: *mut PyModuleDef) -> c_int;

    #[cfg_attr(PyPy, link_name = "PyPyState_RemoveModule")]
    pub fn PyState_RemoveModule(arg1: *mut PyModuleDef) -> c_int;

    // only has PyPy prefix since 3.10
    #[cfg_attr(all(PyPy, Py_3_10), link_name = "PyPyState_FindModule")]
    pub fn PyState_FindModule(arg1: *mut PyModuleDef) -> *mut PyObject;

    #[cfg_attr(PyPy, link_name = "PyPyThreadState_New")]
    pub fn PyThreadState_New(arg1: *mut PyInterpreterState) -> *mut PyThreadState;
    #[cfg_attr(PyPy, link_name = "PyPyThreadState_Clear")]
    pub fn PyThreadState_Clear(arg1: *mut PyThreadState);
    #[cfg_attr(PyPy, link_name = "PyPyThreadState_Delete")]
    pub fn PyThreadState_Delete(arg1: *mut PyThreadState);

    #[cfg_attr(PyPy, link_name = "PyPyThreadState_Get")]
    pub fn PyThreadState_Get() -> *mut PyThreadState;
}

#[inline]
pub unsafe fn PyThreadState_GET() -> *mut PyThreadState {
    PyThreadState_Get()
}

extern "C" {
    #[cfg_attr(PyPy, link_name = "PyPyThreadState_Swap")]
    pub fn PyThreadState_Swap(arg1: *mut PyThreadState) -> *mut PyThreadState;
    #[cfg_attr(PyPy, link_name = "PyPyThreadState_GetDict")]
    pub fn PyThreadState_GetDict() -> *mut PyObject;
    #[cfg(not(PyPy))]
    pub fn PyThreadState_SetAsyncExc(arg1: c_long, arg2: *mut PyObject) -> c_int;
}

// skipped non-limited / 3.9 PyThreadState_GetInterpreter
// skipped non-limited / 3.9 PyThreadState_GetID

extern "C" {
    // PyThreadState_GetFrame
    #[cfg(all(Py_3_10, not(PyPy), not(Py_LIMITED_API)))]
    pub fn PyThreadState_GetFrame(arg1: *mut PyThreadState) -> *mut PyFrameObject;
}

#[repr(C)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum PyGILState_STATE {
    PyGILState_LOCKED,
    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. Python 3.14+ does not do
// pthread_exit from PyGILState_Ensure (https://github.com/python/cpython/issues/87135).
mod raw {
    #[cfg(all(not(Py_3_14), rustc_has_extern_c_unwind))]
    extern "C-unwind" {
        #[cfg_attr(PyPy, link_name = "PyPyGILState_Ensure")]
        pub fn PyGILState_Ensure() -> super::PyGILState_STATE;
    }

    #[cfg(not(all(not(Py_3_14), rustc_has_extern_c_unwind)))]
    extern "C" {
        #[cfg_attr(PyPy, link_name = "PyPyGILState_Ensure")]
        pub fn PyGILState_Ensure() -> super::PyGILState_STATE;
    }
}

#[cfg(not(Py_3_14))]
pub unsafe extern "C" fn PyGILState_Ensure() -> PyGILState_STATE {
    let guard = HangThread;
    // If `PyGILState_Ensure` calls `pthread_exit`, which it does on Python < 3.14
    // when the interpreter is shutting down, this will cause a forced unwind.
    // doing a forced unwind through a function with a Rust destructor is unspecified
    // behavior.
    //
    // However, currently it runs the destructor, which will cause the thread to
    // hang as it should.
    //
    // And if we don't catch the unwinding here, then one of our callers probably has a destructor,
    // so it's unspecified behavior anyway, and on many configurations causes the process to abort.
    //
    // The alternative is for pyo3 to contain custom C or C++ code that catches the `pthread_exit`,
    // but that's also annoying from a portability point of view.
    //
    // On Windows, `PyGILState_Ensure` calls `_endthreadex` instead, which AFAICT can't be caught
    // and therefore will cause unsafety if there are pinned objects on the stack. AFAICT there's
    // nothing we can do it other than waiting for Python 3.14 or not using Windows. At least,
    // if there is nothing pinned on the stack, it won't cause the process to crash.
    let ret: PyGILState_STATE = raw::PyGILState_Ensure();
    std::mem::forget(guard);
    ret
}

#[cfg(Py_3_14)]
pub use self::raw::PyGILState_Ensure;

extern "C" {
    #[cfg_attr(PyPy, link_name = "PyPyGILState_Release")]
    pub fn PyGILState_Release(arg1: PyGILState_STATE);
    #[cfg(not(PyPy))]
    pub fn PyGILState_GetThisThreadState() -> *mut PyThreadState;
}