Skip to content

_Py_SetImmortal must be run on allocating thread (no-gil) #113956

Open
@mjp41

Description

@mjp41

Bug report

Bug description:

The code in _Py_SetImmortal must be run on the thread that originally allocated the object. If this is not the case, then a race between Py_INCREF can cause the _Py_SetImmortal to be ignored.

static inline void _Py_SetImmortal(PyObject *op)
{
if (op) {
#ifdef Py_GIL_DISABLED
op->ob_tid = _Py_UNOWNED_TID;
op->ob_ref_local = _Py_IMMORTAL_REFCNT_LOCAL;
op->ob_ref_shared = 0;
#else
op->ob_refcnt = _Py_IMMORTAL_REFCNT;
#endif
}
}

Example sequence:

  1. Allocating thread begins an _Py_Incref.
  2. Allocating thread detects it should perform inc ref on ob_ref_local, and calculates new value.
  3. Non-allocating thread runs all of _Py_SetImmortal.
  4. Allocating thread completes incref with assignment to ob_ref_local, and unsets the value from immortal.

In the nogil fork, there is some defensive code on other operations that need to be run on the allocating thread, e.g.

_PyObject_SetDeferredRefcount(PyObject *op)
{
    assert(_Py_ThreadLocal(op) && "non thread-safe");

https://github.com/colesbury/nogil-3.12/blob/cedde4f5ec3759ad723c89d44738776f362df564/Include/internal/pycore_object.h#L361C19-L361C19

I think something like this should occur inside _Py_SetImmortal.

It looks like _Py_SetImmortal is not exposed outside the runtime. However, PEP 683, says:

Then setting any object’s refcount to _Py_IMMORTAL_REFCNT makes it immortal.

This implies there is an intent to expose it, and with the changes to nogil. It seems that this should be exposed as an explicit operation, rather than by setting the ref count directly.

CC @ericsnowcurrently @colesbury

P.S. I am happy to PR any required changes.

CPython versions tested on:

CPython main branch

Operating systems tested on:

No response

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions