Skip to content

Commit d6d9de8

Browse files
committed
gh-131586: Avoid refcount contention in context managers
This avoid reference count contention in the free threading build when calling special methods like `__enter__` and `__exit__`.
1 parent d260631 commit d6d9de8

File tree

11 files changed

+202
-166
lines changed

11 files changed

+202
-166
lines changed

Include/internal/pycore_object.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -950,7 +950,7 @@ extern int _PyObject_IsInstanceDictEmpty(PyObject *);
950950

951951
// Export for 'math' shared extension
952952
PyAPI_FUNC(PyObject*) _PyObject_LookupSpecial(PyObject *, PyObject *);
953-
PyAPI_FUNC(PyObject*) _PyObject_LookupSpecialMethod(PyObject *self, PyObject *attr, PyObject **self_or_null);
953+
PyAPI_FUNC(int) _PyObject_LookupSpecialMethod(PyObject *attr, _PyStackRef *method_and_self);
954954

955955
// Calls the method named `attr` on `self`, but does not set an exception if
956956
// the attribute does not exist.

Include/internal/pycore_opcode_metadata.h

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_uop_ids.h

Lines changed: 88 additions & 87 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_uop_metadata.h

Lines changed: 5 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Objects/typeobject.c

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2774,32 +2774,37 @@ _PyObject_LookupSpecial(PyObject *self, PyObject *attr)
27742774
return res;
27752775
}
27762776

2777-
/* Steals a reference to self */
2778-
PyObject *
2779-
_PyObject_LookupSpecialMethod(PyObject *self, PyObject *attr, PyObject **self_or_null)
2777+
// Lookup the method name `attr` on `self`. On entry, `method_and_self[0]`
2778+
// is null and `method_and_self[1]` is `self`. On exit, `method_and_self[0]`
2779+
// is the method object and `method_and_self[1]` is `self` if the method is
2780+
// not bound.
2781+
// Return 0 on success, -1 on error or if the method is missing.
2782+
int
2783+
_PyObject_LookupSpecialMethod(PyObject *attr, _PyStackRef *method_and_self)
27802784
{
2781-
PyObject *res;
2782-
2783-
res = _PyType_LookupRef(Py_TYPE(self), attr);
2784-
if (res == NULL) {
2785-
Py_DECREF(self);
2786-
*self_or_null = NULL;
2787-
return NULL;
2785+
PyObject *self = PyStackRef_AsPyObjectBorrow(method_and_self[1]);
2786+
_PyType_LookupStackRefAndVersion(Py_TYPE(self), attr, &method_and_self[0]);
2787+
PyObject *method_o = PyStackRef_AsPyObjectBorrow(method_and_self[0]);
2788+
if (method_o == NULL) {
2789+
return -1;
27882790
}
27892791

2790-
if (_PyType_HasFeature(Py_TYPE(res), Py_TPFLAGS_METHOD_DESCRIPTOR)) {
2792+
if (_PyType_HasFeature(Py_TYPE(method_o), Py_TPFLAGS_METHOD_DESCRIPTOR)) {
27912793
/* Avoid temporary PyMethodObject */
2792-
*self_or_null = self;
2794+
return 0;
27932795
}
2794-
else {
2795-
descrgetfunc f = Py_TYPE(res)->tp_descr_get;
2796-
if (f != NULL) {
2797-
Py_SETREF(res, f(res, self, (PyObject *)(Py_TYPE(self))));
2796+
2797+
descrgetfunc f = Py_TYPE(method_o)->tp_descr_get;
2798+
if (f != NULL) {
2799+
PyObject *func = f(method_o, self, (PyObject *)(Py_TYPE(self)));
2800+
if (func == NULL) {
2801+
return -1;
27982802
}
2799-
*self_or_null = NULL;
2800-
Py_DECREF(self);
2803+
PyStackRef_CLEAR(method_and_self[0]); // clear method
2804+
method_and_self[0] = PyStackRef_FromPyObjectSteal(func);
28012805
}
2802-
return res;
2806+
PyStackRef_CLEAR(method_and_self[1]); // clear self
2807+
return 0;
28032808
}
28042809

28052810
static int

Python/bytecodes.c

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3345,25 +3345,29 @@ dummy_func(
33453345
_FOR_ITER_GEN_FRAME +
33463346
_PUSH_FRAME;
33473347

3348-
inst(LOAD_SPECIAL, (owner -- attr, self_or_null)) {
3349-
assert(oparg <= SPECIAL_MAX);
3350-
PyObject *owner_o = PyStackRef_AsPyObjectSteal(owner);
3348+
op(_INSERT_NULL, (arg -- args[2])) {
3349+
args[0] = PyStackRef_NULL;
3350+
args[1] = arg;
3351+
DEAD(arg);
3352+
}
3353+
3354+
op(_LOAD_SPECIAL, (method_and_self[2] -- method_and_self[2])) {
33513355
PyObject *name = _Py_SpecialMethods[oparg].name;
3352-
PyObject *self_or_null_o;
3353-
PyObject *attr_o = _PyObject_LookupSpecialMethod(owner_o, name, &self_or_null_o);
3354-
if (attr_o == NULL) {
3356+
int err = _PyObject_LookupSpecialMethod(name, method_and_self);
3357+
if (err < 0) {
33553358
if (!_PyErr_Occurred(tstate)) {
33563359
_PyErr_Format(tstate, PyExc_TypeError,
33573360
_Py_SpecialMethods[oparg].error,
3358-
Py_TYPE(owner_o)->tp_name);
3361+
PyStackRef_TYPE(method_and_self[1])->tp_name);
33593362
}
33603363
ERROR_IF(true, error);
33613364
}
3362-
attr = PyStackRef_FromPyObjectSteal(attr_o);
3363-
self_or_null = self_or_null_o == NULL ?
3364-
PyStackRef_NULL : PyStackRef_FromPyObjectSteal(self_or_null_o);
33653365
}
33663366

3367+
macro(LOAD_SPECIAL) =
3368+
_INSERT_NULL +
3369+
_LOAD_SPECIAL;
3370+
33673371
inst(WITH_EXCEPT_START, (exit_func, exit_self, lasti, unused, val -- exit_func, exit_self, lasti, unused, val, res)) {
33683372
/* At the top of the stack are 4 values:
33693373
- val: TOP = exc_info()

0 commit comments

Comments
 (0)