Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork32k
gh-131586: Avoid refcount contention in context managers#131851
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Conversation
This avoid reference count contention in the free threading buildwhen calling special methods like `__enter__` and `__exit__`.
Python/bytecodes.c Outdated
inst(LOAD_SPECIAL, (owner -- attr, self_or_null)) { | ||
assert(oparg <= SPECIAL_MAX); | ||
PyObject *owner_o = PyStackRef_AsPyObjectSteal(owner); | ||
op(_INSERT_NULL, (arg -- args[2])) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
@markshannon - this is what I was referring to in our last meeting. I had to split upLOAD_SPECIAL
into two ops to ensure that thatframe->stackpointer
includes both operands when it's spilled before the_PyObject_LookupSpecialMethod
call.
I'm not sure if this is a better way to do this.
Tools/cases_generator/stack.py Outdated
@@ -259,7 +259,7 @@ def pop(self, var: StackItem) -> tuple[str, Local]: | |||
else: | |||
rename = "" | |||
if not popped.in_local: | |||
assert popped.memory_offset is not None | |||
#assert popped.memory_offset is not None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
@markshannon - I'm not sure what to do about this.
Oh woops I thought I was looking at a different pr! |
The simpler formulation inst(LOAD_SPECIAL, (self--method_and_self[2])) {method_and_self[1]=self;method_and_self[0]=NULL;DEAD(self);PyObject*name=_Py_SpecialMethods[oparg].name;interr=_PyObject_LookupSpecialMethod(name,method_and_self);if (err<0) {if (!_PyErr_Occurred(tstate)) {_PyErr_Format(tstate,PyExc_TypeError,_Py_SpecialMethods[oparg].error,PyStackRef_TYPE(method_and_self[1])->tp_name);ERROR_NO_POP(); }ERROR_NO_POP(); } } should work now, with no changes to the cases generator needed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Negative return values for errors should be only for errors
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
Python/generated_cases.c.h Outdated
_PyStackRefself; | ||
_PyStackRef*method_and_self; | ||
self = stack_pointer[-1]; | ||
method_and_self =&stack_pointer[-1]; | ||
method_and_self[1] = self; | ||
method_and_self[0] = PyStackRef_NULL; | ||
PyObject *name = _Py_SpecialMethods[oparg].name; | ||
PyObject *self_or_null_o; | ||
stack_pointer += -1; | ||
assert(WITHIN_STACK_BOUNDS()); | ||
_PyFrame_SetStackPointer(frame, stack_pointer); | ||
PyObject *attr_o = _PyObject_LookupSpecialMethod(owner_o,name,&self_or_null_o); | ||
int err = _PyObject_LookupSpecialMethod(name,method_and_self); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
@markshannon - I switched to the simpler formulation, but the stackpointer adjustments aren't safe here for the free threaded build.
Thestack_pointer += -1;
means thatself
is not visible on the stack anymore when_PyObject_LookupSpecialMethod
is called. In other words the code is generated as if the inputs are dead (okay), but the outputs are not yet alive (bad).
Make sure that `method_and_self` stays alive across the call to_PyObject_LookupSpecialMethod. When written as a single op, the input ismarked as dead, but the outputs aren't considered alive until the end ofthe op.
@markshannon@brandtbucher - the JIT build is failing on this PR. Would you please take a look? I think the generated |
Thanks for the ping, I’ll take a look at this tomorrow. |
brandtbucher commentedApr 15, 2025 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
Just from looking at the diff, Ithink the issue may be that we need a case in |
Thanks@brandtbucher! Would you please take a look at my changes tooptimizer_bytecodes.c to see if they make sense? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Yep, they look good.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
LGTM
Objects/typeobject.c Outdated
// Lookup the method name `attr` on `self`. On entry, `method_and_self[0]` | ||
// is null and `method_and_self[1]` is `self`. On exit, `method_and_self[0]` | ||
// is the method object and `method_and_self[1]` is `self` if the method is | ||
// not bound. | ||
// Return 0 on success, -1 on error or if the method is missing. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
This comment needs to be updated.
I don't see the need for arrays. Using arrays forces values into memory which prevents top of stack caching, and it makes the code harder to follow. If you give the Using scalars works, as the code generator can track what is live or not and make sure that everything live is visible to the GC. This does what you want, I think: inst(LOAD_SPECIAL, (owner--attr,self_or_null)) {PyObject*owner_o=PyStackRef_AsPyObjectBorrow(owner);PyObject*name=_Py_SpecialMethods[oparg].name;// Returns -1 for error, 0 for normal attr, 1 for method.interr=_PyObject_LookupSpecialMethod(owner_o,name,&attr);if (err>0) {self_or_null=owner;DEAD(owner); }elseif (err<0) {// The code generator assumes that _PyObject_LookupSpecialMethod// has defined `attr`, so overwrite it with `owner`.attr=owner;DEAD(owner);if (!_PyErr_Occurred(tstate)) {_PyErr_Format(tstate,PyExc_TypeError,_Py_SpecialMethods[oparg].error,Py_TYPE(owner_o)->tp_name); }ERROR_IF(true,error); }else {PyStackRef_CLOSE(owner);self_or_null=PyStackRef_NULL; } } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Try to avoid using arrays unless there is an actual array, like forBUILD_TUPLE
.
Also, if the code generator is not doing what you think it should, file a bug report.
Either we need to document it, or fix it.
When you're done making the requested changes, leave the comment: |
No, that doesn't work@markshannon. Both In your formulation, once |
colesbury commentedApr 16, 2025 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
Here is part of the generated code for your formulation. _PyStackRefowner;_PyStackRefattr;_PyStackRefself_or_null;owner=stack_pointer[-1];PyObject*owner_o=PyStackRef_AsPyObjectBorrow(owner);PyObject*name=_Py_SpecialMethods[oparg].name;_PyFrame_SetStackPointer(frame,stack_pointer);interr=_PyObject_LookupSpecialMethod(owner_o,name,&attr); |
|
That's why it's initialized to |
No difference in single-threaded performance (<0.02% default; <0.06% FT; clang-20 tail-call) |
bedevere-bot commentedApr 21, 2025
🤖 New build scheduled with the buildbot fleet by@colesbury for commit270b87b 🤖 Results will be shown at: https://buildbot.python.org/all/#/grid?branch=refs%2Fpull%2F131851%2Fmerge If you want to schedule another build, you need to add the🔨 test-with-refleak-buildbots label again. |
da53660
intopython:mainUh oh!
There was an error while loading.Please reload this page.
bedevere-bot commentedApr 21, 2025
|
Uh oh!
There was an error while loading.Please reload this page.
This avoid reference count contention in the free threading build when calling special methods like
__enter__
and__exit__
._PyType_LookupRef
and related functions #131586