https://github.com/python/cpython/commit/8123ed12fae89161dbc34cbdc7f743b777b3459c
commit: 8123ed12fae89161dbc34cbdc7f743b777b3459c
branch: main
author: Kumar Aditya <[email protected]>
committer: kumaraditya303 <[email protected]>
date: 2026-08-13T18:52:43+05:30
summary:

gh-154916: Fix data races in GenericAlias using critical sections (#155634)

files:
M Lib/test/test_free_threading/test_types.py
M Objects/genericaliasobject.c

diff --git a/Lib/test/test_free_threading/test_types.py 
b/Lib/test/test_free_threading/test_types.py
index 76fcf1590122f54..96e4ab69a1ad1cc 100644
--- a/Lib/test/test_free_threading/test_types.py
+++ b/Lib/test/test_free_threading/test_types.py
@@ -28,6 +28,34 @@ def refresh():
             *[refresh for _ in range(2)],
         ])
 
+    def test_getitem_parameters_race(self):
+        # gh-153298: ga_getitem() lazily initializes __parameters__;
+        # racing subscriptions must not race on the write or leak.
+        T = TypeVar('T')
+        for _ in range(100):
+            alias = list[T]
+
+            def subscribe():
+                self.assertEqual(alias[int], list[int])
+
+            threading_helper.run_concurrently(subscribe, nthreads=8)
+
+    def test_iter_next_reduce_race(self):
+        # gh-154916: next() clears the iterator's reference to the alias
+        # while __reduce__() reads it; the alias must not be freed in
+        # between (the iterator can hold the last reference).
+        def use(it):
+            it.__reduce__()
+            try:
+                next(it)
+            except StopIteration:
+                pass
+            it.__reduce__()
+
+        for _ in range(100):
+            it = iter(list[int])
+            threading_helper.run_concurrently(use, nthreads=8, args=(it,))
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/Objects/genericaliasobject.c b/Objects/genericaliasobject.c
index 348c7dd6967a397..1504adb950ef44f 100644
--- a/Objects/genericaliasobject.c
+++ b/Objects/genericaliasobject.c
@@ -2,6 +2,7 @@
 
 #include "Python.h"
 #include "pycore_ceval.h"         // _PyEval_GetBuiltin()
+#include "pycore_critical_section.h" // Py_BEGIN_CRITICAL_SECTION()
 #include "pycore_modsupport.h"    // _PyArg_NoKeywords()
 #include "pycore_object.h"
 #include "pycore_typevarobject.h" // _Py_typing_type_repr
@@ -578,19 +579,24 @@ PyDoc_STRVAR(genericalias__doc__,
 "For example, for t = list[int], t.__origin__ is list and t.__args__\n"
 "is (int,).");
 
+static PyObject *
+ga_parameters_lock_held(PyObject *self);
+
 static PyObject *
 ga_getitem(PyObject *self, PyObject *item)
 {
     gaobject *alias = (gaobject *)self;
     // Populate __parameters__ if needed.
-    if (alias->parameters == NULL) {
-        alias->parameters = _Py_make_parameters(alias->args);
-        if (alias->parameters == NULL) {
-            return NULL;
-        }
+    PyObject *parameters;
+    Py_BEGIN_CRITICAL_SECTION(self);
+    parameters = ga_parameters_lock_held(self);
+    Py_END_CRITICAL_SECTION();
+    if (parameters == NULL) {
+        return NULL;
     }
 
-    PyObject *newargs = _Py_subs_parameters(self, alias->args, 
alias->parameters, item);
+    PyObject *newargs = _Py_subs_parameters(self, alias->args, parameters, 
item);
+    Py_DECREF(parameters);
     if (newargs == NULL) {
         return NULL;
     }
@@ -846,6 +852,7 @@ static PyMemberDef ga_members[] = {
 static PyObject *
 ga_parameters_lock_held(PyObject *self)
 {
+    _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self);
     gaobject *alias = (gaobject *)self;
     if (alias->parameters == NULL) {
         alias->parameters = _Py_make_parameters(alias->args);
@@ -942,12 +949,11 @@ static PyObject *
 ga_iternext(PyObject *op)
 {
     gaiterobject *gi = (gaiterobject*)op;
-#ifdef Py_GIL_DISABLED
-    PyObject *obj = _Py_atomic_exchange_ptr(&gi->obj, NULL);
-#else
-    PyObject* obj = gi->obj;
+    PyObject *obj;
+    Py_BEGIN_CRITICAL_SECTION(gi);
+    obj = gi->obj;
     gi->obj = NULL;
-#endif
+    Py_END_CRITICAL_SECTION();
     if (obj == NULL) {
         PyErr_SetNone(PyExc_StopIteration);
         return NULL;
@@ -997,10 +1003,19 @@ ga_iter_reduce(PyObject *self, PyObject 
*Py_UNUSED(ignored))
      * call must be before access of iterator pointers.
      * see issue #101765 */
 
-    if (gi->obj)
-        return Py_BuildValue("N(O)", iter, gi->obj);
-    else
+    PyObject *obj;
+    Py_BEGIN_CRITICAL_SECTION(gi);
+    obj = Py_XNewRef(gi->obj);
+    Py_END_CRITICAL_SECTION();
+
+    if (obj) {
+        PyObject *result = Py_BuildValue("N(O)", iter, obj);
+        Py_DECREF(obj);
+        return result;
+    }
+    else {
         return Py_BuildValue("N(())", iter);
+    }
 }
 
 static PyMethodDef ga_iter_methods[] = {

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to