Skip to content

Commit bcc1be3

Browse files
authored
gh-119585: Fix crash involving PyGILState_Release() and PyThreadState_Clear() (#119753)
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.
1 parent 891c1e3 commit bcc1be3

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
@@ -2888,6 +2888,22 @@ def callback():
28882888
t.start()
28892889
t.join()
28902890

2891+
@threading_helper.reap_threads
2892+
@threading_helper.requires_working_threading()
2893+
def test_thread_gilstate_in_clear(self):
2894+
# See https://github.com/python/cpython/issues/119585
2895+
class C:
2896+
def __del__(self):
2897+
_testcapi.gilstate_ensure_release()
2898+
2899+
# Thread-local variables are destroyed in `PyThreadState_Clear()`.
2900+
local_var = threading.local()
2901+
2902+
def callback():
2903+
local_var.x = C()
2904+
2905+
_testcapi._test_thread_state(callback)
2906+
28912907
@threading_helper.reap_threads
28922908
@threading_helper.requires_working_threading()
28932909
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
@@ -764,6 +764,14 @@ test_thread_state(PyObject *self, PyObject *args)
764764
Py_RETURN_NONE;
765765
}
766766

767+
static PyObject *
768+
gilstate_ensure_release(PyObject *module, PyObject *Py_UNUSED(ignored))
769+
{
770+
PyGILState_STATE state = PyGILState_Ensure();
771+
PyGILState_Release(state);
772+
Py_RETURN_NONE;
773+
}
774+
767775
#ifndef MS_WINDOWS
768776
static PyThread_type_lock wait_done = NULL;
769777

@@ -3351,6 +3359,7 @@ static PyMethodDef TestMethods[] = {
33513359
{"test_get_type_dict", test_get_type_dict, METH_NOARGS},
33523360
{"test_reftracer", test_reftracer, METH_NOARGS},
33533361
{"_test_thread_state", test_thread_state, METH_VARARGS},
3362+
{"gilstate_ensure_release", gilstate_ensure_release, METH_NOARGS},
33543363
#ifndef MS_WINDOWS
33553364
{"_spawn_pthread_waiter", spawn_pthread_waiter, METH_NOARGS},
33563365
{"_end_spawned_pthread", end_spawned_pthread, METH_NOARGS},

Python/pystate.c

+6
Original file line numberDiff line numberDiff line change
@@ -2808,12 +2808,18 @@ PyGILState_Release(PyGILState_STATE oldstate)
28082808
/* can't have been locked when we created it */
28092809
assert(oldstate == PyGILState_UNLOCKED);
28102810
// XXX Unbind tstate here.
2811+
// gh-119585: `PyThreadState_Clear()` may call destructors that
2812+
// themselves use PyGILState_Ensure and PyGILState_Release, so make
2813+
// sure that gilstate_counter is not zero when calling it.
2814+
++tstate->gilstate_counter;
28112815
PyThreadState_Clear(tstate);
2816+
--tstate->gilstate_counter;
28122817
/* Delete the thread-state. Note this releases the GIL too!
28132818
* It's vital that the GIL be held here, to avoid shutdown
28142819
* races; see bugs 225673 and 1061968 (that nasty bug has a
28152820
* habit of coming back).
28162821
*/
2822+
assert(tstate->gilstate_counter == 0);
28172823
assert(current_fast_get() == tstate);
28182824
_PyThreadState_DeleteCurrent(tstate);
28192825
}

0 commit comments

Comments
 (0)