| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 5ef90ee commit 7329213
4 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -193,6 +193,22 @@ class X: | |||
| 193 | 193 | self.assertIsNone(wr()) | |
| 194 | 194 | ||
| 195 | 195 | ||
| 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 | + | ||
| 196 | 212 | class ThreadLocalTest(unittest.TestCase, BaseLocalTest): | |
| 197 | 213 | _local = _thread._local | |
| 198 | 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 | |
|---|---|---|---|
@@ -4239,12 +4239,19 @@ temporary_c_thread(void *data) | |||
| 4239 | 4239 | PyThread_exit_thread(); | |
| 4240 | 4240 | } | |
| 4241 | 4241 | ||
| 4242 | + static test_c_thread_t test_c_thread; | ||
| 4243 | + | ||
| 4242 | 4244 | static PyObject * | |
| 4243 | - call_in_temporary_c_thread(PyObject *self, PyObject *callback) | ||
| 4245 | + call_in_temporary_c_thread(PyObject *self, PyObject *args) | ||
| 4244 | 4246 | { | |
| 4245 | 4247 | PyObject *res = NULL; | |
| 4246 | - test_c_thread_t test_c_thread; | ||
| 4248 | + PyObject *callback = NULL; | ||
| 4247 | 4249 | long thread; | |
| 4250 | + int wait = 1; | ||
| 4251 | + if (!PyArg_ParseTuple(args, "O|i", &callback, &wait)) | ||
| 4252 | + { | ||
| 4253 | + return NULL; | ||
| 4254 | + } | ||
| 4248 | 4255 | ||
| 4249 | 4256 | test_c_thread.start_event = PyThread_allocate_lock(); | |
| 4250 | 4257 | test_c_thread.exit_event = PyThread_allocate_lock(); | |
@@ -4271,6 +4278,10 @@ call_in_temporary_c_thread(PyObject *self, PyObject *callback) | |||
| 4271 | 4278 | PyThread_acquire_lock(test_c_thread.start_event, 1); | |
| 4272 | 4279 | PyThread_release_lock(test_c_thread.start_event); | |
| 4273 | 4280 | ||
| 4281 | + if (!wait) { | ||
| 4282 | + Py_RETURN_NONE; | ||
| 4283 | + } | ||
| 4284 | + | ||
| 4274 | 4285 | Py_BEGIN_ALLOW_THREADS | |
| 4275 | 4286 | PyThread_acquire_lock(test_c_thread.exit_event, 1); | |
| 4276 | 4287 | PyThread_release_lock(test_c_thread.exit_event); | |
@@ -4281,13 +4292,32 @@ call_in_temporary_c_thread(PyObject *self, PyObject *callback) | |||
| 4281 | 4292 | ||
| 4282 | 4293 | exit: | |
| 4283 | 4294 | Py_CLEAR(test_c_thread.callback); | |
| 4284 | - if (test_c_thread.start_event) | ||
| 4295 | + if (test_c_thread.start_event) { | ||
| 4285 | 4296 | 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) { | ||
| 4287 | 4300 | PyThread_free_lock(test_c_thread.exit_event); | |
| 4301 | + test_c_thread.exit_event = NULL; | ||
| 4302 | + } | ||
| 4288 | 4303 | return res; | |
| 4289 | 4304 | } | |
| 4290 | 4305 | ||
| 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 | + | ||
| 4291 | 4321 | /* marshal */ | |
| 4292 | 4322 | ||
| 4293 | 4323 | static PyObject* | |
@@ -5532,8 +5562,9 @@ static PyMethodDef TestMethods[] = { | |||
| 5532 | 5562 | {"docstring_with_signature_with_defaults", | |
| 5533 | 5563 | (PyCFunction)test_with_docstring, METH_NOARGS, | |
| 5534 | 5564 | 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, | ||
| 5536 | 5566 | PyDoc_STR("set_error_class(error_class) -> None")}, | |
| 5567 | + {"join_temporary_c_thread", join_temporary_c_thread, METH_NOARGS}, | ||
| 5537 | 5568 | {"pymarshal_write_long_to_file", | |
| 5538 | 5569 | pymarshal_write_long_to_file, METH_VARARGS}, | |
| 5539 | 5570 | {"pymarshal_write_object_to_file", | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -801,6 +801,11 @@ local_traverse(localobject *self, visitproc visit, void *arg) | |||
| 801 | 801 | return 0; | |
| 802 | 802 | } | |
| 803 | 803 | ||
| 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 | + | ||
| 804 | 809 | static int | |
| 805 | 810 | local_clear(localobject *self) | |
| 806 | 811 | { | |
@@ -810,17 +815,26 @@ local_clear(localobject *self) | |||
| 810 | 815 | Py_CLEAR(self->dummies); | |
| 811 | 816 | Py_CLEAR(self->wr_callback); | |
| 812 | 817 | /* 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 { | ||
| 821 | 831 | PyErr_Clear(); | |
| 822 | 832 | } | |
| 823 | 833 | } | |
| 834 | + HEAD_LOCK(runtime); | ||
| 835 | + tstate = PyThreadState_Next(tstate); | ||
| 836 | + HEAD_UNLOCK(runtime); | ||
| 837 | + } | ||
| 824 | 838 | } | |
| 825 | 839 | return 0; | |
| 826 | 840 | } | |
| Back | FazBrowse Home | New Git URL |
0 commit comments