Skip to content

Commit 738cf21

Browse files
authored
[3.12] gh-119585: Fix crash involving PyGILState_Release() and PyThreadState_Clear() (GH-119753) (#119861)
Make sure that `gilstate_counter` is not zero in when calling `PyThreadState_Clear()`. A destructor called from `PyThreadState_Clear()` may call back into `PyGILState_Ensure()` and `PyGILState_Release()`. If `gilstate_counter` is zero, it will try to create a new thread state before the current active thread state is destroyed, leading to an assertion failure or crash. (cherry picked from commit bcc1be3)
1 parent d4680b9 commit 738cf21

File tree

4 files changed

+36
-0
lines changed

4 files changed

+36
-0
lines changed

Lib/test/test_capi/test_misc.py

+16
Original file line numberDiff line numberDiff line change
@@ -2094,6 +2094,22 @@ def callback():
20942094
t.start()
20952095
t.join()
20962096

2097+
@threading_helper.reap_threads
2098+
@threading_helper.requires_working_threading()
2099+
def test_thread_gilstate_in_clear(self):
2100+
# See https://github.com/python/cpython/issues/119585
2101+
class C:
2102+
def __del__(self):
2103+
_testcapi.gilstate_ensure_release()
2104+
2105+
# Thread-local variables are destroyed in `PyThreadState_Clear()`.
2106+
local_var = threading.local()
2107+
2108+
def callback():
2109+
local_var.x = C()
2110+
2111+
_testcapi._test_thread_state(callback)
2112+
20972113
@threading_helper.reap_threads
20982114
@threading_helper.requires_working_threading()
20992115
def test_gilstate_ensure_no_deadlock(self):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Fix crash when a thread state that was created by :c:func:`PyGILState_Ensure`
2+
calls a destructor that during :c:func:`PyThreadState_Clear` that
3+
calls back into :c:func:`PyGILState_Ensure` and :c:func:`PyGILState_Release`.
4+
This might occur when in the free-threaded build or when using thread-local
5+
variables whose destructors call :c:func:`PyGILState_Ensure`.

Modules/_testcapimodule.c

+9
Original file line numberDiff line numberDiff line change
@@ -822,6 +822,14 @@ test_thread_state(PyObject *self, PyObject *args)
822822
Py_RETURN_NONE;
823823
}
824824

825+
static PyObject *
826+
gilstate_ensure_release(PyObject *module, PyObject *Py_UNUSED(ignored))
827+
{
828+
PyGILState_STATE state = PyGILState_Ensure();
829+
PyGILState_Release(state);
830+
Py_RETURN_NONE;
831+
}
832+
825833
#ifndef MS_WINDOWS
826834
static PyThread_type_lock wait_done = NULL;
827835

@@ -3267,6 +3275,7 @@ static PyMethodDef TestMethods[] = {
32673275
{"test_get_type_qualname", test_get_type_qualname, METH_NOARGS},
32683276
{"test_get_type_dict", test_get_type_dict, METH_NOARGS},
32693277
{"_test_thread_state", test_thread_state, METH_VARARGS},
3278+
{"gilstate_ensure_release", gilstate_ensure_release, METH_NOARGS},
32703279
#ifndef MS_WINDOWS
32713280
{"_spawn_pthread_waiter", spawn_pthread_waiter, METH_NOARGS},
32723281
{"_end_spawned_pthread", end_spawned_pthread, METH_NOARGS},

Python/pystate.c

+6
Original file line numberDiff line numberDiff line change
@@ -2277,12 +2277,18 @@ PyGILState_Release(PyGILState_STATE oldstate)
22772277
/* can't have been locked when we created it */
22782278
assert(oldstate == PyGILState_UNLOCKED);
22792279
// XXX Unbind tstate here.
2280+
// gh-119585: `PyThreadState_Clear()` may call destructors that
2281+
// themselves use PyGILState_Ensure and PyGILState_Release, so make
2282+
// sure that gilstate_counter is not zero when calling it.
2283+
++tstate->gilstate_counter;
22802284
PyThreadState_Clear(tstate);
2285+
--tstate->gilstate_counter;
22812286
/* Delete the thread-state. Note this releases the GIL too!
22822287
* It's vital that the GIL be held here, to avoid shutdown
22832288
* races; see bugs 225673 and 1061968 (that nasty bug has a
22842289
* habit of coming back).
22852290
*/
2291+
assert(tstate->gilstate_counter == 0);
22862292
assert(current_fast_get(runtime) == tstate);
22872293
_PyThreadState_DeleteCurrent(tstate);
22882294
}

0 commit comments

Comments
 (0)