8000 [3.11] GH-100892: Fix race in clearing `threading.local` (GH-100922).… · python/cpython@e707671 · GitHub
[go: up one dir, main page]

Skip to content

Commit e707671

Browse files
[3.11] GH-100892: Fix race in clearing threading.local (GH-100922). (#100937)
(cherry picked from commit 762745a) Co-authored-by: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com>
1 parent 1b2459d commit e707671

File tree

4 files changed

+73
-19
lines changed

4 files changed

+73
-19
lines changed

Lib/test/test_threading_local.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from doctest import DocTestSuite
44
from test import support
55
from test.support import threading_helper
6+
from test.support.import_helper import import_module
67
import weakref
78
import gc
89

@@ -197,6 +198,18 @@ class X:
197198
self.assertIsNone(wr())
198199

199200

201+
def test_threading_local_clear_race(self):
202+
# See https://github.com/python/cpython/issues/100892
203+
204+
_testcapi = import_module('_testcapi')
205+
_testcapi.call_in_temporary_c_thread(lambda: None, False)
206+
207+
for _ in range(1000):
208+
_ = threading.local()
209+
210+
_testcapi.join_temporary_c_thread()
211+
212+
200213
class ThreadLocalTest(unittest.TestCase, BaseLocalTest):
201214
_local = _thread._local
202215

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix race while iterating over thread states in clearing :class:`threading.local`. Patch by Kumar Aditya.

Modules/_testcapimodule.c

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2746,6 +2746,7 @@ get_date_fromdate(PyObject *self, PyObject *args)
27462746
return rv;
27472747
}
27482748

2749+
27492750
static PyObject *
27502751
get_datetime_fromdateandtime(PyObject *self, PyObject *args)
27512752
{
@@ -4526,12 +4527,19 @@ temporary_c_thread(void *data)
45264527
PyThread_release_lock(test_c_thread->exit_event);
45274528
}
45284529

4530+
static test_c_thread_t test_c_thread;
4531+
45294532
static PyObject *
4530-
call_in_temporary_c_thread(PyObject *self, PyObject *callback)
4533+
call_in_temporary_c_thread(PyObject *self, PyObject *args)
45314534
{
45324535
PyObject *res = NULL;
4533-
test_c_thread_t test_c_thread;
4536+
PyObject *callback = NULL;
45344537
long thread;
4538+
int wait = 1;
4539+
if (!PyArg_ParseTuple(args, "O|i", &callback, &wait))
4540+
{
4541+
return NULL;
4542+
}
45354543

45364544
test_c_thread.start_event = PyThread_allocate_lock();
45374545
test_c_thread.exit_event = PyThread_allocate_lock();
@@ -4541,8 +4549,7 @@ call_in_temporary_c_thread(PyObject *self, PyObject *callback)
45414549
goto exit;
45424550
}
45434551

4544-
Py_INCREF(callback);
4545-
test_c_thread.callback = callback;
4552+
test_c_thread.callback = Py_NewRef(callback);
45464553

45474554
PyThread_acquire_lock(test_c_thread.start_event, 1);
45484555
PyThread_acquire_lock(test_c_thread.exit_event, 1);
@@ -4558,23 +4565,45 @@ call_in_temporary_c_thread(PyObject *self, PyObject *callback)
45584565
PyThread_acquire_lock(test_c_thread.start_event, 1);
45594566
PyThread_release_lock(test_c_thread.start_event);
45604567

4568+
if (!wait) {
4569+
Py_RETURN_NONE;
4570+
}
4571+
45614572
Py_BEGIN_ALLOW_THREADS
45624573
PyThread_acquire_lock(test_c_thread.exit_event, 1);
45634574
PyThread_release_lock(test_c_thread.exit_event);
45644575
Py_END_ALLOW_THREADS
45654576

4566-
Py_INCREF(Py_None);
4567-
res = Py_None;
4577+
res = Py_NewRef(Py_None);
45684578

45694579
exit:
45704580
Py_CLEAR(test_c_thread.callback);
4571-
if (test_c_thread.start_event)
4581+
if (test_c_thread.start_event) {
45724582
PyThread_free_lock(test_c_thread.start_event);
4573-
if (test_c_thread.exit_event)
4583+
test_c_thread.start_event = NULL;
4584+
}
4585+
if (test_c_thread.exit_event) {
45744586
PyThread_free_lock(test_c_thread.exit_event);
4587+
test_c_thread.exit_event = NULL;
4588+
}
45754589
return res;
45764590
}
45774591

4592+
static PyObject *
4593+
join_temporary_c_thread(PyObject *self, PyObject *Py_UNUSED(ignored))
4594+
{
4595+
Py_BEGIN_ALLOW_THREADS
4596+
PyThread_acquire_lock(test_c_thread.exit_event, 1);
4597+
PyThread_release_lock(test_c_thread.exit_event);
4598+
Py_END_ALLOW_THREADS
4599+
Py_CLEAR(test_c_thread.callback);
4600+
PyThread_free_lock(test_c_thread.start_event);
4601+
test_c_thread.start_event = NULL;
4602+
PyThread_free_lock(test_c_thread.exit_event);
4603+
test_c_thread.exit_event = NULL;
4604+
Py_RETURN_NONE;
4605+
}
4606+
45784607
/* marshal */
45794608

45804609
static PyObject*
@@ -6418,8 +6447,9 @@ static PyMethodDef TestMethods[] = {
64186447
{"docstring_with_signature_with_defaults",
64196448
(PyCFunction)test_with_docstring, METH_NOARGS,
64206449
docstring_with_signature_with_defaults},
6421-
{"call_in_temporary_c_thread", call_in_temporary_c_thread, METH_O,
6450+
{"call_in_temporary_c_thread", call_in_temporary_c_thread, METH_VARARGS,
64226451
PyDoc_STR("set_error_class(error_class) -> None")},
6452+
{"join_temporary_c_thread", join_temporary_c_thread, METH_NOARGS},
64236453
{"pymarshal_write_long_to_file",
64246454
pymarshal_write_long_to_file, METH_VARARGS},
64256455
{"pymarshal_write_object_to_file",

Modules/_threadmodule.c

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -839,6 +839,11 @@ local_traverse(localobject *self, visitproc visit, void *arg)
839839
return 0;
840840
}
841841

842+
#define HEAD_LOCK(runtime) \
843+
PyThread_acquire_lock((runtime)->interpreters.mutex, WAIT_LOCK)
844+
#define HEAD_UNLOCK(runtime) \
845+
PyThread_release_lock((runtime)->interpreters.mutex)
846+
842847
static int
843848
local_clear(localobject *self)
844849
{
@@ -849,18 +854,23 @@ local_clear(localobject *self)
849854
/* Remove all strong references to dummies from the thread states */
850855
if (self->key) {
851856
PyInterpreterState *interp = _PyInterpreterState_GET();
857+
_PyRuntimeState *runtime = &_PyRuntime;
858+
HEAD_LOCK(runtime);
852859
PyThreadState *tstate = PyInterpreterState_ThreadHead(interp);
853-
for(; tstate; tstate = PyThreadState_Next(tstate)) {
854-
if (tstate->dict == NULL) {
855-
continue;
856-
}
857-
PyObject *v = _PyDict_Pop(tstate->dict, self->key, Py_None);
858-
if (v != NULL) {
859-
Py_DECREF(v);
860-
}
861-
else {
862-
PyErr_Clear();
860+
HEAD_UNLOCK(runtime);
861+
while (tstate) {
862+
if (tstate->dict) {
863+
PyObject *v = _PyDict_Pop(tstate->dict, self->key, Py_None);
864+
if (v != NULL) {
865+
7547 Py_DECREF(v);
866+
}
867+
else {
868+
PyErr_Clear();
869+
}
863870
}
871+
HEAD_LOCK(runtime);
872+
tstate = PyThreadState_Next(tstate);
873+
HEAD_UNLOCK(runtime);
864874
}
865875
}
866876
return 0;

0 commit comments

Comments
 (0)
0