Skip to content

Commit 6d28aaf

Browse files
authored
[3.14] gh-145685: Avoid contention on TYPE_LOCK in super() lookups (gh-145775) (#145804)
(cherry picked from commit bdf6de8)
1 parent f4d5332 commit 6d28aaf

File tree

3 files changed

+33
-11
lines changed

3 files changed

+33
-11
lines changed

Include/internal/pycore_stackref.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -735,6 +735,13 @@ _PyThreadState_PushCStackRef(PyThreadState *tstate, _PyCStackRef *ref)
735735
ref->ref = PyStackRef_NULL;
736736
}
737737

738+
static inline void
739+
_PyThreadState_PushCStackRefNew(PyThreadState *tstate, _PyCStackRef *ref, PyObject *obj)
740+
{
741+
_PyThreadState_PushCStackRef(tstate, ref);
742+
ref->ref = PyStackRef_FromPyObjectNew(obj);
743+
}
744+
738745
static inline void
739746
_PyThreadState_PopCStackRef(PyThreadState *tstate, _PyCStackRef *ref)
740747
{

Objects/typeobject.c

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11820,18 +11820,16 @@ _super_lookup_descr(PyTypeObject *su_type, PyTypeObject *su_obj_type, PyObject *
1182011820
PyObject *mro, *res;
1182111821
Py_ssize_t i, n;
1182211822

11823-
BEGIN_TYPE_LOCK();
1182411823
mro = lookup_tp_mro(su_obj_type);
11825-
/* keep a strong reference to mro because su_obj_type->tp_mro can be
11826-
replaced during PyDict_GetItemRef(dict, name, &res) and because
11827-
another thread can modify it after we end the critical section
11828-
below */
11829-
Py_XINCREF(mro);
11830-
END_TYPE_LOCK();
11831-
1183211824
if (mro == NULL)
1183311825
return NULL;
1183411826

11827+
/* Keep a strong reference to mro because su_obj_type->tp_mro can be
11828+
replaced during PyDict_GetItemRef(dict, name, &res). */
11829+
PyThreadState *tstate = _PyThreadState_GET();
11830+
_PyCStackRef mro_ref;
11831+
_PyThreadState_PushCStackRefNew(tstate, &mro_ref, mro);
11832+
1183511833
assert(PyTuple_Check(mro));
1183611834
n = PyTuple_GET_SIZE(mro);
1183711835

@@ -11842,7 +11840,7 @@ _super_lookup_descr(PyTypeObject *su_type, PyTypeObject *su_obj_type, PyObject *
1184211840
}
1184311841
i++; /* skip su->type (if any) */
1184411842
if (i >= n) {
11845-
Py_DECREF(mro);
11843+
_PyThreadState_PopCStackRef(tstate, &mro_ref);
1184611844
return NULL;
1184711845
}
1184811846

@@ -11853,13 +11851,13 @@ _super_lookup_descr(PyTypeObject *su_type, PyTypeObject *su_obj_type, PyObject *
1185311851

1185411852
if (PyDict_GetItemRef(dict, name, &res) != 0) {
1185511853
// found or error
11856-
Py_DECREF(mro);
11854+
_PyThreadState_PopCStackRef(tstate, &mro_ref);
1185711855
return res;
1185811856
}
1185911857

1186011858
i++;
1186111859
} while (i < n);
11862-
Py_DECREF(mro);
11860+
_PyThreadState_PopCStackRef(tstate, &mro_ref);
1186311861
return NULL;
1186411862
}
1186511863

Tools/ftscalingbench/ftscalingbench.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,23 @@ def instantiate_dataclass():
201201
for _ in range(1000 * WORK_SCALE):
202202
obj = MyDataClass(x=1, y=2, z=3)
203203

204+
@register_benchmark
205+
def super_call():
206+
# TODO: super() on the same class from multiple threads still doesn't
207+
# scale well, so use a class per-thread here for now.
208+
class Base:
209+
def method(self):
210+
return 1
211+
212+
class Derived(Base):
213+
def method(self):
214+
return super().method()
215+
216+
obj = Derived()
217+
for _ in range(1000 * WORK_SCALE):
218+
obj.method()
219+
220+
204221
def bench_one_thread(func):
205222
t0 = time.perf_counter_ns()
206223
func()

0 commit comments

Comments
 (0)