-
-
Notifications
You must be signed in to change notification settings - Fork 32.7k
Description
Bug report
The implementation of Python thread local variables (threading.local()
or _thread._local
) has some thread-safety issues. The issues are particularly relevant to the free-threaded build, but some can affect the default build too.
local_clear loop over threads isn't safe
The local_clear()
function is called when a thread local variable is destroyed. It loops over all threads in the interpreter and removes itself from their dictionaries.
cpython/Modules/_threadmodule.c
Lines 1568 to 1581 in fd0f814
HEAD_LOCK(runtime); | |
PyThreadState *tstate = PyInterpreterState_ThreadHead(interp); | |
HEAD_UNLOCK(runtime); | |
while (tstate) { | |
if (tstate->dict) { | |
if (PyDict_Pop(tstate->dict, self->key, NULL) < 0) { | |
// Silently ignore error | |
PyErr_Clear(); | |
} | |
} | |
HEAD_LOCK(runtime); | |
tstate = PyThreadState_Next(tstate); | |
HEAD_UNLOCK(runtime); | |
} |
This isn't thread-safe because after HEAD_UNLOCK(runtime)
, the stored tstate
might be deleted concurrently. This can happen even in the default build with the GIL enabled because PyThreadState_Delete()
doesn't require the GIL to be held. However, it's less likely to occur in practice because threading
module created threads hold onto the GIL until they're deleted.
local_clear access to tstate->dict
isn't thread-safe
In the free-threaded build, local_clear()
may be run concurrently with some other thread's PyThreadState_Clear()
. The access to another thread's tstate->dict
isn't safe because it may be getting destroyed concurrently.