| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -3,6 +3,7 @@ | |||
| 3 | 3 | from doctest import DocTestSuite | |
| 4 | 4 | from test import support | |
| 5 | 5 | from test.support import threading_helper | |
| 6 | + from test.support.import_helper import import_module | ||
| 6 | 7 | import weakref | |
| 7 | 8 | ||
| 8 | 9 | # Modules under test | |
@@ -196,6 +197,18 @@ class X: | |||
| 196 | 197 | self.assertIsNone(wr()) | |
| 197 | 198 | ||
| 198 | 199 | ||
| 200 | + def test_threading_local_clear_race(self): | ||
| 201 | + # See https://github.com/python/cpython/issues/100892 | ||
| 202 | + | ||
| 203 | + _testcapi = import_module('_testcapi') | ||
| 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 | + | ||
| 199 | 212 | class ThreadLocalTest(unittest.TestCase, BaseLocalTest): | |
| 200 | 213 | _local = _thread._local | |
| 201 | 214 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1 @@ | |||
| 1 | + Fix race while iterating over thread states in clearing :class:`threading.local`. Patch by Kumar Aditya. | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1879,12 +1879,19 @@ temporary_c_thread(void *data) | |||
| 1879 | 1879 | PyThread_release_lock(test_c_thread->exit_event); | |
| 1880 | 1880 | } | |
| 1881 | 1881 | ||
| 1882 | + static test_c_thread_t test_c_thread; | ||
| 1883 | + | ||
| 1882 | 1884 | static PyObject * | |
| 1883 | - call_in_temporary_c_thread(PyObject *self, PyObject *callback) | ||
| 1885 | + call_in_temporary_c_thread(PyObject *self, PyObject *args) | ||
| 1884 | 1886 | { | |
| 1885 | 1887 | PyObject *res = NULL; | |
| 1886 | - test_c_thread_t test_c_thread; | ||
| 1888 | + PyObject *callback = NULL; | ||
| 1887 | 1889 | long thread; | |
| 1890 | + int wait = 1; | ||
| 1891 | + if (!PyArg_ParseTuple(args, "O|i", &callback, &wait)) | ||
| 1892 | + { | ||
| 1893 | + return NULL; | ||
| 1894 | + } | ||
| 1888 | 1895 | ||
| 1889 | 1896 | test_c_thread.start_event = PyThread_allocate_lock(); | |
| 1890 | 1897 | test_c_thread.exit_event = PyThread_allocate_lock(); | |
@@ -1910,6 +1917,10 @@ call_in_temporary_c_thread(PyObject *self, PyObject *callback) | |||
| 1910 | 1917 | PyThread_acquire_lock(test_c_thread.start_event, 1); | |
| 1911 | 1918 | PyThread_release_lock(test_c_thread.start_event); | |
| 1912 | 1919 | ||
| 1920 | + if (!wait) { | ||
| 1921 | + Py_RETURN_NONE; | ||
| 1922 | + } | ||
| 1923 | + | ||
| 1913 | 1924 | Py_BEGIN_ALLOW_THREADS | |
| 1914 | 1925 | PyThread_acquire_lock(test_c_thread.exit_event, 1); | |
| 1915 | 1926 | PyThread_release_lock(test_c_thread.exit_event); | |
@@ -1919,13 +1930,32 @@ call_in_temporary_c_thread(PyObject *self, PyObject *callback) | |||
| 1919 | 1930 | ||
| 1920 | 1931 | exit: | |
| 1921 | 1932 | Py_CLEAR(test_c_thread.callback); | |
| 1922 | - if (test_c_thread.start_event) | ||
| 1933 | + if (test_c_thread.start_event) { | ||
| 1923 | 1934 | PyThread_free_lock(test_c_thread.start_event); | |
| 1924 | - if (test_c_thread.exit_event) | ||
| 1935 | + test_c_thread.start_event = NULL; | ||
| 1936 | + } | ||
| 1937 | + if (test_c_thread.exit_event) { | ||
| 1925 | 1938 | PyThread_free_lock(test_c_thread.exit_event); | |
| 1939 | + test_c_thread.exit_event = NULL; | ||
| 1940 | + } | ||
| 1926 | 1941 | return res; | |
| 1927 | 1942 | } | |
| 1928 | 1943 | ||
| 1944 | + static PyObject * | ||
| 1945 | + join_temporary_c_thread(PyObject *self, PyObject *Py_UNUSED(ignored)) | ||
| 1946 | + { | ||
| 1947 | + Py_BEGIN_ALLOW_THREADS | ||
| 1948 | + PyThread_acquire_lock(test_c_thread.exit_event, 1); | ||
| 1949 | + PyThread_release_lock(test_c_thread.exit_event); | ||
| 1950 | + Py_END_ALLOW_THREADS | ||
| 1951 | + Py_CLEAR(test_c_thread.callback); | ||
| 1952 | + PyThread_free_lock(test_c_thread.start_event); | ||
| 1953 | + test_c_thread.start_event = NULL; | ||
| 1954 | + PyThread_free_lock(test_c_thread.exit_event); | ||
| 1955 | + test_c_thread.exit_event = NULL; | ||
| 1956 | + Py_RETURN_NONE; | ||
| 1957 | + } | ||
| 1958 | + | ||
| 1929 | 1959 | /* marshal */ | |
| 1930 | 1960 | ||
| 1931 | 1961 | static PyObject* | |
@@ -3275,8 +3305,9 @@ static PyMethodDef TestMethods[] = { | |||
| 3275 | 3305 | METH_VARARGS | METH_KEYWORDS}, | |
| 3276 | 3306 | {"with_tp_del", with_tp_del, METH_VARARGS}, | |
| 3277 | 3307 | {"create_cfunction", create_cfunction, METH_NOARGS}, | |
| 3278 | - {"call_in_temporary_c_thread", call_in_temporary_c_thread, METH_O, | ||
| 3308 | + {"call_in_temporary_c_thread", call_in_temporary_c_thread, METH_VARARGS, | ||
| 3279 | 3309 | PyDoc_STR("set_error_class(error_class) -> None")}, | |
| 3310 | + {"join_temporary_c_thread", join_temporary_c_thread, METH_NOARGS}, | ||
| 3280 | 3311 | {"pymarshal_write_long_to_file", | |
| 3281 | 3312 | pymarshal_write_long_to_file, METH_VARARGS}, | |
| 3282 | 3313 | {"pymarshal_write_object_to_file", | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -839,6 +839,11 @@ local_traverse(localobject *self, visitproc visit, void *arg) | |||
| 839 | 839 | return 0; | |
| 840 | 840 | } | |
| 841 | 841 | ||
| 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 | + | ||
| 842 | 847 | static int | |
| 843 | 848 | local_clear(localobject *self) | |
| 844 | 849 | { | |
@@ -849,18 +854,23 @@ local_clear(localobject *self) | |||
| 849 | 854 | /* Remove all strong references to dummies from the thread states */ | |
| 850 | 855 | if (self->key) { | |
| 851 | 856 | PyInterpreterState *interp = _PyInterpreterState_GET(); | |
| 857 | + _PyRuntimeState *runtime = &_PyRuntime; | ||
| 858 | + HEAD_LOCK(runtime); | ||
| 852 | 859 | 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 | + Py_DECREF(v); | ||
| 866 | + } | ||
| 867 | + else { | ||
| 868 | + PyErr_Clear(); | ||
| 869 | + } | ||
| 863 | 870 | } | |
| 871 | + HEAD_LOCK(runtime); | ||
| 872 | + tstate = PyThreadState_Next(tstate); | ||
| 873 | + HEAD_UNLOCK(runtime); | ||
| 864 | 874 | } | |
| 865 | 875 | } | |
| 866 | 876 | return 0; | |
| Back | FazBrowse Home | New Git URL |
0 commit comments