https://github.com/python/cpython/commit/be5386f03f3383516c0f6288a8dd99e53048698a
commit: be5386f03f3383516c0f6288a8dd99e53048698a
branch: 3.14
author: Neil Schemenauer <[email protected]>
committer: nascheme <[email protected]>
date: 2026-10-30T11:03:31-07:00
summary:
[3.14] gh-159136: Avoid stop-the-world when setting function name attributes
(gh-159269) (gh-159726)
Setting __name__, __qualname__, __doc__ or __module__ on a function
stopped the world in the free-threaded build since gh-154826 and
gh-154851, making functools.wraps() orders of magnitude slower with
multiple threads.
Use the per-object critical section and _PyObject_XSetRefDelayed()
instead, as gen_set_name() already does. Use acquire loads in the
readers and hold strong references across error reporting, tracing, and
eval-frame recording that may execute Python or detach the thread. The
__code__, __defaults__, __kwdefaults__ and __closure__ setters still
stop the world to clear func_version.
Document synchronization requirements for direct field access and the
lifetime of borrowed function attributes.
(cherry picked from commit 010bebbf9fd12bba1e3f40bdbddacd51a9c2bf20)
files:
A
Misc/NEWS.d/next/Core_and_Builtins/2026-10-12-20-00-00.gh-issue-159136.fnAttr.rst
M Doc/c-api/function.rst
M Doc/c-api/reflection.rst
M Include/cpython/funcobject.h
M Include/internal/pycore_object.h
M Lib/test/test_capi/test_function.py
M Lib/test/test_free_threading/test_functions.py
M Lib/test/test_funcattrs.py
M Modules/_testcapi/function.c
M Modules/_testinternalcapi.c
M Objects/funcobject.c
M Objects/genobject.c
M Objects/obmalloc.c
M Python/ceval.c
diff --git a/Doc/c-api/function.rst b/Doc/c-api/function.rst
index 60d609c0dd58e0f..c4cd4cf747ea92f 100644
--- a/Doc/c-api/function.rst
+++ b/Doc/c-api/function.rst
@@ -14,6 +14,10 @@ There are a few functions specific to Python functions.
The C structure used for functions.
+ In the free-threaded build, direct access to mutable fields requires
+ synchronization if another thread may modify the function. See
+ :ref:`freethreading-extensions-howto`.
+
.. c:var:: PyTypeObject PyFunction_Type
@@ -71,6 +75,11 @@ There are a few functions specific to Python functions.
This is normally a :class:`string <str>` containing the module name,
but can be set to any other object by Python code.
+ In the free-threaded build, if the attribute may be modified concurrently,
+ convert the borrowed reference to a strong reference with
:c:func:`Py_XNewRef`
+ immediately, before calling any other Python API. The same applies to
+ :c:func:`PyFunction_GET_MODULE`.
+
.. c:function:: PyObject* PyFunction_GetDefaults(PyObject *op)
diff --git a/Doc/c-api/reflection.rst b/Doc/c-api/reflection.rst
index 54fd5a064aa2ac2..37213dff216b564 100644
--- a/Doc/c-api/reflection.rst
+++ b/Doc/c-api/reflection.rst
@@ -96,6 +96,12 @@ Reflection
Return the name of *func* if it is a function, class or instance object,
else the
name of *func*\s type.
+ For a Python function or method, the returned pointer refers to the
+ function's name and must not be retained across operations that may change
+ that name. In the free-threaded build, copy the string before detaching
+ the thread state or executing Python code if the name may be modified
+ concurrently.
+
.. c:function:: const char* PyEval_GetFuncDesc(PyObject *func)
diff --git a/Include/cpython/funcobject.h b/Include/cpython/funcobject.h
index 69e23f19462c08b..8556b715252fb98 100644
--- a/Include/cpython/funcobject.h
+++ b/Include/cpython/funcobject.h
@@ -98,7 +98,12 @@ static inline PyObject* PyFunction_GET_GLOBALS(PyObject
*func) {
#define PyFunction_GET_GLOBALS(func)
PyFunction_GET_GLOBALS(_PyObject_CAST(func))
static inline PyObject* PyFunction_GET_MODULE(PyObject *func) {
+#ifdef Py_GIL_DISABLED
+ return (PyObject *)_Py_atomic_load_ptr_acquire(
+ &_PyFunction_CAST(func)->func_module);
+#else
return _PyFunction_CAST(func)->func_module;
+#endif
}
#define PyFunction_GET_MODULE(func) PyFunction_GET_MODULE(_PyObject_CAST(func))
@@ -118,7 +123,12 @@ static inline PyObject* PyFunction_GET_CLOSURE(PyObject
*func) {
#define PyFunction_GET_CLOSURE(func)
PyFunction_GET_CLOSURE(_PyObject_CAST(func))
static inline PyObject* PyFunction_GET_ANNOTATIONS(PyObject *func) {
+#ifdef Py_GIL_DISABLED
+ return (PyObject *)_Py_atomic_load_ptr_acquire(
+ &_PyFunction_CAST(func)->func_annotations);
+#else
return _PyFunction_CAST(func)->func_annotations;
+#endif
}
#define PyFunction_GET_ANNOTATIONS(func)
PyFunction_GET_ANNOTATIONS(_PyObject_CAST(func))
diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h
index a25998af96bb933..64fb419975670d7 100644
--- a/Include/internal/pycore_object.h
+++ b/Include/internal/pycore_object.h
@@ -767,6 +767,17 @@ _Py_TryIncref(PyObject *op)
#endif
}
+// Like Py_XSETREF, but publish the new value atomically and delay the decref
+// using QSBR in the free-threaded build. The caller must synchronize writers.
+#ifdef Py_GIL_DISABLED
+PyAPI_FUNC(void) _PyObject_XSetRefDelayed(PyObject **p_obj, PyObject *obj);
+#else
+static inline void _PyObject_XSetRefDelayed(PyObject **p_obj, PyObject *obj)
+{
+ Py_XSETREF(*p_obj, obj);
+}
+#endif
+
#ifdef Py_REF_DEBUG
extern void _PyInterpreterState_FinalizeRefTotal(PyInterpreterState *);
extern void _Py_FinalizeRefTotal(_PyRuntimeState *);
diff --git a/Lib/test/test_capi/test_function.py
b/Lib/test/test_capi/test_function.py
index c1a278e5d4da916..9a07e3106f3a22a 100644
--- a/Lib/test/test_capi/test_function.py
+++ b/Lib/test/test_capi/test_function.py
@@ -44,6 +44,12 @@ def some():
with self.assertRaises(SystemError):
_testcapi.function_get_module(None) # not a function
+ self.assertEqual(_testcapi.function_get_module_macro(some), module)
+ value = object()
+ some.__module__ = value
+ self.assertIs(_testcapi.function_get_module(some), value)
+ self.assertIs(_testcapi.function_get_module_macro(some), value)
+
def test_function_get_defaults(self):
# Test PyFunction_GetDefaults()
def some(
diff --git a/Lib/test/test_free_threading/test_functions.py
b/Lib/test/test_free_threading/test_functions.py
index 7a59638d27dc9d4..1874922d424db8b 100644
--- a/Lib/test/test_free_threading/test_functions.py
+++ b/Lib/test/test_free_threading/test_functions.py
@@ -81,6 +81,123 @@ def set_annotate():
threading_helper.run_concurrently(
[set_annotations, set_annotate] * (NUM_THREADS // 2))
+ def test_read_while_replacing_name_attrs(self):
+ # Readers load __name__, __qualname__, __doc__ and __module__
+ # without holding a lock. A writer thread replacing them must not
+ # free a value a reader thread is about to incref.
+ def target():
+ yield
+
+ def writer():
+ for _ in range(ITERS):
+ target.__name__ = random_string()
+ target.__qualname__ = random_string()
+ target.__doc__ = random_string()
+ target.__module__ = random_string()
+
+ def reader():
+ for _ in range(ITERS):
+ target.__name__
+ target.__qualname__
+ target.__doc__
+ target.__module__
+ # make_gen() copies __name__ and __qualname__.
+ gen = target()
+ gen.__name__
+ gen.__qualname__
+ gen.close()
+
+ threading_helper.run_concurrently(
+ [writer, reader] * (NUM_THREADS // 2))
+
+ def test_repr_while_replacing_qualname(self):
+ def target():
+ pass
+
+ def writer():
+ for _ in range(ITERS):
+ target.__qualname__ = random_string()
+
+ def reader():
+ for _ in range(ITERS):
+ self.assertRegex(repr(target), r"^<function .* at
0x[0-9a-fA-F]+>$")
+
+ threading_helper.run_concurrently(
+ [writer, reader] * (NUM_THREADS // 2))
+
+ def test_c_api_read_while_replacing_module(self):
+ from test.support import import_helper
+ _testcapi = import_helper.import_module('_testcapi')
+
+ def target():
+ pass
+
+ def writer():
+ for _ in range(ITERS):
+ target.__module__ = random_string()
+ target.__module__ = object()
+ target.__module__ = None
+
+ def reader():
+ for _ in range(ITERS):
+ for get_module in (_testcapi.function_get_module,
+ _testcapi.function_get_module_macro):
+ value = get_module(target)
+ self.assertIn(type(value), (str, object, type(None)))
+
+ threading_helper.run_concurrently(
+ [writer, reader] * (NUM_THREADS // 2))
+
+ def test_c_api_read_while_replacing_name(self):
+ from test.support import import_helper
+ _testlimitedcapi = import_helper.import_module('_testlimitedcapi')
+
+ def target():
+ pass
+
+ def writer():
+ for _ in range(ITERS):
+ # Non-ASCII names exercise lazy UTF-8 conversion.
+ target.__name__ = 'name_\u03bb' + random_string()
+
+ def reader():
+ for _ in range(ITERS):
+ name = _testlimitedcapi.eval_get_func_name(target)
+ self.assertIsInstance(name, str)
+
+ threading_helper.run_concurrently(
+ [writer, reader] * (NUM_THREADS // 2))
+
+ def test_argument_errors_while_replacing_qualname(self):
+ def target(x, *, y):
+ pass
+
+ def posonly(x, /):
+ pass
+
+ calls = (
+ lambda: target(),
+ lambda: target(1),
+ lambda: target(1, 2),
+ lambda: target(1, x=2),
+ lambda: target(1, extra=2),
+ lambda: posonly(x=1),
+ )
+
+ def writer():
+ for _ in range(ITERS):
+ target.__qualname__ = random_string()
+ posonly.__qualname__ = random_string()
+
+ def reader():
+ for _ in range(ITERS):
+ for call in calls:
+ with self.assertRaises(TypeError):
+ call()
+
+ threading_helper.run_concurrently(
+ [writer, reader] * (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.
diff --git a/Lib/test/test_funcattrs.py b/Lib/test/test_funcattrs.py
index 526d10d172d9a9c..02bdfce25a63faa 100644
--- a/Lib/test/test_funcattrs.py
+++ b/Lib/test/test_funcattrs.py
@@ -1,8 +1,10 @@
+import gc
import textwrap
import types
import typing
import unittest
import warnings
+import weakref
from test import support
@@ -50,6 +52,35 @@ class FunctionPropertiesTest(FuncAttrsTest):
def test_module(self):
self.assertEqual(self.b.__module__, __name__)
+ def test_qualname_replaced_during_argument_error(self):
+ class Qualname(str):
+ pass
+
+ def target(x, /):
+ pass
+
+ target.__qualname__ = Qualname('original_qualname')
+ ref = weakref.ref(target.__qualname__)
+
+ class Keyword(str):
+ __hash__ = str.__hash__
+
+ def __eq__(self, other):
+ # Replacing and freeing the qualname while comparisons
+ # execute Python must not leave a dangling pointer.
+ target.__qualname__ = 'new_qualname'
+ gc.collect()
+ return True
+
+ with self.assertRaisesRegex(
+ TypeError,
+ r"new_qualname\(\) got some positional-only arguments "
+ r"passed as keyword arguments: 'x'",
+ ):
+ target(**{Keyword('x'): 1})
+ gc.collect()
+ self.assertIsNone(ref())
+
def test_dir_includes_correct_attrs(self):
self.b.known_attr = 7
self.assertIn('known_attr', dir(self.b),
diff --git
a/Misc/NEWS.d/next/Core_and_Builtins/2026-10-12-20-00-00.gh-issue-159136.fnAttr.rst
b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-12-20-00-00.gh-issue-159136.fnAttr.rst
new file mode 100644
index 000000000000000..d092d270bdc4ffe
--- /dev/null
+++
b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-12-20-00-00.gh-issue-159136.fnAttr.rst
@@ -0,0 +1,4 @@
+Setting ``__name__``, ``__qualname__``, ``__doc__`` or ``__module__`` on a
+function no longer stops all threads in the free-threaded build. This fixes a
+large slowdown in multi-threaded code using :func:`functools.wraps` since
+3.14.7.
diff --git a/Modules/_testcapi/function.c b/Modules/_testcapi/function.c
index 40767adbd3f14a7..28420ce19f112e9 100644
--- a/Modules/_testcapi/function.c
+++ b/Modules/_testcapi/function.c
@@ -38,6 +38,13 @@ function_get_module(PyObject *self, PyObject *func)
}
+static PyObject *
+function_get_module_macro(PyObject *self, PyObject *func)
+{
+ return Py_XNewRef(PyFunction_GET_MODULE(func));
+}
+
+
static PyObject *
function_get_defaults(PyObject *self, PyObject *func)
{
@@ -134,6 +141,7 @@ static PyMethodDef test_methods[] = {
{"function_get_code", function_get_code, METH_O, NULL},
{"function_get_globals", function_get_globals, METH_O, NULL},
{"function_get_module", function_get_module, METH_O, NULL},
+ {"function_get_module_macro", function_get_module_macro, METH_O, NULL},
{"function_get_defaults", function_get_defaults, METH_O, NULL},
{"function_set_defaults", function_set_defaults, METH_VARARGS, NULL},
{"function_get_kw_defaults", function_get_kw_defaults, METH_O, NULL},
diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c
index 4a9ef56e3fcd003..9524e5436932c8e 100644
--- a/Modules/_testinternalcapi.c
+++ b/Modules/_testinternalcapi.c
@@ -670,7 +670,9 @@ record_eval(PyThreadState *tstate, struct
_PyInterpreterFrame *f, int exc)
assert(module != NULL);
module_state *state = get_module_state(module);
Py_DECREF(module);
- int res = PyList_Append(state->record_list, func->func_name);
+ PyObject *name =
Py_NewRef(FT_ATOMIC_LOAD_PTR_ACQUIRE(func->func_name));
+ int res = PyList_Append(state->record_list, name);
+ Py_DECREF(name);
if (res < 0) {
return NULL;
}
diff --git a/Objects/funcobject.c b/Objects/funcobject.c
index 2c645315012ed06..405b5a455f1203f 100644
--- a/Objects/funcobject.c
+++ b/Objects/funcobject.c
@@ -40,7 +40,8 @@ notify_func_watchers(PyInterpreterState *interp,
PyFunction_WatchEvent event,
if (cb(event, func, new_value) < 0) {
PyErr_FormatUnraisable(
"Exception ignored in %s watcher callback for function %U
at %p",
- func_event_name(event), func->func_qualname, func);
+ func_event_name(event),
+ FT_ATOMIC_LOAD_PTR_ACQUIRE(func->func_qualname), func);
}
}
i++;
@@ -422,7 +423,7 @@ PyFunction_GetModule(PyObject *op)
PyErr_BadInternalCall();
return NULL;
}
- return ((PyFunctionObject *) op) -> func_module;
+ return PyFunction_GET_MODULE(op);
}
PyObject *
@@ -552,9 +553,29 @@ PyFunction_SetClosure(PyObject *op, PyObject *closure)
return 0;
}
+static void
+func_set_annotations_and_annotate(PyFunctionObject *op, PyObject *annotations,
+ PyObject *annotate)
+{
+ _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(op);
+ // Care is needed, finalizers can run when references are dropped. Store
+ // both new values before dropping references to the old.
+ PyObject *old_annotations = op->func_annotations;
+ PyObject *old_annotate = op->func_annotate;
+ FT_ATOMIC_STORE_PTR_RELEASE(op->func_annotations, annotations);
+ FT_ATOMIC_STORE_PTR_RELEASE(op->func_annotate, annotate);
+ if (old_annotations != NULL && !_Py_IsImmortal(old_annotations)) {
+ _PyObject_XDecRefDelayed(old_annotations);
+ }
+ if (old_annotate != NULL && !_Py_IsImmortal(old_annotate)) {
+ _PyObject_XDecRefDelayed(old_annotate);
+ }
+}
+
static PyObject *
func_get_annotation_dict(PyFunctionObject *op)
{
+ _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(op);
if (op->func_annotations == NULL) {
if (op->func_annotate == NULL || !PyCallable_Check(op->func_annotate))
{
Py_RETURN_NONE;
@@ -570,7 +591,7 @@ func_get_annotation_dict(PyFunctionObject *op)
Py_DECREF(ann_dict);
return NULL;
}
- Py_XSETREF(op->func_annotations, ann_dict);
+ _PyObject_XSetRefDelayed(&op->func_annotations, ann_dict);
return ann_dict;
}
if (PyTuple_CheckExact(op->func_annotations)) {
@@ -592,7 +613,7 @@ func_get_annotation_dict(PyFunctionObject *op)
return NULL;
}
}
- Py_SETREF(op->func_annotations, ann_dict);
+ _PyObject_XSetRefDelayed(&op->func_annotations, ann_dict);
}
assert(PyDict_Check(op->func_annotations));
return op->func_annotations;
@@ -605,7 +626,11 @@ PyFunction_GetAnnotations(PyObject *op)
PyErr_BadInternalCall();
return NULL;
}
- return func_get_annotation_dict((PyFunctionObject *)op);
+ PyObject *d;
+ Py_BEGIN_CRITICAL_SECTION(op);
+ d = func_get_annotation_dict((PyFunctionObject *)op);
+ Py_END_CRITICAL_SECTION();
+ return d;
}
int
@@ -626,15 +651,9 @@ PyFunction_SetAnnotations(PyObject *op, PyObject
*annotations)
return -1;
}
PyFunctionObject *func = (PyFunctionObject *)op;
- 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);
+ Py_BEGIN_CRITICAL_SECTION(func);
+ func_set_annotations_and_annotate(func, annotations, NULL);
+ Py_END_CRITICAL_SECTION();
return 0;
}
@@ -689,11 +708,11 @@ func_set_code(PyObject *self, PyObject *value, void
*Py_UNUSED(ignored))
Py_ssize_t nclosure = (op->func_closure == NULL ? 0 :
PyTuple_GET_SIZE(op->func_closure));
if (nclosure != nfree) {
+ PyObject *name = FT_ATOMIC_LOAD_PTR_ACQUIRE(op->func_name);
PyErr_Format(PyExc_ValueError,
"%U() requires a code object with %zd free vars,"
" not %d",
- op->func_name,
- nclosure, nfree);
+ name, nclosure, nfree);
return -1;
}
@@ -725,7 +744,8 @@ static PyObject *
func_get_name(PyObject *self, void *Py_UNUSED(ignored))
{
PyFunctionObject *op = _PyFunction_CAST(self);
- return Py_NewRef(op->func_name);
+ PyObject *name = FT_ATOMIC_LOAD_PTR_ACQUIRE(op->func_name);
+ return Py_NewRef(name);
}
static int
@@ -739,12 +759,9 @@ func_set_name(PyObject *self, PyObject *value, void
*Py_UNUSED(ignored))
"__name__ must be set to a string object");
return -1;
}
- 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);
+ Py_BEGIN_CRITICAL_SECTION(op);
+ _PyObject_XSetRefDelayed(&op->func_name, Py_NewRef(value));
+ Py_END_CRITICAL_SECTION();
return 0;
}
@@ -752,7 +769,8 @@ static PyObject *
func_get_qualname(PyObject *self, void *Py_UNUSED(ignored))
{
PyFunctionObject *op = _PyFunction_CAST(self);
- return Py_NewRef(op->func_qualname);
+ PyObject *qualname = FT_ATOMIC_LOAD_PTR_ACQUIRE(op->func_qualname);
+ return Py_NewRef(qualname);
}
static int
@@ -766,12 +784,9 @@ func_set_qualname(PyObject *self, PyObject *value, void
*Py_UNUSED(ignored))
"__qualname__ must be set to a string object");
return -1;
}
- 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);
+ Py_BEGIN_CRITICAL_SECTION(op);
+ _PyObject_XSetRefDelayed(&op->func_qualname, Py_NewRef(value));
+ Py_END_CRITICAL_SECTION();
return 0;
}
@@ -779,7 +794,7 @@ static PyObject *
func_get_doc(PyObject *self, void *Py_UNUSED(ignored))
{
PyFunctionObject *op = _PyFunction_CAST(self);
- PyObject *doc = op->func_doc;
+ PyObject *doc = FT_ATOMIC_LOAD_PTR_ACQUIRE(op->func_doc);
if (doc == NULL) {
doc = Py_None;
}
@@ -791,12 +806,9 @@ func_set_doc(PyObject *self, PyObject *value, void
*Py_UNUSED(ignored))
{
/* Legal to del f.__doc__ or to set it to any object. */
PyFunctionObject *op = _PyFunction_CAST(self);
- PyInterpreterState *interp = _PyInterpreterState_GET();
- _PyEval_StopTheWorld(interp);
- PyObject *old_doc = op->func_doc;
- op->func_doc = Py_XNewRef(value);
- _PyEval_StartTheWorld(interp);
- Py_XDECREF(old_doc);
+ Py_BEGIN_CRITICAL_SECTION(op);
+ _PyObject_XSetRefDelayed(&op->func_doc, Py_XNewRef(value));
+ Py_END_CRITICAL_SECTION();
return 0;
}
@@ -804,7 +816,7 @@ static PyObject *
func_get_module(PyObject *self, void *Py_UNUSED(ignored))
{
PyFunctionObject *op = _PyFunction_CAST(self);
- PyObject *module = op->func_module;
+ PyObject *module = FT_ATOMIC_LOAD_PTR_ACQUIRE(op->func_module);
if (module == NULL) {
module = Py_None;
}
@@ -816,12 +828,9 @@ func_set_module(PyObject *self, PyObject *value, void
*Py_UNUSED(ignored))
{
/* Legal to del f.__module__ or to set it to any object. */
PyFunctionObject *op = _PyFunction_CAST(self);
- PyInterpreterState *interp = _PyInterpreterState_GET();
- _PyEval_StopTheWorld(interp);
- PyObject *old_module = op->func_module;
- op->func_module = Py_XNewRef(value);
- _PyEval_StartTheWorld(interp);
- Py_XDECREF(old_module);
+ Py_BEGIN_CRITICAL_SECTION(op);
+ _PyObject_XSetRefDelayed(&op->func_module, Py_XNewRef(value));
+ Py_END_CRITICAL_SECTION();
return 0;
}
@@ -955,12 +964,11 @@ function___annotate___set_impl(PyFunctionObject *self,
PyObject *value)
return -1;
}
if (Py_IsNone(value)) {
- Py_XSETREF(self->func_annotate, Py_NewRef(value));
+ _PyObject_XSetRefDelayed(&self->func_annotate, Py_NewRef(value));
return 0;
}
else if (PyCallable_Check(value)) {
- Py_XSETREF(self->func_annotate, Py_NewRef(value));
- Py_CLEAR(self->func_annotations);
+ func_set_annotations_and_annotate(self, NULL, Py_NewRef(value));
return 0;
}
else {
@@ -985,9 +993,10 @@ function___annotations___get_impl(PyFunctionObject *self)
PyObject *d = 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)
+ PyObject *ann_dict = PyDict_New();
+ if (ann_dict == NULL)
return NULL;
+ FT_ATOMIC_STORE_PTR_RELEASE(self->func_annotations, ann_dict);
}
d = func_get_annotation_dict(self);
return Py_XNewRef(d);
@@ -1014,8 +1023,7 @@ 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);
+ func_set_annotations_and_annotate(self, Py_XNewRef(value), NULL);
return 0;
}
@@ -1252,8 +1260,8 @@ static PyObject*
func_repr(PyObject *self)
{
PyFunctionObject *op = _PyFunction_CAST(self);
- return PyUnicode_FromFormat("<function %U at %p>",
- op->func_qualname, op);
+ PyObject *qualname = FT_ATOMIC_LOAD_PTR_ACQUIRE(op->func_qualname);
+ return PyUnicode_FromFormat("<function %U at %p>", qualname, op);
}
static int
diff --git a/Objects/genobject.c b/Objects/genobject.c
index bb2b8fdf1598c8b..96fed33358be3f3 100644
--- a/Objects/genobject.c
+++ b/Objects/genobject.c
@@ -926,10 +926,12 @@ make_gen(PyTypeObject *type, PyFunctionObject *func)
gen->gi_exc_state.exc_value = NULL;
gen->gi_exc_state.previous_item = NULL;
gen->gi_iframe.f_executable = PyStackRef_None;
- assert(func->func_name != NULL);
- gen->gi_name = Py_NewRef(func->func_name);
- assert(func->func_qualname != NULL);
- gen->gi_qualname = Py_NewRef(func->func_qualname);
+ PyObject *name = FT_ATOMIC_LOAD_PTR_ACQUIRE(func->func_name);
+ assert(name != NULL);
+ gen->gi_name = Py_NewRef(name);
+ PyObject *qualname = FT_ATOMIC_LOAD_PTR_ACQUIRE(func->func_qualname);
+ assert(qualname != NULL);
+ gen->gi_qualname = Py_NewRef(qualname);
_PyObject_GC_TRACK(gen);
return (PyObject *)gen;
}
diff --git a/Objects/obmalloc.c b/Objects/obmalloc.c
index c4ffa516cf78d2a..e8d1945f9de98a8 100644
--- a/Objects/obmalloc.c
+++ b/Objects/obmalloc.c
@@ -1338,6 +1338,16 @@ _PyObject_XDecRefDelayed(PyObject *ptr)
}
#endif
+#ifdef Py_GIL_DISABLED
+void
+_PyObject_XSetRefDelayed(PyObject **ptr, PyObject *value)
+{
+ PyObject *old = *ptr;
+ FT_ATOMIC_STORE_PTR_RELEASE(*ptr, value);
+ _PyObject_XDecRefDelayed(old);
+}
+#endif
+
static struct _mem_work_chunk *
work_queue_first(struct llist_node *head)
{
diff --git a/Python/ceval.c b/Python/ceval.c
index eca281e37cc7f6c..c0b22613a07445f 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -229,9 +229,9 @@ lltrace_resume_frame(_PyInterpreterFrame *frame)
}
PyFunctionObject *f = (PyFunctionObject *)fobj;
PyObject *exc = PyErr_GetRaisedException();
- PyObject *name = f->func_qualname;
+ PyObject *name = Py_XNewRef(FT_ATOMIC_LOAD_PTR_ACQUIRE(f->func_qualname));
if (name == NULL) {
- name = f->func_name;
+ name = Py_XNewRef(FT_ATOMIC_LOAD_PTR_ACQUIRE(f->func_name));
}
printf("\nResuming frame");
if (name) {
@@ -239,12 +239,15 @@ lltrace_resume_frame(_PyInterpreterFrame *frame)
if (PyObject_Print(name, stdout, 0) < 0) {
PyErr_Clear();
}
+ Py_DECREF(name);
}
- if (f->func_module) {
+ PyObject *module = Py_XNewRef(FT_ATOMIC_LOAD_PTR_ACQUIRE(f->func_module));
+ if (module) {
printf(" in module ");
- if (PyObject_Print(f->func_module, stdout, 0) < 0) {
+ if (PyObject_Print(module, stdout, 0) < 0) {
PyErr_Clear();
}
+ Py_DECREF(module);
}
printf("\n");
fflush(stdout);
@@ -1551,7 +1554,7 @@ too_many_positional(PyThreadState *tstate, PyCodeObject
*co,
static int
positional_only_passed_as_keyword(PyThreadState *tstate, PyCodeObject *co,
Py_ssize_t kwcount, PyObject* kwnames,
- PyObject *qualname)
+ PyFunctionObject *func)
{
int posonly_conflicts = 0;
PyObject* posonly_names = PyList_New(0);
@@ -1598,7 +1601,8 @@ positional_only_passed_as_keyword(PyThreadState *tstate,
PyCodeObject *co,
_PyErr_Format(tstate, PyExc_TypeError,
"%U() got some positional-only arguments passed"
" as keyword arguments: '%U'",
- qualname, error_names);
+ FT_ATOMIC_LOAD_PTR_ACQUIRE(func->func_qualname),
+ error_names);
Py_DECREF(error_names);
goto fail;
}
@@ -1757,8 +1761,8 @@ initialize_locals(PyThreadState *tstate, PyFunctionObject
*func,
if (keyword == NULL || !PyUnicode_Check(keyword)) {
_PyErr_Format(tstate, PyExc_TypeError,
- "%U() keywords must be strings",
- func->func_qualname);
+ "%U() keywords must be strings",
+ FT_ATOMIC_LOAD_PTR_ACQUIRE(func->func_qualname));
goto kw_fail;
}
@@ -1790,7 +1794,7 @@ initialize_locals(PyThreadState *tstate, PyFunctionObject
*func,
if (co->co_posonlyargcount
&& positional_only_passed_as_keyword(tstate, co,
kwcount, kwnames,
- func->func_qualname))
+ func))
{
goto kw_fail;
}
@@ -1811,15 +1815,16 @@ initialize_locals(PyThreadState *tstate,
PyFunctionObject *func,
}
}
+ PyObject *qualname =
FT_ATOMIC_LOAD_PTR_ACQUIRE(func->func_qualname);
if (suggestion_keyword) {
_PyErr_Format(tstate, PyExc_TypeError,
"%U() got an unexpected keyword argument '%S'.
Did you mean '%S'?",
- func->func_qualname, keyword,
suggestion_keyword);
+ qualname, keyword, suggestion_keyword);
Py_DECREF(suggestion_keyword);
} else {
_PyErr_Format(tstate, PyExc_TypeError,
"%U() got an unexpected keyword argument '%S'",
- func->func_qualname, keyword);
+ qualname, keyword);
}
goto kw_fail;
@@ -1840,8 +1845,9 @@ initialize_locals(PyThreadState *tstate, PyFunctionObject
*func,
kw_found:
if (PyStackRef_AsPyObjectBorrow(localsplus[j]) != NULL) {
_PyErr_Format(tstate, PyExc_TypeError,
- "%U() got multiple values for argument '%S'",
- func->func_qualname, keyword);
+ "%U() got multiple values for argument '%S'",
+ FT_ATOMIC_LOAD_PTR_ACQUIRE(func->func_qualname),
+ keyword);
goto kw_fail;
}
localsplus[j] = value_stackref;
@@ -1851,7 +1857,7 @@ initialize_locals(PyThreadState *tstate, PyFunctionObject
*func,
/* Check the number of positional arguments */
if ((argcount > co->co_argcount) && !(co->co_flags & CO_VARARGS)) {
too_many_positional(tstate, co, argcount, func->func_defaults,
localsplus,
- func->func_qualname);
+ FT_ATOMIC_LOAD_PTR_ACQUIRE(func->func_qualname));
goto fail_post_args;
}
@@ -1867,7 +1873,7 @@ initialize_locals(PyThreadState *tstate, PyFunctionObject
*func,
}
if (missing) {
missing_arguments(tstate, co, missing, defcount, localsplus,
- func->func_qualname);
+ FT_ATOMIC_LOAD_PTR_ACQUIRE(func->func_qualname));
goto fail_post_args;
}
if (n > m)
@@ -1906,7 +1912,7 @@ initialize_locals(PyThreadState *tstate, PyFunctionObject
*func,
}
if (missing) {
missing_arguments(tstate, co, missing, -1, localsplus,
- func->func_qualname);
+ FT_ATOMIC_LOAD_PTR_ACQUIRE(func->func_qualname));
goto fail_post_args;
}
}
@@ -3042,14 +3048,40 @@ PyEval_MergeCompilerFlags(PyCompilerFlags *cf)
return result;
}
+static const char *
+functionobject_get_name(PyFunctionObject *f)
+{
+#ifdef Py_GIL_DISABLED
+ for (;;) {
+ PyObject *name = Py_NewRef(FT_ATOMIC_LOAD_PTR_ACQUIRE(f->func_name));
+ // UTF-8 conversion may detach while acquiring the string's lock.
+ // This could result in func_name being freed if func_name was
+ // recently replaced using _PyObject_XSetRefDelayed(). The returned
+ // char* is only valid until the next quiescent point.
+ const char *result = PyUnicode_AsUTF8(name);
+ if (result == NULL) {
+ Py_DECREF(name);
+ return NULL;
+ }
+ int unchanged = name == FT_ATOMIC_LOAD_PTR_ACQUIRE(f->func_name);
+ Py_DECREF(name);
+ if (unchanged) {
+ return result;
+ }
+ }
+#else
+ return PyUnicode_AsUTF8(f->func_name);
+#endif
+}
const char *
PyEval_GetFuncName(PyObject *func)
{
if (PyMethod_Check(func))
return PyEval_GetFuncName(PyMethod_GET_FUNCTION(func));
- else if (PyFunction_Check(func))
- return PyUnicode_AsUTF8(((PyFunctionObject*)func)->func_name);
+ else if (PyFunction_Check(func)) {
+ return functionobject_get_name((PyFunctionObject *)func);
+ }
else if (PyCFunction_Check(func))
return ((PyCFunctionObject*)func)->m_ml->ml_name;
else
_______________________________________________
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]