Skip to content

Commit da53660

Browse files
authored
gh-131586: Avoid refcount contention in context managers (gh-131851)
This avoid reference count contention in the free threading build when calling special methods like `__enter__` and `__exit__`.
1 parent 8dfa840 commit da53660

11 files changed

+244
-204
lines changed

Include/internal/pycore_object.h

+1-1
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

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_uop_ids.h

+104-103
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_uop_metadata.h

+6-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Objects/typeobject.c

+24-19
Original file line numberDiff line numberDiff line change
@@ -2794,32 +2794,37 @@ _PyObject_LookupSpecial(PyObject *self, PyObject *attr)
27942794
return res;
27952795
}
27962796

2797-
/* Steals a reference to self */
2798-
PyObject *
2799-
_PyObject_LookupSpecialMethod(PyObject *self, PyObject *attr, PyObject **self_or_null)
2797+
// Lookup the method name `attr` on `self`. On entry, `method_and_self[0]`
2798+
// is null and `method_and_self[1]` is `self`. On exit, `method_and_self[0]`
2799+
// is the method object and `method_and_self[1]` is `self` if the method is
2800+
// not bound.
2801+
// Return 1 on success, -1 on error, and 0 if the method is missing.
2802+
int
2803+
_PyObject_LookupSpecialMethod(PyObject *attr, _PyStackRef *method_and_self)
28002804
{
2801-
PyObject *res;
2802-
2803-
res = _PyType_LookupRef(Py_TYPE(self), attr);
2804-
if (res == NULL) {
2805-
Py_DECREF(self);
2806-
*self_or_null = NULL;
2807-
return NULL;
2805+
PyObject *self = PyStackRef_AsPyObjectBorrow(method_and_self[1]);
2806+
_PyType_LookupStackRefAndVersion(Py_TYPE(self), attr, &method_and_self[0]);
2807+
PyObject *method_o = PyStackRef_AsPyObjectBorrow(method_and_self[0]);
2808+
if (method_o == NULL) {
2809+
return 0;
28082810
}
28092811

2810-
if (_PyType_HasFeature(Py_TYPE(res), Py_TPFLAGS_METHOD_DESCRIPTOR)) {
2812+
if (_PyType_HasFeature(Py_TYPE(method_o), Py_TPFLAGS_METHOD_DESCRIPTOR)) {
28112813
/* Avoid temporary PyMethodObject */
2812-
*self_or_null = self;
2814+
return 1;
28132815
}
2814-
else {
2815-
descrgetfunc f = Py_TYPE(res)->tp_descr_get;
2816-
if (f != NULL) {
2817-
Py_SETREF(res, f(res, self, (PyObject *)(Py_TYPE(self))));
2816+
2817+
descrgetfunc f = Py_TYPE(method_o)->tp_descr_get;
2818+
if (f != NULL) {
2819+
PyObject *func = f(method_o, self, (PyObject *)(Py_TYPE(self)));
2820+
if (func == NULL) {
2821+
return -1;
28182822
}
2819-
*self_or_null = NULL;
2820-
Py_DECREF(self);
2823+
PyStackRef_CLEAR(method_and_self[0]); // clear method
2824+
method_and_self[0] = PyStackRef_FromPyObjectSteal(func);
28212825
}
2822-
return res;
2826+
PyStackRef_CLEAR(method_and_self[1]); // clear self
2827+
return 1;
28232828
}
28242829

28252830
static int

0 commit comments

Comments
 (0)