GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — gh-150865: Allow immortalizing objects as long as thread owns it by seberg · Pull Request #150956 · python/cpython

Conversation

@seberg

@seberg seberg commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

As discussed in gh-150865, it seems unnecessary to enforce a unique reference. Rather, an object can be immortalized (even accidentally by overflowing the local reference count) unless it is owned by the current thread.

Allowing immortalizing more generally simplifies immortalization of objects with complex ceration (in the example, new types may be created with multiple references from the start).

This may mean that the ref_shared reference count can be nonzero after immortalization, but that should be safe (as it also happens for accidental immortalization).

I made the function idempotent (even for unicode on this one) just for the sake of it.

Closes gh-150865

(Not sure this needs a blurp, I guess I can add myself to the existing one, but I don't mind either way.)


(once merged, I guess this should also be ported to pythoncapi-compat)

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@seberg
pythongh-150865: Allow immortalizing objects as long as thread owns it
4486572
As discussed in pythongh-150865, it seems unnecessary to enforce a
unique reference.  Rather, an object can be immortalized (even
accidentally by overflowing the local reference count) unless it is
owned by the current thread.

Allowing immortalizing more generally simplifies immortalization of
objects with complex ceration (in the example, new types may be created
with multiple references from the start).

This may mean that the `ref_shared` reference count can be nonzero
after immortalization, but that should be safe (as it also happens
for accidental immortalization).

I made the function idempotent (even for unicode on this one) just for
the sake of it.
@seberg
seberg requested a review from ZeroIntensity as a code owner June 5, 2026 11:56
@bedevere-app bedevere-app Bot added the awaiting review label Jun 5, 2026
@bedevere-app bedevere-app Bot mentioned this pull request Jun 5, 2026
Relax PyUnstable_SetImmortal to objects allocated in that thread? #150865
Open
@read-the-docs-community

read-the-docs-community Bot commented Jun 5, 2026

Copy link
Copy Markdown

Documentation build overview

📚 cpython-previews | 🛠️ Build #33006772 | 📁 Comparing 4486572 against main (2f064fb)

  🔍 Preview build  

1 file changed
± c-api/object.html
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

ZeroIntensity
ZeroIntensity reviewed Jun 5, 2026
View reviewed changes

@ZeroIntensity ZeroIntensity left a comment

Copy link
Copy Markdown
Member

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.

Thanks for the PR. I'm going to temporarily apply DO-NOT-MERGE while I dig through my old implementation and figure out the contract more concretely.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

seberg reacted with thumbs up emoji
All reactions
  • 👍 1 reaction
Comment thread Doc/c-api/object.rst
Comment on lines +819 to +820
This function is intended to be used for reducing reference counting contention
in free-threaded builds for objects which are shared across threads.

@ZeroIntensity ZeroIntensity Jun 5, 2026

Copy link
Copy Markdown
Member

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 should note that it's intended for highly-specific cases where either:

  1. The object is not tracked by the GC (so DRC doesn't work).
  2. The contention occurs in native code calling Py_INCREF, where DRC does not apply.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Doc/c-api/object.rst
Comment on lines +817 to +818
Unicode objects are not immortalized and :c:func:`PyUnicode_InternInPlace` should
be used instead.

@ZeroIntensity ZeroIntensity Jun 5, 2026

Copy link
Copy Markdown
Member

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 should document that some objects may not support immortalization in the future and that some objects will break when immortalized. (The second part should be in a .. warning note.)

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Modules/_testcapi/object.c
object.ob_gc_bits = 0;
object.ob_ref_local = 1;
object.ob_ref_shared = 0;
object.ob_ref_local = 3;

@ZeroIntensity ZeroIntensity Jun 5, 2026

Copy link
Copy Markdown
Member

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 kind of arbitrary, can you add a comment explaining why 3 is used?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Modules/_testcapi/object.c
object.ob_ref_local = 1;
object.ob_ref_shared = 0;
object.ob_ref_local = 3;
object.ob_ref_shared = 128;

@ZeroIntensity ZeroIntensity Jun 5, 2026

Copy link
Copy Markdown
Member

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 is the shared refcount being changed?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Objects/object.c
Comment on lines +2893 to +2894
// Only the owning thread can immortalize the object safely
if (!_Py_IsOwnedByCurrentThread(op)) {

@ZeroIntensity ZeroIntensity Jun 5, 2026

Copy link
Copy Markdown
Member

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.

Don't we need to also check that ob_ref_shared == 0?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Modules/_testcapi/object.c

@ZeroIntensity ZeroIntensity Jun 5, 2026

Copy link
Copy Markdown
Member

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.

Let's expose PyUnstable_SetImmortal as a helper and add tests using some more exotic objects. I'm still not very keen on the idea that "owned by current thread" implies "safe to immortalize".

Here's an outline:

static PyObject *
pyobject_set_immortal(PyObject *self, PyObject *op)
{
    return PyLong_FromLong(PyUnstable_SetImmortal(op));
}
import _testcapi

class TestWhatever(TestCase):
    def test_set_immortal(self):
        for obj, expected_result in [(module_object, 1), (shared_module_object, 0), ...]:
            self.assertEqual(_testcapi.pyobject_enable_deferred_refcount(module), expected_result)

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@ZeroIntensity ZeroIntensity added the DO-NOT-MERGE label Jun 5, 2026
ZeroIntensity
ZeroIntensity requested changes Jun 5, 2026
View reviewed changes
Comment thread Objects/object.c
@@ -2885,7 +2885,17 @@ int
PyUnstable_SetImmortal(PyObject *op)

@ZeroIntensity ZeroIntensity Jun 5, 2026

Copy link
Copy Markdown
Member

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.

Ok, looking at my version, we need to do a few other things here:

  1. The interpreter stack might have some live references to the object, and it currently thinks that the object is mortal. We need to undo that (see this function).
  2. I think we need to disable deferred reference counting on the object in case there are some queued references that will break later. I achieved this by exposing this function from gc_free_threading.c and calling _PyObject_MergePerThreadRefcounts.
  3. It's probably a good idea to lower the reftotal so -Xshowrefcount doesn't break. (See this loop.)
  4. If you want to support calling PyUnstable_SetImmortal on objects that have a non-zero ob_ref_shared as well, then you also need to make a stop-the-world pause (_PyEval_StopTheWorld/_PyEval_StartTheWorld) to ensure that the object is in a consistent state when immortalizing it.

This also furthers my suspicion that accidental immortalization does not work correctly.

Note that my implementation was also designed to eventually deallocate the immortal object, so there are some things in my PyUnstable_Immortalize that we don't need to do in PyUnstable_SetImmortal (namely the _Py_immortal stuff), but the above list makes sense to me even if the object isn't ever deallocated.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@seberg seberg Jun 5, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor Author

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.

  • 4: Well, I'll assume stop the world likely not worth the effort. And I expect without that, there could be arbitrary increfs/decrefs in-flight on ob_ref_shared.
    So, unless we assume it's OK if (maybe except the flags space, I'll guess that only gets mutated from the owning thread/during stop-the-world) ob_ref_shared must be considered undefined for immortal objects. Otherwise, I expect it is best to just close this PR.

  • 3: Not sure I follow why it matters that immortalizing leaves the total refcount untouched. Immortalizing seems like "arbitrary code" and arbitrary code may modify it?

  • 1-2: This is maybe just more complicated then I think, I can't follow that deeply unfortunately. Part of me keeps wondering if there are two competing concepts of "immortal" here, but I just don't know (the best I can do is find this comment in PyStackRef_CheckValid right now:)

    /* Can be immortal if object was made immortal after reference came into existence */
    assert(!_Py_IsStaticImmortal(obj));
    

    (and it isn't even clear to me that the current check would be enough to protect if that wasn't.)

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@ZeroIntensity ZeroIntensity Jun 5, 2026

Copy link
Copy Markdown
Member

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.

Yeah, I'm not sure what the difference between a "static immortal" and "immortal" is.

For 3, it would just be to improve debugging. Lowering the global refcount would make it so -Xshowrefcount shows 0 leaked references at the end.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@bedevere-app bedevere-app Bot added awaiting changes and removed awaiting review labels Jun 5, 2026
@bedevere-app

bedevere-app Bot commented Jun 5, 2026

Copy link
Copy Markdown

A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated.

Once you have made the requested changes, please leave a comment on this pull request containing the phrase I have made the requested changes; please review again. I will then notify any core developers who have left a review that you're ready for them to take another look at this pull request.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown

This PR is stale because it has been open for 90 days with no activity.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@github-actions github-actions Bot added the stale Stale PR or inactive for long period of time. label Sep 5, 2026
@eendebakpt eendebakpt mentioned this pull request Sep 6, 2026
ENH: Immortalize builtin constants on free-threaded build numpy/numpy#32513
Merged
@seberg

seberg commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

I guess I'll close this if @ZeroIntensity is picking it up. (I never became sure what the, to me it seems this should be pretty close with some things, like the debug refcount, being at least OK to be off when there are multiple threads accessing the object at the time of immortalization.)

Not like the changes here are tricky, this was more of a nudging request :).

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@seberg seberg closed this Sep 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@ZeroIntensity ZeroIntensity ZeroIntensity requested changes

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

awaiting changes DO-NOT-MERGE stale Stale PR or inactive for long period of time.

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Relax PyUnstable_SetImmortal to objects allocated in that thread?

Uh oh!

There was an error while loading. Please reload this page.

2 participants

@seberg @ZeroIntensity
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.