Skip to content

Commit 2540258

Browse files
[3.15] gh-154916: Fix data races in GenericAlias using critical sections (GH-155634) (#155705)
gh-154916: Fix data races in GenericAlias using critical sections (#155634) (cherry picked from commit 8123ed1)
1 parent 6411ac7 commit 2540258

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
@@ -588,19 +589,24 @@ PyDoc_STRVAR(genericalias__doc__,
588589
"For example, for t = list[int], t.__origin__ is list and t.__args__\n"
589590
"is (int,).");
590591

592+
static PyObject *
593+
ga_parameters_lock_held(PyObject *self);
594+
591595
static PyObject *
592596
ga_getitem(PyObject *self, PyObject *item)
593597
{
594598
gaobject *alias = (gaobject *)self;
595599
// Populate __parameters__ if needed.
596-
if (alias->parameters == NULL) {
597-
alias->parameters = _Py_make_parameters(alias->args);
598-
if (alias->parameters == NULL) {
599-
return NULL;
600-
}
600+
PyObject *parameters;
601+
Py_BEGIN_CRITICAL_SECTION(self);
602+
parameters = ga_parameters_lock_held(self);
603+
Py_END_CRITICAL_SECTION();
604+
if (parameters == NULL) {
605+
return NULL;
601606
}
602607

603-
PyObject *newargs = _Py_subs_parameters(self, alias->args, alias->parameters, item);
608+
PyObject *newargs = _Py_subs_parameters(self, alias->args, parameters, item);
609+
Py_DECREF(parameters);
604610
if (newargs == NULL) {
605611
return NULL;
606612
}
@@ -856,6 +862,7 @@ static PyMemberDef ga_members[] = {
856862
static PyObject *
857863
ga_parameters_lock_held(PyObject *self)
858864
{
865+
_Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self);
859866
gaobject *alias = (gaobject *)self;
860867
if (alias->parameters == NULL) {
861868
alias->parameters = _Py_make_parameters(alias->args);
@@ -952,17 +959,22 @@ static PyObject *
952959
ga_iternext(PyObject *op)
953960
{
954961
gaiterobject *gi = (gaiterobject*)op;
955-
if (gi->obj == NULL) {
962+
PyObject *obj;
963+
Py_BEGIN_CRITICAL_SECTION(gi);
964+
obj = gi->obj;
965+
gi->obj = NULL;
966+
Py_END_CRITICAL_SECTION();
967+
if (obj == NULL) {
956968
PyErr_SetNone(PyExc_StopIteration);
957969
return NULL;
958970
}
959-
gaobject *alias = (gaobject *)gi->obj;
971+
gaobject *alias = (gaobject *)obj;
960972
PyObject *starred_alias = Py_GenericAlias(alias->origin, alias->args);
973+
Py_DECREF(obj);
961974
if (starred_alias == NULL) {
962975
return NULL;
963976
}
964977
((gaobject *)starred_alias)->starred = true;
965-
Py_SETREF(gi->obj, NULL);
966978
return starred_alias;
967979
}
968980

@@ -1001,10 +1013,19 @@ ga_iter_reduce(PyObject *self, PyObject *Py_UNUSED(ignored))
10011013
* call must be before access of iterator pointers.
10021014
* see issue #101765 */
10031015

1004-
if (gi->obj)
1005-
return Py_BuildValue("N(O)", iter, gi->obj);
1006-
else
1016+
PyObject *obj;
1017+
Py_BEGIN_CRITICAL_SECTION(gi);
1018+
obj = Py_XNewRef(gi->obj);
1019+
Py_END_CRITICAL_SECTION();
1020+
1021+
if (obj) {
1022+
PyObject *result = Py_BuildValue("N(O)", iter, obj);
1023+
Py_DECREF(obj);
1024+
return result;
1025+
}
1026+
else {
10071027
return Py_BuildValue("N(())", iter);
1028+
}
10081029
}
10091030

10101031
static PyMethodDef ga_iter_methods[] = {

0 commit comments

Comments
 (0)