FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

GH-100892: Fix race in clearing `threading.local` by kumaraditya303 · Pull Request #100922 · python/cpython · GitHub

/ cpython Public
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension .c  (2) .py  (1) .rst  (1) All 3 file types selected
Viewed files
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Unified
Split
Hide whitespace
Diff view
Unified
Split
Hide whitespace
13 changes: 13 additions & 0 deletions Lib/test/test_threading_local.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from doctest import DocTestSuite
from test import support
from test.support import threading_helper
from test.support.import_helper import import_module
import weakref

# Modules under test
Expand Down Expand Up @@ -196,6 +197,18 @@ class X:
self.assertIsNone(wr())


def test_threading_local_clear_race(self):
# See https://github.com/python/cpython/issues/100892

_testcapi = import_module('_testcapi')
_testcapi.call_in_temporary_c_thread(lambda: None, False)

for _ in range(1000):
_ = threading.local()

_testcapi.join_temporary_c_thread()


class ThreadLocalTest(unittest.TestCase, BaseLocalTest):
_local = _thread._local

Expand Down
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix race while iterating over thread states in clearing :class:`threading.local`. Patch by Kumar Aditya.
41 changes: 36 additions & 5 deletions Modules/_testcapimodule.c
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -1879,12 +1879,19 @@ temporary_c_thread(void *data)
PyThread_release_lock(test_c_thread->exit_event);
}

static test_c_thread_t test_c_thread;

static PyObject *
call_in_temporary_c_thread(PyObject *self, PyObject *callback)
call_in_temporary_c_thread(PyObject *self, PyObject *args)
{
PyObject *res = NULL;
test_c_thread_t test_c_thread;
PyObject *callback = NULL;
long thread;
int wait = 1;
if (!PyArg_ParseTuple(args, "O|i", &callback, &wait))
{
return NULL;
}

test_c_thread.start_event = PyThread_allocate_lock();
test_c_thread.exit_event = PyThread_allocate_lock();
Expand All @@ -1910,6 +1917,10 @@ call_in_temporary_c_thread(PyObject *self, PyObject *callback)
PyThread_acquire_lock(test_c_thread.start_event, 1);
PyThread_release_lock(test_c_thread.start_event);

if (!wait) {
Py_RETURN_NONE;
}

Py_BEGIN_ALLOW_THREADS
PyThread_acquire_lock(test_c_thread.exit_event, 1);
PyThread_release_lock(test_c_thread.exit_event);
Expand All @@ -1919,13 +1930,32 @@ call_in_temporary_c_thread(PyObject *self, PyObject *callback)

exit:
Py_CLEAR(test_c_thread.callback);
if (test_c_thread.start_event)
if (test_c_thread.start_event) {
PyThread_free_lock(test_c_thread.start_event);
if (test_c_thread.exit_event)
test_c_thread.start_event = NULL;
}
if (test_c_thread.exit_event) {
PyThread_free_lock(test_c_thread.exit_event);
test_c_thread.exit_event = NULL;
}
return res;
}

static PyObject *
join_temporary_c_thread(PyObject *self, PyObject *Py_UNUSED(ignored))
{
Py_BEGIN_ALLOW_THREADS
PyThread_acquire_lock(test_c_thread.exit_event, 1);
PyThread_release_lock(test_c_thread.exit_event);
Py_END_ALLOW_THREADS
Py_CLEAR(test_c_thread.callback);
PyThread_free_lock(test_c_thread.start_event);
test_c_thread.start_event = NULL;
PyThread_free_lock(test_c_thread.exit_event);
test_c_thread.exit_event = NULL;
Py_RETURN_NONE;
}

/* marshal */

static PyObject*
Expand Down Expand Up @@ -3275,8 +3305,9 @@ static PyMethodDef TestMethods[] = {
METH_VARARGS | METH_KEYWORDS},
{"with_tp_del", with_tp_del, METH_VARARGS},
{"create_cfunction", create_cfunction, METH_NOARGS},
{"call_in_temporary_c_thread", call_in_temporary_c_thread, METH_O,
{"call_in_temporary_c_thread", call_in_temporary_c_thread, METH_VARARGS,
PyDoc_STR("set_error_class(error_class) -> None")},
{"join_temporary_c_thread", join_temporary_c_thread, METH_NOARGS},
{"pymarshal_write_long_to_file",
pymarshal_write_long_to_file, METH_VARARGS},
{"pymarshal_write_object_to_file",
Expand Down
30 changes: 20 additions & 10 deletions Modules/_threadmodule.c
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -839,6 +839,11 @@ local_traverse(localobject *self, visitproc visit, void *arg)
return 0;
}
Comment thread
kumaraditya303 marked this conversation as resolved.

#define HEAD_LOCK(runtime) \
Comment thread
kumaraditya303 marked this conversation as resolved.
PyThread_acquire_lock((runtime)->interpreters.mutex, WAIT_LOCK)
#define HEAD_UNLOCK(runtime) \
PyThread_release_lock((runtime)->interpreters.mutex)

static int
local_clear(localobject *self)
{
Expand All @@ -849,18 +854,23 @@ local_clear(localobject *self)
/* Remove all strong references to dummies from the thread states */
if (self->key) {
PyInterpreterState *interp = _PyInterpreterState_GET();
_PyRuntimeState *runtime = &_PyRuntime;
HEAD_LOCK(runtime);
PyThreadState *tstate = PyInterpreterState_ThreadHead(interp);
for(; tstate; tstate = PyThreadState_Next(tstate)) {
if (tstate->dict == NULL) {
continue;
}
PyObject *v = _PyDict_Pop(tstate->dict, self->key, Py_None);
if (v != NULL) {
Py_DECREF(v);
}
else {
PyErr_Clear();
HEAD_UNLOCK(runtime);
while (tstate) {
if (tstate->dict) {
PyObject *v = _PyDict_Pop(tstate->dict, self->key, Py_None);
Comment thread
kumaraditya303 marked this conversation as resolved.
if (v != NULL) {
Py_DECREF(v);
}
else {
PyErr_Clear();
}
}
HEAD_LOCK(runtime);
tstate = PyThreadState_Next(tstate);
HEAD_UNLOCK(runtime);
}
}
return 0;
Expand Down

Back | FazBrowse Home | New Git URL