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]