Skip to content

Data race creating types.GenericAlias.__parameters__ lazily under free-threading #153298

Description

@Naserume

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions