Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 4f3e856

Browse filesBrowse files
gh-154821: fix data races on function attributes (#154826)
1 parent cb0f028 commit 4f3e856
Copy full SHA for 4f3e856

3 files changed

+202-41Lines changed: 202 additions & 41 deletions

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
Expand file treeCollapse file tree
Open diff view settings
Collapse file
+98Lines 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
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()
Collapse file
+2Lines changed: 2 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix data races when setting attributes of function objects
2+
on the :term:`free threaded <free threading>` build.
Collapse file

‎Objects/funcobject.c‎

Copy file name to clipboardExpand all lines: Objects/funcobject.c
+102-41Lines 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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.