]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.14] gh-145685: Avoid contention on TYPE_LOCK in super() lookups (gh-145775) (...
authorSam Gross <colesbury@gmail.com>
Wed, 11 Mar 2026 11:50:13 +0000 (07:50 -0400)
committerGitHub <noreply@github.com>
Wed, 11 Mar 2026 11:50:13 +0000 (11:50 +0000)
(cherry picked from commit bdf6de8c3f0c2ec0d737f38014a32c1eed02c7f1)

Include/internal/pycore_stackref.h
Objects/typeobject.c
Tools/ftscalingbench/ftscalingbench.py

index 52acd918c9b9f9b7640367e702c1fd8b148be2b8..76f6333739dfd6b4a0558bc80f7790e3ac0c5ba5 100644 (file)
@@ -735,6 +735,13 @@ _PyThreadState_PushCStackRef(PyThreadState *tstate, _PyCStackRef *ref)
     ref->ref = PyStackRef_NULL;
 }
 
+static inline void
+_PyThreadState_PushCStackRefNew(PyThreadState *tstate, _PyCStackRef *ref, PyObject *obj)
+{
+    _PyThreadState_PushCStackRef(tstate, ref);
+    ref->ref = PyStackRef_FromPyObjectNew(obj);
+}
+
 static inline void
 _PyThreadState_PopCStackRef(PyThreadState *tstate, _PyCStackRef *ref)
 {
index 232ead49a7f098c35cbe1efefde45e5a317517b5..9777055c5f23133b9b762ae41e50506d7dffccc3 100644 (file)
@@ -11820,18 +11820,16 @@ _super_lookup_descr(PyTypeObject *su_type, PyTypeObject *su_obj_type, PyObject *
     PyObject *mro, *res;
     Py_ssize_t i, n;
 
-    BEGIN_TYPE_LOCK();
     mro = lookup_tp_mro(su_obj_type);
-    /* keep a strong reference to mro because su_obj_type->tp_mro can be
-       replaced during PyDict_GetItemRef(dict, name, &res) and because
-       another thread can modify it after we end the critical section
-       below  */
-    Py_XINCREF(mro);
-    END_TYPE_LOCK();
-
     if (mro == NULL)
         return NULL;
 
+    /* Keep a strong reference to mro because su_obj_type->tp_mro can be
+       replaced during PyDict_GetItemRef(dict, name, &res). */
+    PyThreadState *tstate = _PyThreadState_GET();
+    _PyCStackRef mro_ref;
+    _PyThreadState_PushCStackRefNew(tstate, &mro_ref, mro);
+
     assert(PyTuple_Check(mro));
     n = PyTuple_GET_SIZE(mro);
 
@@ -11842,7 +11840,7 @@ _super_lookup_descr(PyTypeObject *su_type, PyTypeObject *su_obj_type, PyObject *
     }
     i++;  /* skip su->type (if any)  */
     if (i >= n) {
-        Py_DECREF(mro);
+        _PyThreadState_PopCStackRef(tstate, &mro_ref);
         return NULL;
     }
 
@@ -11853,13 +11851,13 @@ _super_lookup_descr(PyTypeObject *su_type, PyTypeObject *su_obj_type, PyObject *
 
         if (PyDict_GetItemRef(dict, name, &res) != 0) {
             // found or error
-            Py_DECREF(mro);
+            _PyThreadState_PopCStackRef(tstate, &mro_ref);
             return res;
         }
 
         i++;
     } while (i < n);
-    Py_DECREF(mro);
+    _PyThreadState_PopCStackRef(tstate, &mro_ref);
     return NULL;
 }
 
index b815376b7ed56f8df1c9fed237163c8205b84a94..cc7d8575f5cfc9ee8ec7d5ce7992866d9da272b6 100644 (file)
@@ -201,6 +201,23 @@ def instantiate_dataclass():
     for _ in range(1000 * WORK_SCALE):
         obj = MyDataClass(x=1, y=2, z=3)
 
+@register_benchmark
+def super_call():
+    # TODO: super() on the same class from multiple threads still doesn't
+    # scale well, so use a class per-thread here for now.
+    class Base:
+        def method(self):
+            return 1
+
+    class Derived(Base):
+        def method(self):
+            return super().method()
+
+    obj = Derived()
+    for _ in range(1000 * WORK_SCALE):
+        obj.method()
+
+
 def bench_one_thread(func):
     t0 = time.perf_counter_ns()
     func()