https://github.com/python/cpython/commit/4f3e85640f72ee79d819ea3546868aa6544a6f49
commit: 4f3e85640f72ee79d819ea3546868aa6544a6f49
branch: main
author: Kumar Aditya <[email protected]>
committer: kumaraditya303 <[email protected]>
date: 2026-07-28T22:24:27+05:30
summary:

gh-154821: fix data races on function attributes (#154826)

files:
A Lib/test/test_free_threading/test_functions.py
A 
Misc/NEWS.d/next/Core_and_Builtins/2026-07-28-10-00-00.gh-issue-133931.fnQzxD.rst
M Objects/funcobject.c

diff --git a/Lib/test/test_free_threading/test_functions.py 
b/Lib/test/test_free_threading/test_functions.py
new file mode 100644
index 000000000000000..65a90a17bd5b8db
--- /dev/null
+++ b/Lib/test/test_free_threading/test_functions.py
@@ -0,0 +1,98 @@
+import random
+import unittest
+from unittest import TestCase
+
+from test.support import threading_helper
+
+threading_helper.requires_working_threading(module=True)
+
+NUM_THREADS = 8
+ITERS = 200
+
+
+def random_string():
+    return ''.join(random.choice('0123456789ABCDEF') for _ in range(10))
+
+
+def template_a(): pass
+def template_b(): pass
+
+
+class TestFTFunctionAttributes(TestCase):
+
+    def stress_attribute(self, attr, make_value):
+        def target(x=1):
+            return x
+
+        def writer():
+            for _ in range(ITERS):
+                setattr(target, attr, make_value())
+                getattr(target, attr)
+
+        threading_helper.run_concurrently(writer, NUM_THREADS)
+
+    def test_name(self):
+        self.stress_attribute("__name__", random_string)
+
+    def test_qualname(self):
+        self.stress_attribute("__qualname__", random_string)
+
+    def test_code(self):
+        codes = (template_a.__code__, template_b.__code__)
+        self.stress_attribute("__code__", lambda: random.choice(codes))
+
+    def test_defaults(self):
+        self.stress_attribute("__defaults__", lambda: (random_string(),))
+
+    def test_kwdefaults(self):
+        self.stress_attribute("__kwdefaults__", lambda: {"x": random_string()})
+
+    def test_annotations(self):
+        self.stress_attribute("__annotations__",
+                              lambda: {"x": random_string()})
+
+    def test_annotate(self):
+        self.stress_attribute("__annotate__",
+                              lambda: (lambda format: {"x": str}))
+
+    def test_type_params(self):
+        self.stress_attribute("__type_params__", lambda: (random_string(),))
+
+    def test_annotations_and_annotate(self):
+        # The __annotations__ and __annotate__ setters clear each other.
+        def target(): pass
+
+        def set_annotations():
+            for _ in range(ITERS):
+                target.__annotations__ = {"x": random_string()}
+                target.__annotations__
+
+        def set_annotate():
+            for _ in range(ITERS):
+                target.__annotate__ = lambda format: {"x": str}
+                target.__annotate__
+
+        threading_helper.run_concurrently(
+            [set_annotations, set_annotate] * (NUM_THREADS // 2))
+
+    def test_call_while_replacing_defaults(self):
+        # The eval loop reads __defaults__ and __kwdefaults__ without holding
+        # a lock while pushing a frame.
+        def target(x="init", *, y="init"):
+            return x, y
+
+        def writer():
+            for _ in range(ITERS):
+                target.__defaults__ = (random_string(),)
+                target.__kwdefaults__ = {"y": random_string()}
+
+        def caller():
+            for _ in range(ITERS):
+                target()
+
+        threading_helper.run_concurrently(
+            [writer, caller] * (NUM_THREADS // 2))
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git 
a/Misc/NEWS.d/next/Core_and_Builtins/2026-07-28-10-00-00.gh-issue-133931.fnQzxD.rst
 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-28-10-00-00.gh-issue-133931.fnQzxD.rst
new file mode 100644
index 000000000000000..139bdd86b84892f
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-28-10-00-00.gh-issue-133931.fnQzxD.rst
@@ -0,0 +1,2 @@
+Fix data races when setting attributes of function objects
+on the :term:`free threaded <free threading>` build.
diff --git a/Objects/funcobject.c b/Objects/funcobject.c
index 0fffd36ad462dab..49a28e8ad667141 100644
--- a/Objects/funcobject.c
+++ b/Objects/funcobject.c
@@ -349,20 +349,6 @@ func_clear_version(PyInterpreterState *interp, 
PyFunctionObject *func)
     func->func_version = FUNC_VERSION_CLEARED;
 }
 
-// Called when any of the critical function attributes are changed
-static void
-_PyFunction_ClearVersion(PyFunctionObject *func)
-{
-    if (func->func_version < FUNC_VERSION_FIRST_VALID) {
-        // Version was never set or has already been cleared.
-        return;
-    }
-    PyInterpreterState *interp = _PyInterpreterState_GET();
-    _PyEval_StopTheWorld(interp);
-    func_clear_version(interp, func);
-    _PyEval_StartTheWorld(interp);
-}
-
 void
 _PyFunction_ClearCodeByVersion(uint32_t version)
 {
@@ -448,10 +434,15 @@ PyFunction_SetDefaults(PyObject *op, PyObject *defaults)
         PyErr_SetString(PyExc_SystemError, "non-tuple default args");
         return -1;
     }
-    handle_func_event(PyFunction_EVENT_MODIFY_DEFAULTS,
-                      (PyFunctionObject *) op, defaults);
-    _PyFunction_ClearVersion((PyFunctionObject *)op);
-    Py_XSETREF(((PyFunctionObject *)op)->func_defaults, defaults);
+    PyFunctionObject *func = (PyFunctionObject *)op;
+    handle_func_event(PyFunction_EVENT_MODIFY_DEFAULTS, func, defaults);
+    PyInterpreterState *interp = _PyInterpreterState_GET();
+    _PyEval_StopTheWorld(interp);
+    func_clear_version(interp, func);
+    PyObject *old_defaults = func->func_defaults;
+    func->func_defaults = defaults;
+    _PyEval_StartTheWorld(interp);
+    Py_XDECREF(old_defaults);
     return 0;
 }
 
@@ -459,8 +450,11 @@ void
 PyFunction_SetVectorcall(PyFunctionObject *func, vectorcallfunc vectorcall)
 {
     assert(func != NULL);
-    _PyFunction_ClearVersion(func);
+    PyInterpreterState *interp = _PyInterpreterState_GET();
+    _PyEval_StopTheWorld(interp);
+    func_clear_version(interp, func);
     func->vectorcall = vectorcall;
+    _PyEval_StartTheWorld(interp);
 }
 
 PyObject *
@@ -490,10 +484,15 @@ PyFunction_SetKwDefaults(PyObject *op, PyObject *defaults)
                         "non-dict keyword only default args");
         return -1;
     }
-    handle_func_event(PyFunction_EVENT_MODIFY_KWDEFAULTS,
-                      (PyFunctionObject *) op, defaults);
-    _PyFunction_ClearVersion((PyFunctionObject *)op);
-    Py_XSETREF(((PyFunctionObject *)op)->func_kwdefaults, defaults);
+    PyFunctionObject *func = (PyFunctionObject *)op;
+    handle_func_event(PyFunction_EVENT_MODIFY_KWDEFAULTS, func, defaults);
+    PyInterpreterState *interp = _PyInterpreterState_GET();
+    _PyEval_StopTheWorld(interp);
+    func_clear_version(interp, func);
+    PyObject *old_kwdefaults = func->func_kwdefaults;
+    func->func_kwdefaults = defaults;
+    _PyEval_StartTheWorld(interp);
+    Py_XDECREF(old_kwdefaults);
     return 0;
 }
 
@@ -525,8 +524,14 @@ PyFunction_SetClosure(PyObject *op, PyObject *closure)
                      Py_TYPE(closure)->tp_name);
         return -1;
     }
-    _PyFunction_ClearVersion((PyFunctionObject *)op);
-    Py_XSETREF(((PyFunctionObject *)op)->func_closure, closure);
+    PyFunctionObject *func = (PyFunctionObject *)op;
+    PyInterpreterState *interp = _PyInterpreterState_GET();
+    _PyEval_StopTheWorld(interp);
+    func_clear_version(interp, func);
+    PyObject *old_closure = func->func_closure;
+    func->func_closure = closure;
+    _PyEval_StartTheWorld(interp);
+    Py_XDECREF(old_closure);
     return 0;
 }
 
@@ -605,8 +610,15 @@ PyFunction_SetAnnotations(PyObject *op, PyObject 
*annotations)
         return -1;
     }
     PyFunctionObject *func = (PyFunctionObject *)op;
-    Py_XSETREF(func->func_annotations, annotations);
-    Py_CLEAR(func->func_annotate);
+    PyInterpreterState *interp = _PyInterpreterState_GET();
+    _PyEval_StopTheWorld(interp);
+    PyObject *old_annotations = func->func_annotations;
+    func->func_annotations = annotations;
+    PyObject *old_annotate = func->func_annotate;
+    func->func_annotate = NULL;
+    _PyEval_StartTheWorld(interp);
+    Py_XDECREF(old_annotations);
+    Py_XDECREF(old_annotate);
     return 0;
 }
 
@@ -685,8 +697,13 @@ func_set_code(PyObject *self, PyObject *value, void 
*Py_UNUSED(ignored))
     }
 
     handle_func_event(PyFunction_EVENT_MODIFY_CODE, op, value);
-    _PyFunction_ClearVersion(op);
-    Py_XSETREF(op->func_code, Py_NewRef(value));
+    PyInterpreterState *interp = _PyInterpreterState_GET();
+    _PyEval_StopTheWorld(interp);
+    func_clear_version(interp, op);
+    PyObject *old_code = op->func_code;
+    op->func_code = Py_NewRef(value);
+    _PyEval_StartTheWorld(interp);
+    Py_XDECREF(old_code);
     return 0;
 }
 
@@ -708,7 +725,12 @@ func_set_name(PyObject *self, PyObject *value, void 
*Py_UNUSED(ignored))
                         "__name__ must be set to a string object");
         return -1;
     }
-    Py_XSETREF(op->func_name, Py_NewRef(value));
+    PyInterpreterState *interp = _PyInterpreterState_GET();
+    _PyEval_StopTheWorld(interp);
+    PyObject *old_name = op->func_name;
+    op->func_name = Py_NewRef(value);
+    _PyEval_StartTheWorld(interp);
+    Py_XDECREF(old_name);
     return 0;
 }
 
@@ -731,7 +753,12 @@ func_set_qualname(PyObject *self, PyObject *value, void 
*Py_UNUSED(ignored))
         return -1;
     }
     handle_func_event(PyFunction_EVENT_MODIFY_QUALNAME, (PyFunctionObject *) 
op, value);
-    Py_XSETREF(op->func_qualname, Py_NewRef(value));
+    PyInterpreterState *interp = _PyInterpreterState_GET();
+    _PyEval_StopTheWorld(interp);
+    PyObject *old_qualname = op->func_qualname;
+    op->func_qualname = Py_NewRef(value);
+    _PyEval_StartTheWorld(interp);
+    Py_XDECREF(old_qualname);
     return 0;
 }
 
@@ -772,8 +799,13 @@ func_set_defaults(PyObject *self, PyObject *value, void 
*Py_UNUSED(ignored))
     }
 
     handle_func_event(PyFunction_EVENT_MODIFY_DEFAULTS, op, value);
-    _PyFunction_ClearVersion(op);
-    Py_XSETREF(op->func_defaults, Py_XNewRef(value));
+    PyInterpreterState *interp = _PyInterpreterState_GET();
+    _PyEval_StopTheWorld(interp);
+    func_clear_version(interp, op);
+    PyObject *old_defaults = op->func_defaults;
+    op->func_defaults = Py_XNewRef(value);
+    _PyEval_StartTheWorld(interp);
+    Py_XDECREF(old_defaults);
     return 0;
 }
 
@@ -815,8 +847,13 @@ func_set_kwdefaults(PyObject *self, PyObject *value, void 
*Py_UNUSED(ignored))
     }
 
     handle_func_event(PyFunction_EVENT_MODIFY_KWDEFAULTS, op, value);
-    _PyFunction_ClearVersion(op);
-    Py_XSETREF(op->func_kwdefaults, Py_XNewRef(value));
+    PyInterpreterState *interp = _PyInterpreterState_GET();
+    _PyEval_StopTheWorld(interp);
+    func_clear_version(interp, op);
+    PyObject *old_kwdefaults = op->func_kwdefaults;
+    op->func_kwdefaults = Py_XNewRef(value);
+    _PyEval_StartTheWorld(interp);
+    Py_XDECREF(old_kwdefaults);
     return 0;
 }
 
@@ -854,12 +891,24 @@ function___annotate___set_impl(PyFunctionObject *self, 
PyObject *value)
         return -1;
     }
     if (Py_IsNone(value)) {
-        Py_XSETREF(self->func_annotate, value);
+        PyInterpreterState *interp = _PyInterpreterState_GET();
+        _PyEval_StopTheWorld(interp);
+        PyObject *old_annotate = self->func_annotate;
+        self->func_annotate = Py_NewRef(value);
+        _PyEval_StartTheWorld(interp);
+        Py_XDECREF(old_annotate);
         return 0;
     }
     else if (PyCallable_Check(value)) {
-        Py_XSETREF(self->func_annotate, Py_XNewRef(value));
-        Py_CLEAR(self->func_annotations);
+        PyInterpreterState *interp = _PyInterpreterState_GET();
+        _PyEval_StopTheWorld(interp);
+        PyObject *old_annotate = self->func_annotate;
+        self->func_annotate = Py_NewRef(value);
+        PyObject *old_annotations = self->func_annotations;
+        self->func_annotations = NULL;
+        _PyEval_StartTheWorld(interp);
+        Py_XDECREF(old_annotate);
+        Py_XDECREF(old_annotations);
         return 0;
     }
     else {
@@ -912,8 +961,15 @@ function___annotations___set_impl(PyFunctionObject *self, 
PyObject *value)
             "__annotations__ must be set to a dict object");
         return -1;
     }
-    Py_XSETREF(self->func_annotations, Py_XNewRef(value));
-    Py_CLEAR(self->func_annotate);
+    PyInterpreterState *interp = _PyInterpreterState_GET();
+    _PyEval_StopTheWorld(interp);
+    PyObject *old_annotations = self->func_annotations;
+    self->func_annotations = Py_XNewRef(value);
+    PyObject *old_annotate = self->func_annotate;
+    self->func_annotate = NULL;
+    _PyEval_StartTheWorld(interp);
+    Py_XDECREF(old_annotations);
+    Py_XDECREF(old_annotate);
     return 0;
 }
 
@@ -954,7 +1010,12 @@ function___type_params___set_impl(PyFunctionObject *self, 
PyObject *value)
                         "__type_params__ must be set to a tuple");
         return -1;
     }
-    Py_XSETREF(self->func_typeparams, Py_NewRef(value));
+    PyInterpreterState *interp = _PyInterpreterState_GET();
+    _PyEval_StopTheWorld(interp);
+    PyObject *old_typeparams = self->func_typeparams;
+    self->func_typeparams = Py_NewRef(value);
+    _PyEval_StartTheWorld(interp);
+    Py_XDECREF(old_typeparams);
     return 0;
 }
 

_______________________________________________
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]

Reply via email to