Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 34k
gh-139540: Fix executor deallocation crash when JIT compilation fails#139952
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 our terms of service and privacy 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
ashm-dev commented Oct 11, 2025 • edited by bedevere-app bot
Loading Uh oh!
There was an error while loading. Please reload this page.
edited by bedevere-app bot
Uh oh!
There was an error while loading. Please reload this page.
picnixz commented Oct 11, 2025
Note that this wouldn't solve the cold executor issue. It would only prevent an assertion failure in case we branch here (but if we branch here, we're already in a very bad state) |
ashm-dev commented Oct 11, 2025
@picnixz Thanks for the context regarding the "cold executor issue". |
picnixz commented Oct 11, 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.
It's a larger and more complex issue. The fact that we have an immortal cold executor seems to make normal executors leak. I don't know if it's because we reset executors in exit sides to a cold executor that then makes that specific executor not garbage-collectable (this is concerning). Your PR would still be fine but I don't know if it won't be eventually rechanged because of the fix we need to do on the other side. |
Fidget-Spinner commented Oct 11, 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.
Sorry but I have the same PR up here https://github.com/python/cpython/pull/137016/files, since July of this year |
The executor object wasn't decremented if Tier 2 code returned with an exception set but not a fatal error. This change moves the Py_DECREF call inside the TIER1_TO_TIER2 macro. This ensures the executor's reference count is always decremented.
picnixz commented Oct 11, 2025
Considering this is a duplicate of gh-137016, I'm closing this PR. Sorry! |
picnixz commented Oct 11, 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.
For the other leaks, |
| if (cold==NULL){ | ||
| Py_FatalError("Cannot allocate core JIT code"); | ||
| } | ||
| _PyObject_GC_TRACK(cold); |
picnixzOct 11, 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.
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 is incorrect. The executor is immortal for now.
No this was correct, my bad. I didn't read the function correctly. The problem is that we're setting it immortal afterwards and I'm not sure it will be happy.
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.
Good point, thanks for the correction.
The intent was to track it for consistency immediately after allocation. My assumption is the GC correctly handles a tracked object that later becomes immortal.
Is that a safe assumption, or should we explicitly _PyObject_GC_UNTRACK it just before immortalization?
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.
That I don't know. The problem here is whether we want an immortal cold executor or not. That's why I wanted to wait for Mark's output here, and didn't write a PR already.
picnixz commented Oct 11, 2025
@Fidget-Spinner Do you mind incorporating some observations from this PR in yours? I don't know whether the change is correct here when changing |
ashm-dev commented Oct 11, 2025
Hi @Fidget-Spinner , Thanks for pointing that out and linking your PR. I've taken a close look at both. While our changes overlap in However, this PR primarily addresses a critical reference counting leak that was detectable with ASan. The root cause was that the My PR also includes the same I believe this PR provides a more complete solution for the stability issues in this code path. |
ashm-dev commented Oct 11, 2025
These changes are correct and necessary. The intended lifecycle is:
The other |
picnixz commented Oct 11, 2025
Does it fix the leak as in the issue? that is, does it fix the refcounting? I can't check this now, but please check whether the reproducer (without ASAN) is fixed when you run |
| #defineTIER1_TO_TIER2(EXECUTOR) \ | ||
| #defineTIER1_TO_TIER2(EXECUTOR) \ | ||
| do{\ | ||
| OPT_STAT_INC(traces_executed); \ |
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.
Why was this changed? please revert.
| /* Tier-switching macros. */ | ||
| #defineTIER1_TO_TIER2(EXECUTOR) \ | ||
| #defineTIER1_TO_TIER2(EXECUTOR) \ |
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.
Why was this changed? please revert.
| tstate->current_executor = (PyObject *)executor; | ||
| _PyFrame_SetStackPointer(frame, stack_pointer); | ||
| stack_pointer = _PyFrame_GetStackPointer(frame); |
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 seems to be redundant.
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.
We are setting the stack pointer and requerying it while doing nothing inbetween.
| next_instr = _Py_jit_entry((EXECUTOR), frame, stack_pointer, tstate); \ | ||
| frame = tstate->current_frame; \ | ||
| stack_pointer = _PyFrame_GetStackPointer(frame); \ | ||
| Py_DECREF(EXECUTOR); \ |
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.
Why should we decref it here?
| assert(tstate->current_executor==NULL); | ||
| assert(executor!=tstate->interp->cold_executor); | ||
| tstate->jit_exit=NULL; | ||
| tstate->current_executor= (PyObject*)executor; |
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.
Is it just to hold a reference to the executor?
There is a memory leak in make_executor_from_uops (Python/optimizer.c) when JIT compilation fails. The executor object is created via _PyObject_GC_NewVar but _PyObject_GC_TRACK is called after the JIT compilation attempt. If _PyJIT_Compile fails and returns an error, the code calls Py_DECREF(executor) before the object has been tracked by the garbage collector.