Skip to content

Commit 8636bcc

Browse files
Add PyUnstable_SetImmortal() (#164)
Co-authored-by: Victor Stinner <vstinner@python.org>
1 parent 11cb80f commit 8636bcc

File tree

4 files changed

+76
-0
lines changed

4 files changed

+76
-0
lines changed

docs/api.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@ Python 3.15
7070

7171
On PyPy, always returns ``-1``.
7272

73+
.. c:function:: int PyUnstable_SetImmortal(PyObject *op)
74+
75+
See `PyUnstable_SetImmortal() documentation <https://docs.python.org/dev/c-api/object.html#c.PyUnstable_SetImmortal>`__.
76+
77+
Availability: Python 3.13 and newer, not available on PyPy.
7378

7479
Python 3.14
7580
-----------

docs/changelog.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
Changelog
22
=========
33

4+
* 2026-02-12: Add functions:
5+
6+
* ``PyUnstable_SetImmortal()``
7+
48
* 2025-10-14: Add functions:
59

610
* ``PyTuple_FromArray()``

pythoncapi_compat.h

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2659,6 +2659,37 @@ PyUnstable_Unicode_GET_CACHED_HASH(PyObject *op)
26592659
}
26602660
#endif
26612661

2662+
#if 0x030D0000 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x030F00A7 && !defined(PYPY_VERSION)
2663+
// Immortal objects were implemented in Python 3.12, however there is no easy API
2664+
// to make objects immortal until 3.14 which has _Py_SetImmortal(). Since
2665+
// immortal objects are primarily needed for free-threading, this API is implemented
2666+
// for 3.14 using _Py_SetImmortal() and uses private macros on 3.13.
2667+
static inline int
2668+
PyUnstable_SetImmortal(PyObject *op)
2669+
{
2670+
assert(op != NULL);
2671+
if (!PyUnstable_Object_IsUniquelyReferenced(op) || PyUnicode_Check(op)) {
2672+
return 0;
2673+
}
2674+
#if 0x030E0000 <= PY_VERSION_HEX
2675+
PyAPI_FUNC(void) _Py_SetImmortal(PyObject *op);
2676+
_Py_SetImmortal(op);
2677+
#else
2678+
// Python 3.13 doesn't export _Py_SetImmortal() function
2679+
if (PyObject_GC_IsTracked(op)) {
2680+
PyObject_GC_UnTrack(op);
2681+
}
2682+
#ifdef Py_GIL_DISABLED
2683+
op->ob_tid = _Py_UNOWNED_TID;
2684+
op->ob_ref_local = _Py_IMMORTAL_REFCNT_LOCAL;
2685+
op->ob_ref_shared = 0;
2686+
#else
2687+
op->ob_refcnt = _Py_IMMORTAL_REFCNT;
2688+
#endif
2689+
#endif
2690+
return 1;
2691+
}
2692+
#endif
26622693

26632694
#ifdef __cplusplus
26642695
}

tests/test_pythoncapi_compat_cext.c

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2489,6 +2489,39 @@ test_try_incref(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
24892489
Py_RETURN_NONE;
24902490
}
24912491

2492+
#if 0x030D0000 <= PY_VERSION_HEX && !defined(PYPY_VERSION)
2493+
static PyObject *
2494+
test_set_immortal(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
2495+
{
2496+
PyObject object;
2497+
memset(&object, 0, sizeof(PyObject));
2498+
#ifdef Py_GIL_DISABLED
2499+
object.ob_tid = _Py_ThreadId();
2500+
object.ob_gc_bits = 0;
2501+
object.ob_ref_local = 1;
2502+
object.ob_ref_shared = 0;
2503+
#else
2504+
object.ob_refcnt = 1;
2505+
#endif
2506+
object.ob_type = &PyBaseObject_Type;
2507+
2508+
int rc = PyUnstable_SetImmortal(&object);
2509+
assert(rc == 1);
2510+
Py_DECREF(&object); // should not dealloc
2511+
2512+
// Check already immortal object
2513+
rc = PyUnstable_SetImmortal(&object);
2514+
assert(rc == 0);
2515+
2516+
// Check unicode objects
2517+
PyObject *unicode = PyUnicode_FromString("test");
2518+
rc = PyUnstable_SetImmortal(unicode);
2519+
assert(rc == 0);
2520+
Py_DECREF(unicode);
2521+
Py_RETURN_NONE;
2522+
}
2523+
#endif
2524+
24922525

24932526
static struct PyMethodDef methods[] = {
24942527
{"test_object", test_object, METH_NOARGS, _Py_NULL},
@@ -2546,6 +2579,9 @@ static struct PyMethodDef methods[] = {
25462579
{"test_byteswriter", test_byteswriter, METH_NOARGS, _Py_NULL},
25472580
{"test_tuple", test_tuple, METH_NOARGS, _Py_NULL},
25482581
{"test_try_incref", test_try_incref, METH_NOARGS, _Py_NULL},
2582+
#if 0x030D0000 <= PY_VERSION_HEX && !defined(PYPY_VERSION)
2583+
{"test_set_immortal", test_set_immortal, METH_NOARGS, _Py_NULL},
2584+
#endif
25492585
{_Py_NULL, _Py_NULL, 0, _Py_NULL}
25502586
};
25512587

0 commit comments

Comments
 (0)