-
-
Notifications
You must be signed in to change notification settings - Fork 31.9k
"Immortal" objects aren't immortal and that breaks things. #125174
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
Comments
There is a fairly simple thing we can do to make immortal refcounts more robust: Start them in the middle of the region of immortality. We want a fast check for immortality, so we use a sign check:
|
It might we worth correcting the refcount of immortal objects occasionally. This could be done during GC. When an immortal object is encountered, rewrite its refcount to the starting value if necessary. |
That's essentially what PEP-683 originally specified. |
Can we add a couple of tests for stable ABI decrefs and increfs that change the refcount of an immortal object by a few million to check that everything still works OK. |
I agree with pretty much everything here. |
There's also the question of immortal heap objects, AKA "interpreter-immortal". They must not outlive the allocator under which they were created. Each isolated interpreter has its own allocator, so immortalized heap objects there must not outlive the interpreter. We ran into some recent crashes related to this. Currently the only immortal heap objects I know about are the interned strings (incidentally, the origin of the crashes). To deal with that, we've switched legacy interpreters to share the main interpreter's interned strings dict. For isolated interpreters we still need to fix this, probably by returning a new immortal object created using the main interpreter (thus bound to the global runtime's lifetime). One of the following must be done to make interpreter-immortal (heap) objects generally safe:
Alternately, we could require that all immortal heap objects be runtime-global, thus disallowing interpreter-bound immortality. This would either mean we have all interpreters share a single global allocator, or we have a separate global allocator just for immortal heap objects, which would require a separate API. FWIW, I like the idea of heap object allocation and immortalization being strongly coupled. Being able to mark a heap object as immortal after-the-fact is prone to problems. Regardless, we need to be very careful that immortal objects only be fully immutable, for the sake of cross-interpreter safety, among other reasons. I suppose mutable immortal objects would be fine for internal global (cross-interpreter) cases for objects that are strictly not exposed to users, where we can use locks for safety. Otherwise, any mutable immortal object would have to be bound the originating interpreter's lifetime, and we're back to interpreter-immortal. For now we should just avoid mutable immortal objects. |
And it should probably also be strongly coupled with deduplication (interning) -- like with strings, you don't want to immortalize a tuple if another equal tuple is already immortal. |
What is the question, exactly?
Why? It seems a perfectly reasonable thing to do. In fact, it is one of the motivations for PEP 683.
OOI, why aren't they safe now? Wasn't that part of PEP 683?
Yes. This is the obviously correct, and probably only correct answer.
This sounds complicated and error prone
This also sounds complicated and expensive to track the references. |
* Set a bit in the unused part of the refcount on 64 bit machines and the free-threaded build. * Use the top of the refcount range on 32 bit machines
* Set a bit in the unused part of the refcount on 64 bit machines and the free-threaded build. * Use the top of the refcount range on 32 bit machines
Fix compiler warning
With the margin of error of about 1 billion, things are about as robust as we can make them for now. Immortal objects aren't going to be truly immortal until all C extensions have been recompiled against a recent version of the headers. But there is nothing we can do about that except wait. |
I'm reopening this issue because of immortality through overflow. There exists an object, The problem is the lack of "stickiness" of immortality in this scenario. The fix would be to make the threshold of immortality higher for increfs than the decrefs. @ericsnowcurrently Note that this is only an issue for 64 bit builds. It is impossible for N above to be large enough to cause a problem on 32 bit builds. * One approach might to use use 0 to represent immortality for increfs and > INT_MAX for decrefs (with 32 bit unsigned refcounts). |
How practically possible is this, though? That's ~17GB of pointers to reference this one object, which of course is possible, but my Linux box still throws an OOM if I request to allocate that much memory in a single block. |
Its' not very likely at all, but it is possible. The test in #131184 takes ~20GB, but it will crash the interpreter. |
This reverts commit 3a91ee9.
* Use a higher threshold for immortality for INCREF than DECREF
…ython#131230) Revert "pythonGH-125174: Make immortality "sticky" (pythonGH-131184)" This reverts commit 3a91ee9.
* Use a higher threshold for immortality for INCREF than DECREF
While it is still theoretically possible to crash the interpreter, it is now practically impossible to so accidentally. FTR, here's how it's done:
|
…ython#131230) Revert "pythonGH-125174: Make immortality "sticky" (pythonGH-131184)" This reverts commit 3a91ee9.
* Use a higher threshold for immortality for INCREF than DECREF
Bug report
Bug description:
Immortal objects should live forever. By definition, immortality is a permanent property of an object; if it can loose immortality, then it wasn't immortal in the first place.
Immortality allows some useful optimizations and safety guarantees that can make CPython faster and more robust.
Which would be great, if we didn't play fast and loose with immortality.
For no good reason that I'm aware of there are two functions
_Py_ClearImmortal
and_Py_SetMortal
that make immortal objects mortal. This is nonsense. We must remove these functions.We have also added
_Py_IsImmortalLoose
because it is too easy for C-extensionsInstead of adding these workarounds, we need to fix this problem as well.
Let's fix immortality so that we can rely on it and take advantage of it.
CPython versions tested on:
3.12, 3.13, CPython main branch
Operating systems tested on:
Other
Linked PRs
UINT32_MAX
in header file #127863The text was updated successfully, but these errors were encountered: