Bug report
Bug description:
In the free-threaded build, reading __parameters__ on a shared types.GenericAlias (e.g. list[T]) from multiple threads races on the lazy creation of the cached parameters tuple.
ga_parameters checks and fills alias->parameters with plain accesses.
|
static PyObject * |
|
ga_parameters(PyObject *self, void *unused) |
|
{ |
|
gaobject *alias = (gaobject *)self; |
|
if (alias->parameters == NULL) { |
|
alias->parameters = _Py_make_parameters(alias->args); |
|
if (alias->parameters == NULL) { |
|
return NULL; |
|
} |
|
} |
|
return Py_NewRef(alias->parameters); |
Two threads that both observe parameters == NULL both compute and store the tuple, racing on the cache pointer.
Reproducer:
from threading import Thread
from typing import TypeVar
T = TypeVar('T')
slot = [list[T]]
def thread1():
for _ in range(20000):
try:
_ = slot[0].__parameters__
except Exception:
pass
def thread2():
for _ in range(20000):
slot[0] = list[T] # fresh GenericAlias (parameters == NULL)
threads = [Thread(target=thread1) for _ in range(6)]
threads += [Thread(target=thread2) for _ in range(2)]
for t in threads: t.start()
for t in threads: t.join()
TSAN Report:
==================
WARNING: ThreadSanitizer: data race (pid=48591)
Write of size 8 at 0x7fffd00d0580 by thread T4:
#0 ga_parameters /cpython/Objects/genericaliasobject.c:848:27
#1 getset_get /cpython/Objects/descrobject.c:194:16
#2 _PyObject_GenericGetAttrWithDict /cpython/Objects/object.c:1926:19
#3 PyObject_GenericGetAttr /cpython/Objects/object.c:2012:12
#4 ga_getattro /cpython/Objects/genericaliasobject.c:708:12
#5 _PyObject_GetAttrStackRef /cpython/Objects/object.c:1369:18
#6 _PyEval_EvalFrameDefault /cpython/Python/generated_cases.c.h:8786:28
...
Previous read of size 8 at 0x7fffd00d0580 by thread T3:
#0 ga_parameters /cpython/Objects/genericaliasobject.c:847:16
#1 getset_get /cpython/Objects/descrobject.c:194:16
#2 _PyObject_GenericGetAttrWithDict /cpython/Objects/object.c:1926:19
#3 PyObject_GenericGetAttr /cpython/Objects/object.c:2012:12
#4 ga_getattro /cpython/Objects/genericaliasobject.c:708:12
#5 _PyObject_GetAttrStackRef /cpython/Objects/object.c:1369:18
#6 _PyEval_EvalFrameDefault /cpython/Python/generated_cases.c.h:8786:28
...
SUMMARY: ThreadSanitizer: data race /cpython/Objects/genericaliasobject.c:848:27 in ga_parameters
==================
CPython versions tested on:
CPython main branch
Operating systems tested on:
Linux
Bug report
Bug description:
In the free-threaded build, reading
__parameters__on a sharedtypes.GenericAlias(e.g.list[T]) from multiple threads races on the lazy creation of the cachedparameterstuple.ga_parameterschecks and fillsalias->parameterswith plain accesses.cpython/Objects/genericaliasobject.c
Lines 843 to 853 in a74280e
Two threads that both observe
parameters == NULLboth compute and store the tuple, racing on the cache pointer.Reproducer:
TSAN Report:
CPython versions tested on:
CPython main branch
Operating systems tested on:
Linux