]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-145685: Avoid contention on TYPE_LOCK in super() lookups (gh-145775)
authorSam Gross <colesbury@gmail.com>
Wed, 11 Mar 2026 11:19:32 +0000 (07:19 -0400)
committerGitHub <noreply@github.com>
Wed, 11 Mar 2026 11:19:32 +0000 (07:19 -0400)
Include/internal/pycore_stackref.h
Objects/typeobject.c
Tools/ftscalingbench/ftscalingbench.py

index 69d667b4be47d2a60800432bd8dad501b1e35654..188da775eb1cc75ba8d81a0121b612030604b083 100644 (file)
@@ -770,6 +770,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 5dc96bf251b384ae605d9097eee35721de56006d..bb473dce68f65bc23fbe854c38f4790306fdbc7b 100644 (file)
@@ -12360,18 +12360,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);
 
@@ -12382,7 +12380,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;
     }
 
@@ -12393,13 +12391,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 f60f5adba5c12cbc9fc9b180698cb2439e057de6..8d8bbc88e7f30ab26316291a8542de58dd936d49 100644 (file)
@@ -241,6 +241,22 @@ def instantiate_typing_namedtuple():
     for _ in range(1000 * WORK_SCALE):
         obj = MyTypingNamedTuple(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()
+
 
 @register_benchmark
 def deepcopy():