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