Skip to content

Commit 6954203

Browse files
[3.9] GH-100892: Fix race in clearing threading.local (GH-100922) (#100939)
[3.9] [3.10] GH-100892: Fix race in clearing `threading.local` (GH-100922). (cherry picked from commit 762745a) Co-authored-by: Kumar Aditya <[email protected]>. (cherry picked from commit 683e9fe) Co-authored-by: Kumar Aditya <[email protected]>
1 parent 6be2e0e commit 6954203

File tree

4 files changed

+75
-13
lines changed

4 files changed

+75
-13
lines changed

Lib/test/test_threading_local.py

+16
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,22 @@ class X:
193193
self.assertIsNone(wr())
194194

195195

196+
def test_threading_local_clear_race(self):
197+
# See https://github.com/python/cpython/issues/100892
198+
199+
try:
200+
import _testcapi
201+
except ImportError:
202+
unittest.skip("requires _testcapi")
203+
204+
_testcapi.call_in_temporary_c_thread(lambda: None, False)
205+
206+
for _ in range(1000):
207+
_ = threading.local()
208+
209+
_testcapi.join_temporary_c_thread()
210+
211+
196212
class ThreadLocalTest(unittest.TestCase, BaseLocalTest):
197213
_local = _thread._local
198214

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

+36-5
Original file line numberDiff line numberDiff line change
@@ -4239,12 +4239,19 @@ temporary_c_thread(void *data)
42394239
PyThread_exit_thread();
42404240
}
42414241

4242+
static test_c_thread_t test_c_thread;
4243+
42424244
static PyObject *
4243-
call_in_temporary_c_thread(PyObject *self, PyObject *callback)
4245+
call_in_temporary_c_thread(PyObject *self, PyObject *args)
42444246
{
42454247
PyObject *res = NULL;
4246-
test_c_thread_t test_c_thread;
4248+
PyObject *callback = NULL;
42474249
long thread;
4250+
int wait = 1;
4251+
if (!PyArg_ParseTuple(args, "O|i", &callback, &wait))
4252+
{
4253+
return NULL;
4254+
}
42484255

42494256
test_c_thread.start_event = PyThread_allocate_lock();
42504257
test_c_thread.exit_event = PyThread_allocate_lock();
@@ -4271,6 +4278,10 @@ call_in_temporary_c_thread(PyObject *self, PyObject *callback)
42714278
PyThread_acquire_lock(test_c_thread.start_event, 1);
42724279
PyThread_release_lock(test_c_thread.start_event);
42734280

4281+
if (!wait) {
4282+
Py_RETURN_NONE;
4283+
}
4284+
42744285
Py_BEGIN_ALLOW_THREADS
42754286
PyThread_acquire_lock(test_c_thread.exit_event, 1);
42764287
PyThread_release_lock(test_c_thread.exit_event);
@@ -4281,13 +4292,32 @@ call_in_temporary_c_thread(PyObject *self, PyObject *callback)
42814292

42824293
exit:
42834294
Py_CLEAR(test_c_thread.callback);
4284-
if (test_c_thread.start_event)
4295+
if (test_c_thread.start_event) {
42854296
PyThread_free_lock(test_c_thread.start_event);
4286-
if (test_c_thread.exit_event)
4297+
test_c_thread.start_event = NULL;
4298+
}
4299+
if (test_c_thread.exit_event) {
42874300
PyThread_free_lock(test_c_thread.exit_event);
4301+
test_c_thread.exit_event = NULL;
4302+
}
42884303
return res;
42894304
}
42904305

4306+
static PyObject *
4307+
join_temporary_c_thread(PyObject *self, PyObject *Py_UNUSED(ignored))
4308+
{
4309+
Py_BEGIN_ALLOW_THREADS
4310+
PyThread_acquire_lock(test_c_thread.exit_event, 1);
4311+
PyThread_release_lock(test_c_thread.exit_event);
4312+
Py_END_ALLOW_THREADS
4313+
Py_CLEAR(test_c_thread.callback);
4314+
PyThread_free_lock(test_c_thread.start_event);
4315+
test_c_thread.start_event = NULL;
4316+
PyThread_free_lock(test_c_thread.exit_event);
4317+
test_c_thread.exit_event = NULL;
4318+
Py_RETURN_NONE;
4319+
}
4320+
42914321
/* marshal */
42924322

42934323
static PyObject*
@@ -5532,8 +5562,9 @@ static PyMethodDef TestMethods[] = {
55325562
{"docstring_with_signature_with_defaults",
55335563
(PyCFunction)test_with_docstring, METH_NOARGS,
55345564
docstring_with_signature_with_defaults},
5535-
{"call_in_temporary_c_thread", call_in_temporary_c_thread, METH_O,
5565+
{"call_in_temporary_c_thread", call_in_temporary_c_thread, METH_VARARGS,
55365566
PyDoc_STR("set_error_class(error_class) -> None")},
5567+
{"join_temporary_c_thread", join_temporary_c_thread, METH_NOARGS},
55375568
{"pymarshal_write_long_to_file",
55385569
pymarshal_write_long_to_file, METH_VARARGS},
55395570
{"pymarshal_write_object_to_file",

Modules/_threadmodule.c

+22-8
Original file line numberDiff line numberDiff line change
@@ -801,6 +801,11 @@ local_traverse(localobject *self, visitproc visit, void *arg)
801801
return 0;
802802
}
803803

804+
#define HEAD_LOCK(runtime) \
805+
PyThread_acquire_lock((runtime)->interpreters.mutex, WAIT_LOCK)
806+
#define HEAD_UNLOCK(runtime) \
807+
PyThread_release_lock((runtime)->interpreters.mutex)
808+
804809
static int
805810
local_clear(localobject *self)
806811
{
@@ -810,17 +815,26 @@ local_clear(localobject *self)
810815
Py_CLEAR(self->dummies);
811816
Py_CLEAR(self->wr_callback);
812817
/* Remove all strong references to dummies from the thread states */
813-
if (self->key
814-
&& (tstate = PyThreadState_Get())
815-
&& tstate->interp) {
816-
for(tstate = PyInterpreterState_ThreadHead(tstate->interp);
817-
tstate;
818-
tstate = PyThreadState_Next(tstate))
819-
if (tstate->dict && PyDict_GetItem(tstate->dict, self->key)) {
820-
if (PyDict_DelItem(tstate->dict, self->key)) {
818+
if (self->key) {
819+
PyInterpreterState *interp = _PyInterpreterState_GET();
820+
_PyRuntimeState *runtime = &_PyRuntime;
821+
HEAD_LOCK(runtime);
822+
PyThreadState *tstate = PyInterpreterState_ThreadHead(interp);
823+
HEAD_UNLOCK(runtime);
824+
while (tstate) {
825+
if (tstate->dict) {
826+
PyObject *v = _PyDict_Pop(tstate->dict, self->key, Py_None);
827+
if (v != NULL) {
828+
Py_DECREF(v);
829+
}
830+
else {
821831
PyErr_Clear();
822832
}
823833
}
834+
HEAD_LOCK(runtime);
835+
tstate = PyThreadState_Next(tstate);
836+
HEAD_UNLOCK(runtime);
837+
}
824838
}
825839
return 0;
826840
}

0 commit comments

Comments
 (0)