Skip to content

threading.local() implementation is not thread-safe in the free-threaded build #120973

@colesbury

Description

@colesbury

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.

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.

Linked PRs

Metadata

Metadata

Assignees

Labels

3.13bugs and security fixes3.14bugs and security fixestopic-free-threadingtype-bugAn unexpected behavior, bug, or error

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions