Skip to content

Commit 83cb05a

Browse files
fix data races on function attributes
1 parent 993a0c6 commit 83cb05a

3 files changed

Lines changed: 202 additions & 41 deletions

File tree

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import random
2+
import unittest
3+
from unittest import TestCase
4+
5+
from test.support import threading_helper, Py_GIL_DISABLED
6+
7+
threading_helper.requires_working_threading(module=True)
8+
9+
NUM_THREADS = 8
10+
ITERS = 200
11+
12+
13+
def random_string():
14+
return ''.join(random.choice('0123456789ABCDEF') for _ in range(10))
15+
16+
17+
def template_a(): pass
18+
def template_b(): pass
19+
20+
21+
class TestFTFunctionAttributes(TestCase):
22+
23+
def stress_attribute(self, attr, make_value):
24+
def target(x=1):
25+
return x
26+
27+
def writer():
28+
for _ in range(ITERS):
29+
setattr(target, attr, make_value())
30+
getattr(target, attr)
31+
32+
threading_helper.run_concurrently(writer, NUM_THREADS)
33+
34+
def test_name(self):
35+
self.stress_attribute("__name__", random_string)
36+
37+
def test_qualname(self):
38+
self.stress_attribute("__qualname__", random_string)
39+
40+
def test_code(self):
41+
codes = (template_a.__code__, template_b.__code__)
42+
self.stress_attribute("__code__", lambda: random.choice(codes))
43+
44+
def test_defaults(self):
45+
self.stress_attribute("__defaults__", lambda: (random_string(),))
46+
47+
def test_kwdefaults(self):
48+
self.stress_attribute("__kwdefaults__", lambda: {"x": random_string()})
49+
50+
def test_annotations(self):
51+
self.stress_attribute("__annotations__",
52+
lambda: {"x": random_string()})
53+
54+
def test_annotate(self):
55+
self.stress_attribute("__annotate__",
56+
lambda: (lambda format: {"x": str}))
57+
58+
def test_type_params(self):
59+
self.stress_attribute("__type_params__", lambda: (random_string(),))
60+
61+
def test_annotations_and_annotate(self):
62+
# The __annotations__ and __annotate__ setters clear each other.
63+
def target(): pass
64+
65+
def set_annotations():
66+
for _ in range(ITERS):
67+
target.__annotations__ = {"x": random_string()}
68+
target.__annotations__
69+
70+
def set_annotate():
71+
for _ in range(ITERS):
72+
target.__annotate__ = lambda format: {"x": str}
73+
target.__annotate__
74+
75+
threading_helper.run_concurrently(
76+
[set_annotations, set_annotate] * (NUM_THREADS // 2))
77+
78+
def test_call_while_replacing_defaults(self):
79+
# The eval loop reads __defaults__ and __kwdefaults__ without holding
80+
# a lock while pushing a frame.
81+
def target(x="init", *, y="init"):
82+
return x, y
83+
84+
def writer():
85+
for _ in range(ITERS):
86+
target.__defaults__ = (random_string(),)
87+
target.__kwdefaults__ = {"y": random_string()}
88+
89+
def caller():
90+
for _ in range(ITERS):
91+
target()
92+
93+
threading_helper.run_concurrently(
94+
[writer, caller] * (NUM_THREADS // 2))
95+
96+
97+
if __name__ == "__main__":
98+
unittest.main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix a race condition when setting attributes of :class:`function` objects
2+
on the :term:`free threaded <free threading>` build.

Objects/funcobject.c

Lines changed: 102 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -349,20 +349,6 @@ func_clear_version(PyInterpreterState *interp, PyFunctionObject *func)
349349
func->func_version = FUNC_VERSION_CLEARED;
350350
}
351351

352-
// Called when any of the critical function attributes are changed
353-
static void
354-
_PyFunction_ClearVersion(PyFunctionObject *func)
355-
{
356-
if (func->func_version < FUNC_VERSION_FIRST_VALID) {
357-
// Version was never set or has already been cleared.
358-
return;
359-
}
360-
PyInterpreterState *interp = _PyInterpreterState_GET();
361-
_PyEval_StopTheWorld(interp);
362-
func_clear_version(interp, func);
363-
_PyEval_StartTheWorld(interp);
364-
}
365-
366352
void
367353
_PyFunction_ClearCodeByVersion(uint32_t version)
368354
{
@@ -448,19 +434,27 @@ PyFunction_SetDefaults(PyObject *op, PyObject *defaults)
448434
PyErr_SetString(PyExc_SystemError, "non-tuple default args");
449435
return -1;
450436
}
451-
handle_func_event(PyFunction_EVENT_MODIFY_DEFAULTS,
452-
(PyFunctionObject *) op, defaults);
453-
_PyFunction_ClearVersion((PyFunctionObject *)op);
454-
Py_XSETREF(((PyFunctionObject *)op)->func_defaults, defaults);
437+
PyFunctionObject *func = (PyFunctionObject *)op;
438+
handle_func_event(PyFunction_EVENT_MODIFY_DEFAULTS, func, defaults);
439+
PyInterpreterState *interp = _PyInterpreterState_GET();
440+
_PyEval_StopTheWorld(interp);
441+
func_clear_version(interp, func);
442+
PyObject *old_defaults = func->func_defaults;
443+
func->func_defaults = defaults;
444+
_PyEval_StartTheWorld(interp);
445+
Py_XDECREF(old_defaults);
455446
return 0;
456447
}
457448

458449
void
459450
PyFunction_SetVectorcall(PyFunctionObject *func, vectorcallfunc vectorcall)
460451
{
461452
assert(func != NULL);
462-
_PyFunction_ClearVersion(func);
453+
PyInterpreterState *interp = _PyInterpreterState_GET();
454+
_PyEval_StopTheWorld(interp);
455+
func_clear_version(interp, func);
463456
func->vectorcall = vectorcall;
457+
_PyEval_StartTheWorld(interp);
464458
}
465459

466460
PyObject *
@@ -490,10 +484,15 @@ PyFunction_SetKwDefaults(PyObject *op, PyObject *defaults)
490484
"non-dict keyword only default args");
491485
return -1;
492486
}
493-
handle_func_event(PyFunction_EVENT_MODIFY_KWDEFAULTS,
494-
(PyFunctionObject *) op, defaults);
495-
_PyFunction_ClearVersion((PyFunctionObject *)op);
496-
Py_XSETREF(((PyFunctionObject *)op)->func_kwdefaults, defaults);
487+
PyFunctionObject *func = (PyFunctionObject *)op;
488+
handle_func_event(PyFunction_EVENT_MODIFY_KWDEFAULTS, func, defaults);
489+
PyInterpreterState *interp = _PyInterpreterState_GET();
490+
_PyEval_StopTheWorld(interp);
491+
func_clear_version(interp, func);
492+
PyObject *old_kwdefaults = func->func_kwdefaults;
493+
func->func_kwdefaults = defaults;
494+
_PyEval_StartTheWorld(interp);
495+
Py_XDECREF(old_kwdefaults);
497496
return 0;
498497
}
499498

@@ -525,8 +524,14 @@ PyFunction_SetClosure(PyObject *op, PyObject *closure)
525524
Py_TYPE(closure)->tp_name);
526525
return -1;
527526
}
528-
_PyFunction_ClearVersion((PyFunctionObject *)op);
529-
Py_XSETREF(((PyFunctionObject *)op)->func_closure, closure);
527+
PyFunctionObject *func = (PyFunctionObject *)op;
528+
PyInterpreterState *interp = _PyInterpreterState_GET();
529+
_PyEval_StopTheWorld(interp);
530+
func_clear_version(interp, func);
531+
PyObject *old_closure = func->func_closure;
532+
func->func_closure = closure;
533+
_PyEval_StartTheWorld(interp);
534+
Py_XDECREF(old_closure);
530535
return 0;
531536
}
532537

@@ -605,8 +610,15 @@ PyFunction_SetAnnotations(PyObject *op, PyObject *annotations)
605610
return -1;
606611
}
607612
PyFunctionObject *func = (PyFunctionObject *)op;
608-
Py_XSETREF(func->func_annotations, annotations);
609-
Py_CLEAR(func->func_annotate);
613+
PyInterpreterState *interp = _PyInterpreterState_GET();
614+
_PyEval_StopTheWorld(interp);
615+
PyObject *old_annotations = func->func_annotations;
616+
func->func_annotations = annotations;
617+
PyObject *old_annotate = func->func_annotate;
618+
func->func_annotate = NULL;
619+
_PyEval_StartTheWorld(interp);
620+
Py_XDECREF(old_annotations);
621+
Py_XDECREF(old_annotate);
610622
return 0;
611623
}
612624

@@ -685,8 +697,13 @@ func_set_code(PyObject *self, PyObject *value, void *Py_UNUSED(ignored))
685697
}
686698

687699
handle_func_event(PyFunction_EVENT_MODIFY_CODE, op, value);
688-
_PyFunction_ClearVersion(op);
689-
Py_XSETREF(op->func_code, Py_NewRef(value));
700+
PyInterpreterState *interp = _PyInterpreterState_GET();
701+
_PyEval_StopTheWorld(interp);
702+
func_clear_version(interp, op);
703+
PyObject *old_code = op->func_code;
704+
op->func_code = Py_NewRef(value);
705+
_PyEval_StartTheWorld(interp);
706+
Py_XDECREF(old_code);
690707
return 0;
691708
}
692709

@@ -708,7 +725,12 @@ func_set_name(PyObject *self, PyObject *value, void *Py_UNUSED(ignored))
708725
"__name__ must be set to a string object");
709726
return -1;
710727
}
711-
Py_XSETREF(op->func_name, Py_NewRef(value));
728+
PyInterpreterState *interp = _PyInterpreterState_GET();
729+
_PyEval_StopTheWorld(interp);
730+
PyObject *old_name = op->func_name;
731+
op->func_name = Py_NewRef(value);
732+
_PyEval_StartTheWorld(interp);
733+
Py_XDECREF(old_name);
712734
return 0;
713735
}
714736

@@ -731,7 +753,12 @@ func_set_qualname(PyObject *self, PyObject *value, void *Py_UNUSED(ignored))
731753
return -1;
732754
}
733755
handle_func_event(PyFunction_EVENT_MODIFY_QUALNAME, (PyFunctionObject *) op, value);
734-
Py_XSETREF(op->func_qualname, Py_NewRef(value));
756+
PyInterpreterState *interp = _PyInterpreterState_GET();
757+
_PyEval_StopTheWorld(interp);
758+
PyObject *old_qualname = op->func_qualname;
759+
op->func_qualname = Py_NewRef(value);
760+
_PyEval_StartTheWorld(interp);
761+
Py_XDECREF(old_qualname);
735762
return 0;
736763
}
737764

@@ -772,8 +799,13 @@ func_set_defaults(PyObject *self, PyObject *value, void *Py_UNUSED(ignored))
772799
}
773800

774801
handle_func_event(PyFunction_EVENT_MODIFY_DEFAULTS, op, value);
775-
_PyFunction_ClearVersion(op);
776-
Py_XSETREF(op->func_defaults, Py_XNewRef(value));
802+
PyInterpreterState *interp = _PyInterpreterState_GET();
803+
_PyEval_StopTheWorld(interp);
804+
func_clear_version(interp, op);
805+
PyObject *old_defaults = op->func_defaults;
806+
op->func_defaults = Py_XNewRef(value);
807+
_PyEval_StartTheWorld(interp);
808+
Py_XDECREF(old_defaults);
777809
return 0;
778810
}
779811

@@ -815,8 +847,13 @@ func_set_kwdefaults(PyObject *self, PyObject *value, void *Py_UNUSED(ignored))
815847
}
816848

817849
handle_func_event(PyFunction_EVENT_MODIFY_KWDEFAULTS, op, value);
818-
_PyFunction_ClearVersion(op);
819-
Py_XSETREF(op->func_kwdefaults, Py_XNewRef(value));
850+
PyInterpreterState *interp = _PyInterpreterState_GET();
851+
_PyEval_StopTheWorld(interp);
852+
func_clear_version(interp, op);
853+
PyObject *old_kwdefaults = op->func_kwdefaults;
854+
op->func_kwdefaults = Py_XNewRef(value);
855+
_PyEval_StartTheWorld(interp);
856+
Py_XDECREF(old_kwdefaults);
820857
return 0;
821858
}
822859

@@ -854,12 +891,24 @@ function___annotate___set_impl(PyFunctionObject *self, PyObject *value)
854891
return -1;
855892
}
856893
if (Py_IsNone(value)) {
857-
Py_XSETREF(self->func_annotate, value);
894+
PyInterpreterState *interp = _PyInterpreterState_GET();
895+
_PyEval_StopTheWorld(interp);
896+
PyObject *old_annotate = self->func_annotate;
897+
self->func_annotate = Py_NewRef(value);
898+
_PyEval_StartTheWorld(interp);
899+
Py_XDECREF(old_annotate);
858900
return 0;
859901
}
860902
else if (PyCallable_Check(value)) {
861-
Py_XSETREF(self->func_annotate, Py_XNewRef(value));
862-
Py_CLEAR(self->func_annotations);
903+
PyInterpreterState *interp = _PyInterpreterState_GET();
904+
_PyEval_StopTheWorld(interp);
905+
PyObject *old_annotate = self->func_annotate;
906+
self->func_annotate = Py_NewRef(value);
907+
PyObject *old_annotations = self->func_annotations;
908+
self->func_annotations = NULL;
909+
_PyEval_StartTheWorld(interp);
910+
Py_XDECREF(old_annotate);
911+
Py_XDECREF(old_annotations);
863912
return 0;
864913
}
865914
else {
@@ -912,8 +961,15 @@ function___annotations___set_impl(PyFunctionObject *self, PyObject *value)
912961
"__annotations__ must be set to a dict object");
913962
return -1;
914963
}
915-
Py_XSETREF(self->func_annotations, Py_XNewRef(value));
916-
Py_CLEAR(self->func_annotate);
964+
PyInterpreterState *interp = _PyInterpreterState_GET();
965+
_PyEval_StopTheWorld(interp);
966+
PyObject *old_annotations = self->func_annotations;
967+
self->func_annotations = Py_XNewRef(value);
968+
PyObject *old_annotate = self->func_annotate;
969+
self->func_annotate = NULL;
970+
_PyEval_StartTheWorld(interp);
971+
Py_XDECREF(old_annotations);
972+
Py_XDECREF(old_annotate);
917973
return 0;
918974
}
919975

@@ -954,7 +1010,12 @@ function___type_params___set_impl(PyFunctionObject *self, PyObject *value)
9541010
"__type_params__ must be set to a tuple");
9551011
return -1;
9561012
}
957-
Py_XSETREF(self->func_typeparams, Py_NewRef(value));
1013+
PyInterpreterState *interp = _PyInterpreterState_GET();
1014+
_PyEval_StopTheWorld(interp);
1015+
PyObject *old_typeparams = self->func_typeparams;
1016+
self->func_typeparams = Py_NewRef(value);
1017+
_PyEval_StartTheWorld(interp);
1018+
Py_XDECREF(old_typeparams);
9581019
return 0;
9591020
}
9601021

0 commit comments

Comments
 (0)