-
-
Notifications
You must be signed in to change notification settings - Fork 34.7k
gh-150865: Allow immortalizing objects as long as thread owns it #150956
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -812,17 +812,21 @@ Object Protocol | |
|
|
||
| .. c:function:: int PyUnstable_SetImmortal(PyObject *op) | ||
|
|
||
| Marks the object *op* :term:`immortal`. The argument should be uniquely referenced by | ||
| the calling thread. This is intended to be used for reducing reference counting contention | ||
| in the :term:`free-threaded build` for objects which are shared across threads. | ||
| Marks the object *op* :term:`immortal`. To successfully immortalize an object on a | ||
| :term:`free-threaded build` it must have been created by the calling thread. | ||
| Unicode objects are not immortalized and :c:func:`PyUnicode_InternInPlace` should | ||
| be used instead. | ||
| This function is intended to be used for reducing reference counting contention | ||
| in free-threaded builds for objects which are shared across threads. | ||
|
Comment on lines
+819
to
+820
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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:
|
||
|
|
||
| This is a one-way process: objects can only be made immortal; they cannot be | ||
| made mortal once again. Immortal objects do not participate in reference counting | ||
| and will never be garbage collected. If the object is GC-tracked, it is untracked. | ||
|
|
||
| This function is intended to be used soon after *op* is created, by the code that | ||
| creates it, such as in the object's :c:member:`~PyTypeObject.tp_new` slot. | ||
| Returns 1 if the object was made immortal and returns 0 if it was not. | ||
| Returns 1 if the object was made immortal or is already immortal and returns 0 | ||
| if it was not. | ||
| This function cannot fail. | ||
|
|
||
| .. versionadded:: 3.15 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's expose 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) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -212,10 +212,10 @@ test_py_set_immortal(PyObject *self, PyObject *unused) | |
| #ifdef Py_GIL_DISABLED | ||
| object.ob_tid = _Py_ThreadId(); | ||
| object.ob_gc_bits = 0; | ||
| object.ob_ref_local = 1; | ||
| object.ob_ref_shared = 0; | ||
| object.ob_ref_local = 3; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? |
||
| object.ob_ref_shared = 128; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is the shared refcount being changed? |
||
| #else | ||
| object.ob_refcnt = 1; | ||
| object.ob_refcnt = 3; | ||
| #endif | ||
| object.ob_type = &PyBaseObject_Type; | ||
|
|
||
|
|
@@ -227,8 +227,18 @@ test_py_set_immortal(PyObject *self, PyObject *unused) | |
| assert(PyUnstable_IsImmortal(&object)); | ||
|
|
||
| // Check already immortal object | ||
| rc = PyUnstable_SetImmortal(&object); | ||
| assert(rc == 1); | ||
|
|
||
| // If not owned by the current thread cannot immortalize | ||
| #if Py_GIL_DISABLED | ||
| object.ob_tid = _Py_ThreadId() + 1; | ||
| object.ob_ref_local = 1; // reset refcount | ||
|
|
||
| rc = PyUnstable_SetImmortal(&object); | ||
| assert(rc == 0); | ||
| assert(!PyUnstable_IsImmortal(&object)); | ||
| #endif | ||
|
|
||
| // Check unicode objects | ||
| PyObject *unicode = PyUnicode_FromString("test"); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2885,7 +2885,17 @@ int | |
| PyUnstable_SetImmortal(PyObject *op) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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:
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
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
| { | ||
| assert(op != NULL); | ||
| if (!_PyObject_IsUniquelyReferenced(op) || PyUnicode_Check(op)) { | ||
| if (_Py_IsImmortal(op)) { | ||
| // If the object is immortal, be idempotent. | ||
| return 1; | ||
| } | ||
| #if Py_GIL_DISABLED | ||
| // Only the owning thread can immortalize the object safely | ||
| if (!_Py_IsOwnedByCurrentThread(op)) { | ||
|
Comment on lines
+2893
to
+2894
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't we need to also check that |
||
| return 0; | ||
| } | ||
| #endif | ||
| if (PyUnicode_Check(op)) { | ||
| return 0; | ||
| } | ||
| _Py_SetImmortal(op); | ||
|
|
||
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
.. warningnote.)