diff --git a/Lib/test/test_free_threading/test_func_annotations.py b/Lib/test/test_free_threading/test_function.py similarity index 66% rename from Lib/test/test_free_threading/test_func_annotations.py rename to Lib/test/test_free_threading/test_function.py index 0fde0136d77c7fa..753f6b0f631dfcb 100644 --- a/Lib/test/test_free_threading/test_func_annotations.py +++ b/Lib/test/test_free_threading/test_function.py @@ -2,9 +2,8 @@ import unittest import inspect from threading import Barrier -from unittest import TestCase -from test.support import threading_helper, Py_GIL_DISABLED +from test.support import threading_helper threading_helper.requires_working_threading(module=True) @@ -25,11 +24,48 @@ def set_func_annotation(f, b): return f.__annotations__ -@unittest.skipUnless(Py_GIL_DISABLED, "Enable only in FT build") -class TestFTFuncAnnotations(TestCase): +class TestFunction(unittest.TestCase): NUM_THREADS = 4 - def test_concurrent_read(self): + def test_name_attribute_race(self): + # gh-153297 + def shared_func(): + return 1 + + def setter(): + for i in range(2000): + shared_func.__name__ = "q_%d_%d" % (id(i), i & 15) + + def reader(): + for _ in range(2000): + _ = shared_func.__name__ + repr(shared_func) + + threading_helper.run_concurrently([ + *[setter for _ in range(6)], + *[reader for _ in range(4)], + ]) + + def test_qualname_attribute_race(self): + # gh-153297 + def shared_func(): + return 1 + + def setter(): + for i in range(2000): + shared_func.__qualname__ = "q_%d_%d" % (id(i), i & 15) + + def reader(): + for _ in range(2000): + _ = shared_func.__qualname__ + repr(shared_func) + + threading_helper.run_concurrently([ + *[setter for _ in range(6)], + *[reader for _ in range(4)], + ]) + + def test_concurrent_read_annotations(self): def f(x: int) -> int: return x + 1 @@ -50,7 +86,7 @@ def f(x: int) -> int: self.assertIsNotNone(annotate) self.assertEqual(annotate, {'x': int, 'return': int}) - def test_concurrent_write(self): + def test_concurrent_write_annotations(self): def bar(x: int, y: float) -> float: return y ** x diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-07-08-15-09-41.gh-issue-153297.hWVsUZ.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-08-15-09-41.gh-issue-153297.hWVsUZ.rst new file mode 100644 index 000000000000000..b853f75bb949b2f --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-08-15-09-41.gh-issue-153297.hWVsUZ.rst @@ -0,0 +1,2 @@ +Fix data race on assigning ``__name__`` and ``__qualname__`` attrs to +function objects on FT builds. diff --git a/Objects/clinic/funcobject.c.h b/Objects/clinic/funcobject.c.h index fec743146466a45..e477cd763a61016 100644 --- a/Objects/clinic/funcobject.c.h +++ b/Objects/clinic/funcobject.c.h @@ -9,6 +9,106 @@ preserve #include "pycore_critical_section.h"// Py_BEGIN_CRITICAL_SECTION() #include "pycore_modsupport.h" // _PyArg_UnpackKeywords() +#if !defined(function___name___DOCSTR) +# define function___name___DOCSTR NULL +#endif +#if defined(FUNCTION___NAME___GETSETDEF) +# undef FUNCTION___NAME___GETSETDEF +# define FUNCTION___NAME___GETSETDEF {"__name__", (getter)function___name___get, (setter)function___name___set, function___name___DOCSTR}, +#else +# define FUNCTION___NAME___GETSETDEF {"__name__", (getter)function___name___get, NULL, function___name___DOCSTR}, +#endif + +static PyObject * +function___name___get_impl(PyFunctionObject *self); + +static PyObject * +function___name___get(PyObject *self, void *Py_UNUSED(context)) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = function___name___get_impl((PyFunctionObject *)self); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + +#if !defined(function___name___DOCSTR) +# define function___name___DOCSTR NULL +#endif +#if defined(FUNCTION___NAME___GETSETDEF) +# undef FUNCTION___NAME___GETSETDEF +# define FUNCTION___NAME___GETSETDEF {"__name__", (getter)function___name___get, (setter)function___name___set, function___name___DOCSTR}, +#else +# define FUNCTION___NAME___GETSETDEF {"__name__", NULL, (setter)function___name___set, NULL}, +#endif + +static int +function___name___set_impl(PyFunctionObject *self, PyObject *value); + +static int +function___name___set(PyObject *self, PyObject *value, void *Py_UNUSED(context)) +{ + int return_value; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = function___name___set_impl((PyFunctionObject *)self, value); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + +#if !defined(function___qualname___DOCSTR) +# define function___qualname___DOCSTR NULL +#endif +#if defined(FUNCTION___QUALNAME___GETSETDEF) +# undef FUNCTION___QUALNAME___GETSETDEF +# define FUNCTION___QUALNAME___GETSETDEF {"__qualname__", (getter)function___qualname___get, (setter)function___qualname___set, function___qualname___DOCSTR}, +#else +# define FUNCTION___QUALNAME___GETSETDEF {"__qualname__", (getter)function___qualname___get, NULL, function___qualname___DOCSTR}, +#endif + +static PyObject * +function___qualname___get_impl(PyFunctionObject *self); + +static PyObject * +function___qualname___get(PyObject *self, void *Py_UNUSED(context)) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = function___qualname___get_impl((PyFunctionObject *)self); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + +#if !defined(function___qualname___DOCSTR) +# define function___qualname___DOCSTR NULL +#endif +#if defined(FUNCTION___QUALNAME___GETSETDEF) +# undef FUNCTION___QUALNAME___GETSETDEF +# define FUNCTION___QUALNAME___GETSETDEF {"__qualname__", (getter)function___qualname___get, (setter)function___qualname___set, function___qualname___DOCSTR}, +#else +# define FUNCTION___QUALNAME___GETSETDEF {"__qualname__", NULL, (setter)function___qualname___set, NULL}, +#endif + +static int +function___qualname___set_impl(PyFunctionObject *self, PyObject *value); + +static int +function___qualname___set(PyObject *self, PyObject *value, void *Py_UNUSED(context)) +{ + int return_value; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = function___qualname___set_impl((PyFunctionObject *)self, value); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + PyDoc_STRVAR(function___annotate____doc__, "Get the code object for a function."); #if defined(function___annotate___DOCSTR) @@ -290,4 +390,4 @@ func_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) exit: return return_value; } -/*[clinic end generated code: output=12cb900088d41bdb input=a9049054013a1b77]*/ +/*[clinic end generated code: output=9d22c2b0ddf343b5 input=a9049054013a1b77]*/ diff --git a/Objects/funcobject.c b/Objects/funcobject.c index 0fffd36ad462dab..7dc14a05fb50b42 100644 --- a/Objects/funcobject.c +++ b/Objects/funcobject.c @@ -690,17 +690,29 @@ func_set_code(PyObject *self, PyObject *value, void *Py_UNUSED(ignored)) return 0; } +/*[clinic input] +@critical_section +@getter +function.__name__ +[clinic start generated code]*/ + static PyObject * -func_get_name(PyObject *self, void *Py_UNUSED(ignored)) +function___name___get_impl(PyFunctionObject *self) +/*[clinic end generated code: output=436852c5b4f6d259 input=3f1d1304d3fd103b]*/ { - PyFunctionObject *op = _PyFunction_CAST(self); - return Py_NewRef(op->func_name); + return Py_NewRef(self->func_name); } +/*[clinic input] +@critical_section +@setter +function.__name__ +[clinic start generated code]*/ + static int -func_set_name(PyObject *self, PyObject *value, void *Py_UNUSED(ignored)) +function___name___set_impl(PyFunctionObject *self, PyObject *value) +/*[clinic end generated code: output=2c571635b003b9cc input=705634aafaa00198]*/ { - PyFunctionObject *op = _PyFunction_CAST(self); /* Not legal to del f.func_name or to set it to anything * other than a string object. */ if (value == NULL || !PyUnicode_Check(value)) { @@ -708,21 +720,33 @@ 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)); + Py_XSETREF(self->func_name, Py_NewRef(value)); return 0; } +/*[clinic input] +@critical_section +@getter +function.__qualname__ +[clinic start generated code]*/ + static PyObject * -func_get_qualname(PyObject *self, void *Py_UNUSED(ignored)) +function___qualname___get_impl(PyFunctionObject *self) +/*[clinic end generated code: output=8fbd60e64464da5f input=761d5ab45fc13493]*/ { - PyFunctionObject *op = _PyFunction_CAST(self); - return Py_NewRef(op->func_qualname); + return Py_NewRef(self->func_qualname); } +/*[clinic input] +@critical_section +@setter +function.__qualname__ +[clinic start generated code]*/ + static int -func_set_qualname(PyObject *self, PyObject *value, void *Py_UNUSED(ignored)) +function___qualname___set_impl(PyFunctionObject *self, PyObject *value) +/*[clinic end generated code: output=4cc5e270fd55f139 input=c803ac4dfdf04c87]*/ { - PyFunctionObject *op = _PyFunction_CAST(self); /* Not legal to del f.__qualname__ or to set it to anything * other than a string object. */ if (value == NULL || !PyUnicode_Check(value)) { @@ -730,8 +754,8 @@ func_set_qualname(PyObject *self, PyObject *value, void *Py_UNUSED(ignored)) "__qualname__ must be set to a string object"); return -1; } - handle_func_event(PyFunction_EVENT_MODIFY_QUALNAME, (PyFunctionObject *) op, value); - Py_XSETREF(op->func_qualname, Py_NewRef(value)); + handle_func_event(PyFunction_EVENT_MODIFY_QUALNAME, self, value); + Py_XSETREF(self->func_qualname, Py_NewRef(value)); return 0; } @@ -976,8 +1000,8 @@ static PyGetSetDef func_getsetlist[] = { FUNCTION___ANNOTATIONS___GETSETDEF FUNCTION___ANNOTATE___GETSETDEF {"__dict__", PyObject_GenericGetDict, PyObject_GenericSetDict}, - {"__name__", func_get_name, func_set_name}, - {"__qualname__", func_get_qualname, func_set_qualname}, + FUNCTION___NAME___GETSETDEF + FUNCTION___QUALNAME___GETSETDEF FUNCTION___TYPE_PARAMS___GETSETDEF {NULL} /* Sentinel */ }; @@ -1147,8 +1171,12 @@ static PyObject* func_repr(PyObject *self) { PyFunctionObject *op = _PyFunction_CAST(self); - return PyUnicode_FromFormat("", - op->func_qualname, op); + PyObject *result; + Py_BEGIN_CRITICAL_SECTION(self); + result = PyUnicode_FromFormat("", + op->func_qualname, op); + Py_END_CRITICAL_SECTION(); + return result; } static int