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