From 736e5dc876e2603f29fc41b158a06efd28c8357c Mon Sep 17 00:00:00 2001 From: Xuanteng Huang Date: Sun, 19 Jan 2025 16:19:38 +0800 Subject: [PATCH 1/7] add ft support for func annotation read --- .../test_func_annotations.py | 31 +++++++++++++++++++ Objects/funcobject.c | 18 +++++++---- 2 files changed, 43 insertions(+), 6 deletions(-) create mode 100644 Lib/test/test_free_threading/test_func_annotations.py diff --git a/Lib/test/test_free_threading/test_func_annotations.py b/Lib/test/test_free_threading/test_func_annotations.py new file mode 100644 index 00000000000000..bedd7c3f41f560 --- /dev/null +++ b/Lib/test/test_free_threading/test_func_annotations.py @@ -0,0 +1,31 @@ +import concurrent.futures +import unittest +from threading import Thread, Barrier +from unittest import TestCase + +from test.support import threading_helper, Py_GIL_DISABLED + +threading_helper.requires_working_threading(module=True) + + +def get_func_annotate(f, b): + b.wait() + return f.__annotation__ + + +@unittest.skipUnless(Py_GIL_DISABLED, "Enable only in FT build") +class TestFTFuncAnnotations(TestCase): + def test_concurrent_read(self): + def f(x: int) -> int: + return x + 1 + + num_threads = 8 + b = Barrier(num_threads) + threads = [] + + with concurrent.futures.ThreadPoolExecutor(max_workers=num_threads) as executor: + futures = {executor.submit(get_func_annotate, f, b): i for i in range(num_threads)} + for fut in concurrent.futures.as_completed(futures): + annotate = fut.result() + self.assertIsNotNone(annotate) + self.assertEqual(annotate, {'x': int, 'return': int}) diff --git a/Objects/funcobject.c b/Objects/funcobject.c index 7b17a9ba31fac4..a9ac89bb1d2b15 100644 --- a/Objects/funcobject.c +++ b/Objects/funcobject.c @@ -2,11 +2,12 @@ /* Function object implementation */ #include "Python.h" -#include "pycore_dict.h" // _Py_INCREF_DICT() -#include "pycore_long.h" // _PyLong_GetOne() -#include "pycore_modsupport.h" // _PyArg_NoKeywords() -#include "pycore_object.h" // _PyObject_GC_UNTRACK() -#include "pycore_pyerrors.h" // _PyErr_Occurred() +#include "pycore_dict.h" // _Py_INCREF_DICT() +#include "pycore_long.h" // _PyLong_GetOne() +#include "pycore_modsupport.h" // _PyArg_NoKeywords() +#include "pycore_object.h" // _PyObject_GC_UNTRACK() +#include "pycore_pyerrors.h" // _PyErr_Occurred() +#include "pycore_critical_section.h" // Py_BEGIN_CRITICAL_SECTION() static const char * @@ -863,13 +864,16 @@ static PyObject * func_get_annotations(PyObject *self, void *Py_UNUSED(ignored)) { PyFunctionObject *op = _PyFunction_CAST(self); + PyObject *d; + Py_BEGIN_CRITICAL_SECTION(self); if (op->func_annotations == NULL && (op->func_annotate == NULL || !PyCallable_Check(op->func_annotate))) { op->func_annotations = PyDict_New(); if (op->func_annotations == NULL) return NULL; } - PyObject *d = func_get_annotation_dict(op); + d = func_get_annotation_dict(op); + Py_END_CRITICAL_SECTION(); return Py_XNewRef(d); } @@ -887,8 +891,10 @@ func_set_annotations(PyObject *self, PyObject *value, void *Py_UNUSED(ignored)) "__annotations__ must be set to a dict object"); return -1; } + Py_BEGIN_CRITICAL_SECTION(self); Py_XSETREF(op->func_annotations, Py_XNewRef(value)); Py_CLEAR(op->func_annotate); + Py_END_CRITICAL_SECTION(); return 0; } From 594e0f005b6d92f4d7fa38a270eb9169ed4054f4 Mon Sep 17 00:00:00 2001 From: Xuanteng Huang Date: Sun, 19 Jan 2025 16:47:56 +0800 Subject: [PATCH 2/7] add concurrent write tests --- .../test_func_annotations.py | 58 +++++++++++++++---- 1 file changed, 47 insertions(+), 11 deletions(-) diff --git a/Lib/test/test_free_threading/test_func_annotations.py b/Lib/test/test_free_threading/test_func_annotations.py index bedd7c3f41f560..1a6461953d4aec 100644 --- a/Lib/test/test_free_threading/test_func_annotations.py +++ b/Lib/test/test_free_threading/test_func_annotations.py @@ -1,5 +1,6 @@ import concurrent.futures import unittest +import inspect from threading import Thread, Barrier from unittest import TestCase @@ -8,24 +9,59 @@ threading_helper.requires_working_threading(module=True) -def get_func_annotate(f, b): +def get_func_annotation(f, b): b.wait() - return f.__annotation__ + return inspect.get_annotations(f) + + +def get_func_annotation_dunder(f, b): + b.wait() + return f.__annotations__ + + +def set_func_annotation(f, b): + b.wait() + f.__annotations__ = {'x': int, 'y': int, 'return': int} + return f.__annotations__ @unittest.skipUnless(Py_GIL_DISABLED, "Enable only in FT build") class TestFTFuncAnnotations(TestCase): + NUM_THREADS = 8 + def test_concurrent_read(self): def f(x: int) -> int: return x + 1 - num_threads = 8 - b = Barrier(num_threads) - threads = [] + for _ in range(100): + with concurrent.futures.ThreadPoolExecutor(max_workers=self.NUM_THREADS) as executor: + b = Barrier(self.NUM_THREADS) + futures = {executor.submit(get_func_annotation, f, b): i for i in range(self.NUM_THREADS)} + for fut in concurrent.futures.as_completed(futures): + annotate = fut.result() + self.assertIsNotNone(annotate) + self.assertEqual(annotate, {'x': int, 'return': int}) + + with concurrent.futures.ThreadPoolExecutor(max_workers=self.NUM_THREADS) as executor: + b = Barrier(self.NUM_THREADS) + futures = {executor.submit(get_func_annotation_dunder, f, b): i for i in range(self.NUM_THREADS)} + for fut in concurrent.futures.as_completed(futures): + annotate = fut.result() + self.assertIsNotNone(annotate) + self.assertEqual(annotate, {'x': int, 'return': int}) + + def test_concurrent_write(self): + def bar(x: int, y: float) -> float: + return y ** x + + for _ in range(100): + with concurrent.futures.ThreadPoolExecutor(max_workers=self.NUM_THREADS) as executor: + b = Barrier(self.NUM_THREADS) + futures = {executor.submit(set_func_annotation, bar, b): i for i in range(self.NUM_THREADS)} + for fut in concurrent.futures.as_completed(futures): + annotate = fut.result() + self.assertIsNotNone(annotate) + self.assertEqual(annotate, {'x': int, 'y': int, 'return': int}) - with concurrent.futures.ThreadPoolExecutor(max_workers=num_threads) as executor: - futures = {executor.submit(get_func_annotate, f, b): i for i in range(num_threads)} - for fut in concurrent.futures.as_completed(futures): - annotate = fut.result() - self.assertIsNotNone(annotate) - self.assertEqual(annotate, {'x': int, 'return': int}) + # func_get_annotations returns in-place dict, so bar.__annotations__ should be modified as well + self.assertEqual(bar.__annotations__, {'x': int, 'y': int, 'return': int}) From 4026608d20ccaf739feed2cdf459c769b69bfd13 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Sun, 19 Jan 2025 09:07:45 +0000 Subject: [PATCH 3/7] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2025-01-19-09-07-44.gh-issue-128714.m1fyCB.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-01-19-09-07-44.gh-issue-128714.m1fyCB.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-01-19-09-07-44.gh-issue-128714.m1fyCB.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-01-19-09-07-44.gh-issue-128714.m1fyCB.rst new file mode 100644 index 00000000000000..75b1de1695216b --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-01-19-09-07-44.gh-issue-128714.m1fyCB.rst @@ -0,0 +1 @@ +Fix the potential races when getting and setting function annotations, and add related tests. From 6d8a5d5261733815c6c517c0d57157a4a3255038 Mon Sep 17 00:00:00 2001 From: Xuanteng Huang Date: Sun, 19 Jan 2025 17:11:39 +0800 Subject: [PATCH 4/7] init return value --- Objects/funcobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/funcobject.c b/Objects/funcobject.c index a9ac89bb1d2b15..db07c01313b279 100644 --- a/Objects/funcobject.c +++ b/Objects/funcobject.c @@ -864,7 +864,7 @@ static PyObject * func_get_annotations(PyObject *self, void *Py_UNUSED(ignored)) { PyFunctionObject *op = _PyFunction_CAST(self); - PyObject *d; + PyObject *d = NULL; Py_BEGIN_CRITICAL_SECTION(self); if (op->func_annotations == NULL && (op->func_annotate == NULL || !PyCallable_Check(op->func_annotate))) { From 00a538e45259646d1de80a9f5a3f59037c38a989 Mon Sep 17 00:00:00 2001 From: Xuanteng Huang Date: Tue, 4 Feb 2025 20:20:31 +0800 Subject: [PATCH 5/7] clinic getset methods which is not performance critical --- Objects/clinic/funcobject.c.h | 174 +++++++++++++++++++++++++++++++++- Objects/funcobject.c | 118 +++++++++++++++-------- 2 files changed, 253 insertions(+), 39 deletions(-) diff --git a/Objects/clinic/funcobject.c.h b/Objects/clinic/funcobject.c.h index 3f95a1db7b2788..efc579dc2597b7 100644 --- a/Objects/clinic/funcobject.c.h +++ b/Objects/clinic/funcobject.c.h @@ -6,8 +6,180 @@ preserve # include "pycore_gc.h" // PyGC_Head # include "pycore_runtime.h" // _Py_ID() #endif +#include "pycore_critical_section.h"// Py_BEGIN_CRITICAL_SECTION() #include "pycore_modsupport.h" // _PyArg_UnpackKeywords() +PyDoc_STRVAR(function___annotate____doc__, +"Get the code object for a function."); +#if defined(function___annotate___DOCSTR) +# undef function___annotate___DOCSTR +#endif +#define function___annotate___DOCSTR function___annotate____doc__ + +#if !defined(function___annotate___DOCSTR) +# define function___annotate___DOCSTR NULL +#endif +#if defined(FUNCTION___ANNOTATE___GETSETDEF) +# undef FUNCTION___ANNOTATE___GETSETDEF +# define FUNCTION___ANNOTATE___GETSETDEF {"__annotate__", (getter)function___annotate___get, (setter)function___annotate___set, function___annotate___DOCSTR}, +#else +# define FUNCTION___ANNOTATE___GETSETDEF {"__annotate__", (getter)function___annotate___get, NULL, function___annotate___DOCSTR}, +#endif + +static PyObject * +function___annotate___get_impl(PyFunctionObject *self); + +static PyObject * +function___annotate___get(PyObject *self, void *Py_UNUSED(context)) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = function___annotate___get_impl((PyFunctionObject *)self); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + +#if !defined(function___annotate___DOCSTR) +# define function___annotate___DOCSTR NULL +#endif +#if defined(FUNCTION___ANNOTATE___GETSETDEF) +# undef FUNCTION___ANNOTATE___GETSETDEF +# define FUNCTION___ANNOTATE___GETSETDEF {"__annotate__", (getter)function___annotate___get, (setter)function___annotate___set, function___annotate___DOCSTR}, +#else +# define FUNCTION___ANNOTATE___GETSETDEF {"__annotate__", NULL, (setter)function___annotate___set, NULL}, +#endif + +static int +function___annotate___set_impl(PyFunctionObject *self, PyObject *value); + +static int +function___annotate___set(PyObject *self, PyObject *value, void *Py_UNUSED(context)) +{ + int return_value; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = function___annotate___set_impl((PyFunctionObject *)self, value); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + +PyDoc_STRVAR(function___annotations____doc__, +"Dict of annotations in a function object."); +#if defined(function___annotations___DOCSTR) +# undef function___annotations___DOCSTR +#endif +#define function___annotations___DOCSTR function___annotations____doc__ + +#if !defined(function___annotations___DOCSTR) +# define function___annotations___DOCSTR NULL +#endif +#if defined(FUNCTION___ANNOTATIONS___GETSETDEF) +# undef FUNCTION___ANNOTATIONS___GETSETDEF +# define FUNCTION___ANNOTATIONS___GETSETDEF {"__annotations__", (getter)function___annotations___get, (setter)function___annotations___set, function___annotations___DOCSTR}, +#else +# define FUNCTION___ANNOTATIONS___GETSETDEF {"__annotations__", (getter)function___annotations___get, NULL, function___annotations___DOCSTR}, +#endif + +static PyObject * +function___annotations___get_impl(PyFunctionObject *self); + +static PyObject * +function___annotations___get(PyObject *self, void *Py_UNUSED(context)) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = function___annotations___get_impl((PyFunctionObject *)self); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + +#if !defined(function___annotations___DOCSTR) +# define function___annotations___DOCSTR NULL +#endif +#if defined(FUNCTION___ANNOTATIONS___GETSETDEF) +# undef FUNCTION___ANNOTATIONS___GETSETDEF +# define FUNCTION___ANNOTATIONS___GETSETDEF {"__annotations__", (getter)function___annotations___get, (setter)function___annotations___set, function___annotations___DOCSTR}, +#else +# define FUNCTION___ANNOTATIONS___GETSETDEF {"__annotations__", NULL, (setter)function___annotations___set, NULL}, +#endif + +static int +function___annotations___set_impl(PyFunctionObject *self, PyObject *value); + +static int +function___annotations___set(PyObject *self, PyObject *value, void *Py_UNUSED(context)) +{ + int return_value; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = function___annotations___set_impl((PyFunctionObject *)self, value); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + +PyDoc_STRVAR(function___type_params____doc__, +"Get the declared type parameters for a function."); +#if defined(function___type_params___DOCSTR) +# undef function___type_params___DOCSTR +#endif +#define function___type_params___DOCSTR function___type_params____doc__ + +#if !defined(function___type_params___DOCSTR) +# define function___type_params___DOCSTR NULL +#endif +#if defined(FUNCTION___TYPE_PARAMS___GETSETDEF) +# undef FUNCTION___TYPE_PARAMS___GETSETDEF +# define FUNCTION___TYPE_PARAMS___GETSETDEF {"__type_params__", (getter)function___type_params___get, (setter)function___type_params___set, function___type_params___DOCSTR}, +#else +# define FUNCTION___TYPE_PARAMS___GETSETDEF {"__type_params__", (getter)function___type_params___get, NULL, function___type_params___DOCSTR}, +#endif + +static PyObject * +function___type_params___get_impl(PyFunctionObject *self); + +static PyObject * +function___type_params___get(PyObject *self, void *Py_UNUSED(context)) +{ + PyObject *return_value = NULL; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = function___type_params___get_impl((PyFunctionObject *)self); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + +#if !defined(function___type_params___DOCSTR) +# define function___type_params___DOCSTR NULL +#endif +#if defined(FUNCTION___TYPE_PARAMS___GETSETDEF) +# undef FUNCTION___TYPE_PARAMS___GETSETDEF +# define FUNCTION___TYPE_PARAMS___GETSETDEF {"__type_params__", (getter)function___type_params___get, (setter)function___type_params___set, function___type_params___DOCSTR}, +#else +# define FUNCTION___TYPE_PARAMS___GETSETDEF {"__type_params__", NULL, (setter)function___type_params___set, NULL}, +#endif + +static int +function___type_params___set_impl(PyFunctionObject *self, PyObject *value); + +static int +function___type_params___set(PyObject *self, PyObject *value, void *Py_UNUSED(context)) +{ + int return_value; + + Py_BEGIN_CRITICAL_SECTION(self); + return_value = function___type_params___set_impl((PyFunctionObject *)self, value); + Py_END_CRITICAL_SECTION(); + + return return_value; +} + PyDoc_STRVAR(func_new__doc__, "function(code, globals, name=None, argdefs=None, closure=None,\n" " kwdefaults=None)\n" @@ -116,4 +288,4 @@ func_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) exit: return return_value; } -/*[clinic end generated code: output=bad4e19757dd26c3 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=3cdce22867efe617 input=a9049054013a1b77]*/ diff --git a/Objects/funcobject.c b/Objects/funcobject.c index db07c01313b279..d5afda8503f0b6 100644 --- a/Objects/funcobject.c +++ b/Objects/funcobject.c @@ -636,6 +636,13 @@ static PyMemberDef func_memberlist[] = { {NULL} /* Sentinel */ }; +/*[clinic input] +class function "PyFunctionObject *" "&PyFunction_Type" +[clinic start generated code]*/ +/*[clinic end generated code: output=da39a3ee5e6b4b0d input=70af9c90aa2e71b0]*/ + +#include "clinic/funcobject.c.h" + static PyObject * func_get_code(PyObject *self, void *Py_UNUSED(ignored)) { @@ -825,32 +832,46 @@ func_set_kwdefaults(PyObject *self, PyObject *value, void *Py_UNUSED(ignored)) return 0; } +/*[clinic input] +@critical_section +@getter +function.__annotate__ + +Get the code object for a function. +[clinic start generated code]*/ + static PyObject * -func_get_annotate(PyObject *self, void *Py_UNUSED(ignored)) +function___annotate___get_impl(PyFunctionObject *self) +/*[clinic end generated code: output=5ec7219ff2bda9e6 input=7f3db11e3c3329f3]*/ { - PyFunctionObject *op = _PyFunction_CAST(self); - if (op->func_annotate == NULL) { + if (self->func_annotate == NULL) { Py_RETURN_NONE; } - return Py_NewRef(op->func_annotate); + return Py_NewRef(self->func_annotate); } +/*[clinic input] +@critical_section +@setter +function.__annotate__ +[clinic start generated code]*/ + static int -func_set_annotate(PyObject *self, PyObject *value, void *Py_UNUSED(ignored)) +function___annotate___set_impl(PyFunctionObject *self, PyObject *value) +/*[clinic end generated code: output=05b7dfc07ada66cd input=eb6225e358d97448]*/ { - PyFunctionObject *op = _PyFunction_CAST(self); if (value == NULL) { PyErr_SetString(PyExc_TypeError, "__annotate__ cannot be deleted"); return -1; } if (Py_IsNone(value)) { - Py_XSETREF(op->func_annotate, value); + Py_XSETREF(self->func_annotate, value); return 0; } else if (PyCallable_Check(value)) { - Py_XSETREF(op->func_annotate, Py_XNewRef(value)); - Py_CLEAR(op->func_annotations); + Py_XSETREF(self->func_annotate, Py_XNewRef(value)); + Py_CLEAR(self->func_annotations); return 0; } else { @@ -860,27 +881,41 @@ func_set_annotate(PyObject *self, PyObject *value, void *Py_UNUSED(ignored)) } } +/*[clinic input] +@critical_section +@getter +function.__annotations__ + +Dict of annotations in a function object. +[clinic start generated code]*/ + static PyObject * -func_get_annotations(PyObject *self, void *Py_UNUSED(ignored)) +function___annotations___get_impl(PyFunctionObject *self) +/*[clinic end generated code: output=a4cf4c884c934cbb input=92643d7186c1ad0c]*/ { - PyFunctionObject *op = _PyFunction_CAST(self); PyObject *d = NULL; Py_BEGIN_CRITICAL_SECTION(self); - if (op->func_annotations == NULL && - (op->func_annotate == NULL || !PyCallable_Check(op->func_annotate))) { - op->func_annotations = PyDict_New(); - if (op->func_annotations == NULL) + if (self->func_annotations == NULL && + (self->func_annotate == NULL || !PyCallable_Check(self->func_annotate))) { + self->func_annotations = PyDict_New(); + if (self->func_annotations == NULL) return NULL; } - d = func_get_annotation_dict(op); + d = func_get_annotation_dict(self); Py_END_CRITICAL_SECTION(); return Py_XNewRef(d); } +/*[clinic input] +@critical_section +@setter +function.__annotations__ +[clinic start generated code]*/ + static int -func_set_annotations(PyObject *self, PyObject *value, void *Py_UNUSED(ignored)) +function___annotations___set_impl(PyFunctionObject *self, PyObject *value) +/*[clinic end generated code: output=a61795d4a95eede4 input=5302641f686f0463]*/ { - PyFunctionObject *op = _PyFunction_CAST(self); if (value == Py_None) value = NULL; /* Legal to del f.func_annotations. @@ -892,36 +927,50 @@ func_set_annotations(PyObject *self, PyObject *value, void *Py_UNUSED(ignored)) return -1; } Py_BEGIN_CRITICAL_SECTION(self); - Py_XSETREF(op->func_annotations, Py_XNewRef(value)); - Py_CLEAR(op->func_annotate); + Py_XSETREF(self->func_annotations, Py_XNewRef(value)); + Py_CLEAR(self->func_annotate); Py_END_CRITICAL_SECTION(); return 0; } +/*[clinic input] +@critical_section +@getter +function.__type_params__ + +Get the declared type parameters for a function. +[clinic start generated code]*/ + static PyObject * -func_get_type_params(PyObject *self, void *Py_UNUSED(ignored)) +function___type_params___get_impl(PyFunctionObject *self) +/*[clinic end generated code: output=eb844d7ffca517a8 input=0864721484293724]*/ { - PyFunctionObject *op = _PyFunction_CAST(self); - if (op->func_typeparams == NULL) { + if (self->func_typeparams == NULL) { return PyTuple_New(0); } - assert(PyTuple_Check(op->func_typeparams)); - return Py_NewRef(op->func_typeparams); + assert(PyTuple_Check(self->func_typeparams)); + return Py_NewRef(self->func_typeparams); } +/*[clinic input] +@critical_section +@setter +function.__type_params__ +[clinic start generated code]*/ + static int -func_set_type_params(PyObject *self, PyObject *value, void *Py_UNUSED(ignored)) +function___type_params___set_impl(PyFunctionObject *self, PyObject *value) +/*[clinic end generated code: output=038b4cda220e56fb input=3862fbd4db2b70e8]*/ { /* Not legal to del f.__type_params__ or to set it to anything * other than a tuple object. */ - PyFunctionObject *op = _PyFunction_CAST(self); if (value == NULL || !PyTuple_Check(value)) { PyErr_SetString(PyExc_TypeError, "__type_params__ must be set to a tuple"); return -1; } - Py_XSETREF(op->func_typeparams, Py_NewRef(value)); + Py_XSETREF(self->func_typeparams, Py_NewRef(value)); return 0; } @@ -940,22 +989,15 @@ static PyGetSetDef func_getsetlist[] = { {"__code__", func_get_code, func_set_code}, {"__defaults__", func_get_defaults, func_set_defaults}, {"__kwdefaults__", func_get_kwdefaults, func_set_kwdefaults}, - {"__annotations__", func_get_annotations, func_set_annotations}, - {"__annotate__", func_get_annotate, func_set_annotate}, + 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}, - {"__type_params__", func_get_type_params, func_set_type_params}, + FUNCTION___TYPE_PARAMS___GETSETDEF {NULL} /* Sentinel */ }; -/*[clinic input] -class function "PyFunctionObject *" "&PyFunction_Type" -[clinic start generated code]*/ -/*[clinic end generated code: output=da39a3ee5e6b4b0d input=70af9c90aa2e71b0]*/ - -#include "clinic/funcobject.c.h" - /* function.__new__() maintains the following invariants for closures. The closure must correspond to the free variables of the code object. From 591378617cf1440051490c009187ee956e8fb4df Mon Sep 17 00:00:00 2001 From: Xuanteng Huang Date: Tue, 4 Feb 2025 20:28:30 +0800 Subject: [PATCH 6/7] update news entry --- .../2025-01-19-09-07-44.gh-issue-128714.m1fyCB.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-01-19-09-07-44.gh-issue-128714.m1fyCB.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-01-19-09-07-44.gh-issue-128714.m1fyCB.rst index 75b1de1695216b..431032241e9157 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2025-01-19-09-07-44.gh-issue-128714.m1fyCB.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-01-19-09-07-44.gh-issue-128714.m1fyCB.rst @@ -1 +1 @@ -Fix the potential races when getting and setting function annotations, and add related tests. +Fix the potential races in get/set dunder methods ``__annotations__``, ``__annotate__`` and ``__type_params__`` for function object, and add related tests. From 70fefd563124777428e8bb5bc7fb78d0283c75e0 Mon Sep 17 00:00:00 2001 From: Xuanteng Huang Date: Thu, 6 Feb 2025 09:49:13 +0800 Subject: [PATCH 7/7] remove unnecessary critical sections --- Objects/funcobject.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Objects/funcobject.c b/Objects/funcobject.c index d5afda8503f0b6..169db2048c6a74 100644 --- a/Objects/funcobject.c +++ b/Objects/funcobject.c @@ -7,7 +7,6 @@ #include "pycore_modsupport.h" // _PyArg_NoKeywords() #include "pycore_object.h" // _PyObject_GC_UNTRACK() #include "pycore_pyerrors.h" // _PyErr_Occurred() -#include "pycore_critical_section.h" // Py_BEGIN_CRITICAL_SECTION() static const char * @@ -894,7 +893,6 @@ function___annotations___get_impl(PyFunctionObject *self) /*[clinic end generated code: output=a4cf4c884c934cbb input=92643d7186c1ad0c]*/ { PyObject *d = NULL; - Py_BEGIN_CRITICAL_SECTION(self); if (self->func_annotations == NULL && (self->func_annotate == NULL || !PyCallable_Check(self->func_annotate))) { self->func_annotations = PyDict_New(); @@ -902,7 +900,6 @@ function___annotations___get_impl(PyFunctionObject *self) return NULL; } d = func_get_annotation_dict(self); - Py_END_CRITICAL_SECTION(); return Py_XNewRef(d); } @@ -926,10 +923,8 @@ function___annotations___set_impl(PyFunctionObject *self, PyObject *value) "__annotations__ must be set to a dict object"); return -1; } - Py_BEGIN_CRITICAL_SECTION(self); Py_XSETREF(self->func_annotations, Py_XNewRef(value)); Py_CLEAR(self->func_annotate); - Py_END_CRITICAL_SECTION(); return 0; }