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