Skip to content

Commit f611ac3

Browse files
gh-154916: Fix data races in GenericAlias using critical sections (#155634)
(cherry picked from commit 8123ed1)
1 parent ed38a08 commit f611ac3

2 files changed

Lines changed: 61 additions & 12 deletions

File tree

Lib/test/test_free_threading/test_types.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,34 @@ def refresh():
2828
*[refresh for _ in range(2)],
2929
])
3030

31+
def test_getitem_parameters_race(self):
32+
# gh-153298: ga_getitem() lazily initializes __parameters__;
33+
# racing subscriptions must not race on the write or leak.
34+
T = TypeVar('T')
35+
for _ in range(100):
36+
alias = list[T]
37+
38+
def subscribe():
39+
self.assertEqual(alias[int], list[int])
40+
41+
threading_helper.run_concurrently(subscribe, nthreads=8)
42+
43+
def test_iter_next_reduce_race(self):
44+
# gh-154916: next() clears the iterator's reference to the alias
45+
# while __reduce__() reads it; the alias must not be freed in
46+
# between (the iterator can hold the last reference).
47+
def use(it):
48+
it.__reduce__()
49+
try:
50+
next(it)
51+
except StopIteration:
52+
pass
53+
it.__reduce__()
54+
55+
for _ in range(100):
56+
it = iter(list[int])
57+
threading_helper.run_concurrently(use, nthreads=8, args=(it,))
58+
3159

3260
if __name__ == "__main__":
3361
unittest.main()

Objects/genericaliasobject.c

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include "Python.h"
44
#include "pycore_ceval.h" // _PyEval_GetBuiltin()
5+
#include "pycore_critical_section.h" // Py_BEGIN_CRITICAL_SECTION()
56
#include "pycore_modsupport.h" // _PyArg_NoKeywords()
67
#include "pycore_object.h"
78
#include "pycore_typevarobject.h" // _Py_typing_type_repr
@@ -578,19 +579,24 @@ PyDoc_STRVAR(genericalias__doc__,
578579
"For example, for t = list[int], t.__origin__ is list and t.__args__\n"
579580
"is (int,).");
580581

582+
static PyObject *
583+
ga_parameters_lock_held(PyObject *self);
584+
581585
static PyObject *
582586
ga_getitem(PyObject *self, PyObject *item)
583587
{
584588
gaobject *alias = (gaobject *)self;
585589
// Populate __parameters__ if needed.
586-
if (alias->parameters == NULL) {
587-
alias->parameters = _Py_make_parameters(alias->args);
588-
if (alias->parameters == NULL) {
589-
return NULL;
590-
}
590+
PyObject *parameters;
591+
Py_BEGIN_CRITICAL_SECTION(self);
592+
parameters = ga_parameters_lock_held(self);
593+
Py_END_CRITICAL_SECTION();
594+
if (parameters == NULL) {
595+
return NULL;
591596
}
592597

593-
PyObject *newargs = _Py_subs_parameters(self, alias->args, alias->parameters, item);
598+
PyObject *newargs = _Py_subs_parameters(self, alias->args, parameters, item);
599+
Py_DECREF(parameters);
594600
if (newargs == NULL) {
595601
return NULL;
596602
}
@@ -846,6 +852,7 @@ static PyMemberDef ga_members[] = {
846852
static PyObject *
847853
ga_parameters_lock_held(PyObject *self)
848854
{
855+
_Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self);
849856
gaobject *alias = (gaobject *)self;
850857
if (alias->parameters == NULL) {
851858
alias->parameters = _Py_make_parameters(alias->args);
@@ -942,17 +949,22 @@ static PyObject *
942949
ga_iternext(PyObject *op)
943950
{
944951
gaiterobject *gi = (gaiterobject*)op;
945-
if (gi->obj == NULL) {
952+
PyObject *obj;
953+
Py_BEGIN_CRITICAL_SECTION(gi);
954+
obj = gi->obj;
955+
gi->obj = NULL;
956+
Py_END_CRITICAL_SECTION();
957+
if (obj == NULL) {
946958
PyErr_SetNone(PyExc_StopIteration);
947959
return NULL;
948960
}
949-
gaobject *alias = (gaobject *)gi->obj;
961+
gaobject *alias = (gaobject *)obj;
950962
PyObject *starred_alias = Py_GenericAlias(alias->origin, alias->args);
963+
Py_DECREF(obj);
951964
if (starred_alias == NULL) {
952965
return NULL;
953966
}
954967
((gaobject *)starred_alias)->starred = true;
955-
Py_SETREF(gi->obj, NULL);
956968
return starred_alias;
957969
}
958970

@@ -991,10 +1003,19 @@ ga_iter_reduce(PyObject *self, PyObject *Py_UNUSED(ignored))
9911003
* call must be before access of iterator pointers.
9921004
* see issue #101765 */
9931005

994-
if (gi->obj)
995-
return Py_BuildValue("N(O)", iter, gi->obj);
996-
else
1006+
PyObject *obj;
1007+
Py_BEGIN_CRITICAL_SECTION(gi);
1008+
obj = Py_XNewRef(gi->obj);
1009+
Py_END_CRITICAL_SECTION();
1010+
1011+
if (obj) {
1012+
PyObject *result = Py_BuildValue("N(O)", iter, obj);
1013+
Py_DECREF(obj);
1014+
return result;
1015+
}
1016+
else {
9971017
return Py_BuildValue("N(())", iter);
1018+
}
9981019
}
9991020

10001021
static PyMethodDef ga_iter_methods[] = {

0 commit comments

Comments
 (0)