gh-159136: Avoid stop-the-world when setting function name attributes (gh-159269)

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.
diff --git a/Doc/c-api/function.rst b/Doc/c-api/function.rst
index 609b5e8..75a4adc 100644
--- a/Doc/c-api/function.rst
+++ b/Doc/c-api/function.rst
@@ -14,6 +14,10 @@
 
    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 @@
    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 54fd5a0..37213df 100644
--- a/Doc/c-api/reflection.rst
+++ b/Doc/c-api/reflection.rst
@@ -96,6 +96,12 @@
    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 9e1599a7..bcdd9df 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/Lib/test/test_capi/test_function.py b/Lib/test/test_capi/test_function.py
index c1a278e..9a07e31 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 7a59638..1874922 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 9ebb76f..f091a6c 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 0000000..21fe270
--- /dev/null
+++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-12-20-00-00.gh-issue-159136.fnAttr.rst
@@ -0,0 +1,8 @@
+Setting ``__name__``, ``__qualname__``, ``__doc__`` or ``__module__`` on a
+function no longer stops all threads in the free-threaded build. The setters
+now use the per-object lock and defer freeing the old value with QSBR, the
+same approach used for generator ``__name__``. This fixes a large slowdown
+in multi-threaded code using :func:`functools.wraps` since 3.14.7. Readers
+of these attributes now use atomic loads, and argument error reporting reads
+``__qualname__`` after keyword comparisons, which may execute Python code and
+replace it. Previously this could use a freed object.
diff --git a/Modules/_testcapi/function.c b/Modules/_testcapi/function.c
index 40767ad..28420ce 100644
--- a/Modules/_testcapi/function.c
+++ b/Modules/_testcapi/function.c
@@ -39,6 +39,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)
 {
     PyObject *defaults = PyFunction_GetDefaults(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 f629b8e..479297a 100644
--- a/Modules/_testinternalcapi.c
+++ b/Modules/_testinternalcapi.c
@@ -1182,7 +1182,8 @@ 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);
+        int res = PyList_Append(state->record_list,
+                                FT_ATOMIC_LOAD_PTR_ACQUIRE(func->func_name));
         if (res < 0) {
             return NULL;
         }
@@ -1243,7 +1244,8 @@ record_eval_interp(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);
+        int res = PyList_Append(state->record_list,
+                                FT_ATOMIC_LOAD_PTR_ACQUIRE(func->func_name));
         if (res < 0) {
             return NULL;
         }
diff --git a/Objects/funcobject.c b/Objects/funcobject.c
index 4d3da23..f4adaf3 100644
--- a/Objects/funcobject.c
+++ b/Objects/funcobject.c
@@ -41,7 +41,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++;
@@ -405,7 +406,7 @@ PyFunction_GetModule(PyObject *op)
         PyErr_BadInternalCall();
         return NULL;
     }
-    return ((PyFunctionObject *) op) -> func_module;
+    return PyFunction_GET_MODULE(op);
 }
 
 PyObject *
@@ -535,9 +536,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;
@@ -554,7 +575,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)) {
@@ -576,7 +597,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;
@@ -589,7 +610,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
@@ -610,15 +635,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;
 }
 
@@ -673,11 +692,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;
     }
 
@@ -709,7 +728,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
@@ -723,12 +743,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;
 }
 
@@ -736,7 +753,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
@@ -751,12 +769,9 @@ func_set_qualname(PyObject *self, PyObject *value, void *Py_UNUSED(ignored))
         return -1;
     }
     handle_func_event(PyFunction_EVENT_MODIFY_QUALNAME, (PyFunctionObject *) op, 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);
+    Py_BEGIN_CRITICAL_SECTION(op);
+    _PyObject_XSetRefDelayed(&op->func_qualname, Py_NewRef(value));
+    Py_END_CRITICAL_SECTION();
     return 0;
 }
 
@@ -764,7 +779,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;
     }
@@ -776,12 +791,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;
 }
 
@@ -789,7 +801,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;
     }
@@ -801,12 +813,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;
 }
 
@@ -934,12 +943,11 @@ function___annotate___set_impl(PyFunctionObject *self, PyObject *value)
 /*[clinic end generated code: output=05b7dfc07ada66cd input=eb6225e358d97448]*/
 {
     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 {
@@ -964,9 +972,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);
@@ -993,8 +1002,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;
 }
 
@@ -1224,8 +1232,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 c313002..05a331e 100644
--- a/Objects/genobject.c
+++ b/Objects/genobject.c
@@ -1117,10 +1117,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/Python/ceval.c b/Python/ceval.c
index 43dc48f..a6a21f0 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -1707,7 +1707,7 @@ suggest_missing_self(PyFunctionObject *func, 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);
@@ -1754,7 +1754,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;
     }
@@ -1850,8 +1851,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;
             }
 
@@ -1883,7 +1884,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;
                 }
@@ -1904,15 +1905,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;
@@ -1933,8 +1935,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;
@@ -1945,7 +1948,8 @@ initialize_locals(PyThreadState *tstate, PyFunctionObject *func,
     if ((argcount > co->co_argcount) && !(co->co_flags & CO_VARARGS)) {
         int missing_self_hint = suggest_missing_self(func, co, first_argument, argcount);
         too_many_positional(tstate, co, argcount, func->func_defaults, localsplus,
-                            func->func_qualname, missing_self_hint);
+                            FT_ATOMIC_LOAD_PTR_ACQUIRE(func->func_qualname),
+                            missing_self_hint);
         goto fail_post_args;
     }
 
@@ -1961,7 +1965,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)
@@ -2000,7 +2004,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;
         }
     }
@@ -2956,14 +2960,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
diff --git a/Python/ceval.h b/Python/ceval.h
index 0437ab8..00db672 100644
--- a/Python/ceval.h
+++ b/Python/ceval.h
@@ -257,9 +257,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) {
@@ -267,12 +267,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);