.. versionadded:: 3.4
+.. function:: getunicodeinternedsize()
+
+ Return the number of unicode objects that have been interned.
+
+ .. versionadded:: 3.12
+
+
.. function:: getandroidapilevel()
Return the build time API version of Android as an integer.
to replace the legacy-api :c:func:`!PyErr_Display`. (Contributed by
Irit Katriel in :gh:`102755`).
+* :pep:`683`: Introduced Immortal Objects to Python which allows objects
+ to bypass reference counts and introduced changes to the C-API:
+
+ - ``_Py_IMMORTAL_REFCNT``: The reference count that defines an object
+ as immortal.
+ - ``_Py_IsImmortal`` Checks if an object has the immortal reference count.
+ - ``PyObject_HEAD_INIT`` This will now initialize reference count to
+ ``_Py_IMMORTAL_REFCNT`` when used with ``Py_BUILD_CORE``.
+ - ``SSTATE_INTERNED_IMMORTAL`` An identifier for interned unicode objects
+ that are immortal.
+ - ``SSTATE_INTERNED_IMMORTAL_STATIC`` An identifier for interned unicode
+ objects that are immortal and static
+ - ``sys.getunicodeinternedsize`` This returns the total number of unicode
+ objects that have been interned. This is now needed for refleak.py to
+ correctly track reference counts and allocated blocks
+
+ (Contributed by Eddie Elizondo in :gh:`84436`.)
+
Porting to Python 3.12
----------------------
* :c:func:`!PyUnicode_GetSize`
* :c:func:`!PyUnicode_GET_DATA_SIZE`
-* Remove the ``PyUnicode_InternImmortal()`` function and the
- ``SSTATE_INTERNED_IMMORTAL`` macro.
+* Remove the ``PyUnicode_InternImmortal()`` function macro.
(Contributed by Victor Stinner in :gh:`85858`.)
* Remove ``Jython`` compatibility hacks from several stdlib modules and tests.
#define PyBool_Check(x) Py_IS_TYPE((x), &PyBool_Type)
-/* Py_False and Py_True are the only two bools in existence.
-Don't forget to apply Py_INCREF() when returning either!!! */
+/* Py_False and Py_True are the only two bools in existence. */
/* Don't use these directly */
PyAPI_DATA(PyLongObject) _Py_FalseStruct;
#define Py_IsFalse(x) Py_Is((x), Py_False)
/* Macros for returning Py_True or Py_False, respectively */
-#define Py_RETURN_TRUE return Py_NewRef(Py_True)
-#define Py_RETURN_FALSE return Py_NewRef(Py_False)
+#define Py_RETURN_TRUE return Py_True
+#define Py_RETURN_FALSE return Py_False
/* Function to return a bool from a C long */
PyAPI_FUNC(PyObject *) PyBool_FromLong(long);
Py_ssize_t length; /* Number of code points in the string */
Py_hash_t hash; /* Hash value; -1 if not set */
struct {
- /* If interned is set, the two references from the
- dictionary to this object are *not* counted in ob_refcnt. */
- unsigned int interned:1;
+ /* If interned is non-zero, the two references from the
+ dictionary to this object are *not* counted in ob_refcnt.
+ The possible values here are:
+ 0: Not Interned
+ 1: Interned
+ 2: Interned and Immortal
+ 3: Interned, Immortal, and Static
+ This categorization allows the runtime to determine the right
+ cleanup mechanism at runtime shutdown. */
+ unsigned int interned:2;
/* Character size:
- PyUnicode_1BYTE_KIND (1):
unsigned int ascii:1;
/* Padding to ensure that PyUnicode_DATA() is always aligned to
4 bytes (see issue #19537 on m68k). */
- unsigned int :26;
+ unsigned int :25;
} state;
} PyASCIIObject;
/* Interning state. */
#define SSTATE_NOT_INTERNED 0
#define SSTATE_INTERNED_MORTAL 1
+#define SSTATE_INTERNED_IMMORTAL 2
+#define SSTATE_INTERNED_IMMORTAL_STATIC 3
/* Use only if you know it's a string */
static inline unsigned int PyUnicode_CHECK_INTERNED(PyObject *op) {
# error "this header requires Py_BUILD_CORE define"
#endif
-#include "pycore_object.h" // _PyObject_IMMORTAL_REFCNT
-
#ifdef Py_DEBUG
static inline void
_PyStaticObject_CheckRefcnt(PyObject *obj) {
- if (Py_REFCNT(obj) < _PyObject_IMMORTAL_REFCNT) {
+ if (Py_REFCNT(obj) < _Py_IMMORTAL_REFCNT) {
_PyObject_ASSERT_FAILED_MSG(obj,
"immortal object has less refcnt than expected "
- "_PyObject_IMMORTAL_REFCNT");
+ "_Py_IMMORTAL_REFCNT");
}
}
#endif
#define _PyLong_DIGIT_INIT(val) \
{ \
- .ob_base = _PyObject_IMMORTAL_INIT(&PyLong_Type), \
+ .ob_base = _PyObject_HEAD_INIT(&PyLong_Type) \
.long_value = { \
.lv_tag = TAG_FROM_SIGN_AND_SIZE( \
(val) == 0 ? 0 : ((val) < 0 ? -1 : 1), \
#include "pycore_pystate.h" // _PyInterpreterState_GET()
#include "pycore_runtime.h" // _PyRuntime
-/* This value provides *effective* immortality, meaning the object should never
- be deallocated (until runtime finalization). See PEP 683 for more details about
- immortality, as well as a proposed mechanism for proper immortality. */
-#define _PyObject_IMMORTAL_REFCNT 999999999
-
-#define _PyObject_IMMORTAL_INIT(type) \
- { \
- .ob_refcnt = _PyObject_IMMORTAL_REFCNT, \
- .ob_type = (type), \
- }
-#define _PyVarObject_IMMORTAL_INIT(type, size) \
- { \
- .ob_base = _PyObject_IMMORTAL_INIT(type), \
- .ob_size = size, \
- }
+/* We need to maintain an internal copy of Py{Var}Object_HEAD_INIT to avoid
+ designated initializer conflicts in C++20. If we use the deinition in
+ object.h, we will be mixing designated and non-designated initializers in
+ pycore objects which is forbiddent in C++20. However, if we then use
+ designated initializers in object.h then Extensions without designated break.
+ Furthermore, we can't use designated initializers in Extensions since these
+ are not supported pre-C++20. Thus, keeping an internal copy here is the most
+ backwards compatible solution */
+#define _PyObject_HEAD_INIT(type) \
+ { \
+ _PyObject_EXTRA_INIT \
+ .ob_refcnt = _Py_IMMORTAL_REFCNT, \
+ .ob_type = (type) \
+ },
+#define _PyVarObject_HEAD_INIT(type, size) \
+ { \
+ .ob_base = _PyObject_HEAD_INIT(type) \
+ .ob_size = size \
+ },
PyAPI_FUNC(void) _Py_NO_RETURN _Py_FatalRefcountErrorFunc(
const char *func,
}
#define _Py_RefcntAdd(op, n) _Py_RefcntAdd(_PyObject_CAST(op), n)
+static inline void _Py_SetImmortal(PyObject *op)
+{
+ if (op) {
+ op->ob_refcnt = _Py_IMMORTAL_REFCNT;
+ }
+}
+#define _Py_SetImmortal(op) _Py_SetImmortal(_PyObject_CAST(op))
+
static inline void
_Py_DECREF_SPECIALIZED(PyObject *op, const destructor destruct)
{
+ if (_Py_IsImmortal(op)) {
+ return;
+ }
_Py_DECREF_STAT_INC();
#ifdef Py_REF_DEBUG
_Py_DEC_REFTOTAL(_PyInterpreterState_GET());
static inline void
_Py_DECREF_NO_DEALLOC(PyObject *op)
{
+ if (_Py_IsImmortal(op)) {
+ return;
+ }
_Py_DECREF_STAT_INC();
#ifdef Py_REF_DEBUG
_Py_DEC_REFTOTAL(_PyInterpreterState_GET());
.latin1 = _Py_str_latin1_INIT, \
}, \
.tuple_empty = { \
- .ob_base = _PyVarObject_IMMORTAL_INIT(&PyTuple_Type, 0) \
+ .ob_base = _PyVarObject_HEAD_INIT(&PyTuple_Type, 0) \
}, \
.hamt_bitmap_node_empty = { \
- .ob_base = _PyVarObject_IMMORTAL_INIT(&_PyHamt_BitmapNode_Type, 0) \
+ .ob_base = _PyVarObject_HEAD_INIT(&_PyHamt_BitmapNode_Type, 0) \
}, \
.context_token_missing = { \
- .ob_base = _PyObject_IMMORTAL_INIT(&_PyContextTokenMissing_Type), \
+ .ob_base = _PyObject_HEAD_INIT(&_PyContextTokenMissing_Type) \
}, \
}, \
}, \
.singletons = { \
._not_used = 1, \
.hamt_empty = { \
- .ob_base = _PyObject_IMMORTAL_INIT(&_PyHamt_Type), \
+ .ob_base = _PyObject_HEAD_INIT(&_PyHamt_Type) \
.h_root = (PyHamtNode*)&_Py_SINGLETON(hamt_bitmap_node_empty), \
}, \
.last_resort_memory_error = { \
- _PyObject_IMMORTAL_INIT(&_PyExc_MemoryError), \
+ _PyObject_HEAD_INIT(&_PyExc_MemoryError) \
}, \
}, \
}, \
#define _PyBytes_SIMPLE_INIT(CH, LEN) \
{ \
- _PyVarObject_IMMORTAL_INIT(&PyBytes_Type, (LEN)), \
+ _PyVarObject_HEAD_INIT(&PyBytes_Type, (LEN)) \
.ob_shash = -1, \
.ob_sval = { (CH) }, \
}
#define _PyUnicode_ASCII_BASE_INIT(LITERAL, ASCII) \
{ \
- .ob_base = _PyObject_IMMORTAL_INIT(&PyUnicode_Type), \
+ .ob_base = _PyObject_HEAD_INIT(&PyUnicode_Type) \
.length = sizeof(LITERAL) - 1, \
.hash = -1, \
.state = { \
#include "pycore_ucnhash.h" // _PyUnicode_Name_CAPI
void _PyUnicode_ExactDealloc(PyObject *op);
+Py_ssize_t _PyUnicode_InternedSize(void);
/* runtime lifecycle */
/* PyObject_HEAD defines the initial segment of every PyObject. */
#define PyObject_HEAD PyObject ob_base;
-#define PyObject_HEAD_INIT(type) \
- { _PyObject_EXTRA_INIT \
- 1, (type) },
+/*
+Immortalization:
+
+The following indicates the immortalization strategy depending on the amount
+of available bits in the reference count field. All strategies are backwards
+compatible but the specific reference count value or immortalization check
+might change depending on the specializations for the underlying system.
+
+Proper deallocation of immortal instances requires distinguishing between
+statically allocated immortal instances vs those promoted by the runtime to be
+immortal. The latter should be the only instances that require
+cleanup during runtime finalization.
+*/
+
+#if SIZEOF_VOID_P > 4
+/*
+In 64+ bit systems, an object will be marked as immortal by setting all of the
+lower 32 bits of the reference count field, which is equal to: 0xFFFFFFFF
+
+Using the lower 32 bits makes the value backwards compatible by allowing
+C-Extensions without the updated checks in Py_INCREF and Py_DECREF to safely
+increase and decrease the objects reference count. The object would lose its
+immortality, but the execution would still be correct.
+
+Reference count increases will use saturated arithmetic, taking advantage of
+having all the lower 32 bits set, which will avoid the reference count to go
+beyond the refcount limit. Immortality checks for reference count decreases will
+be done by checking the bit sign flag in the lower 32 bits.
+*/
+#define _Py_IMMORTAL_REFCNT UINT_MAX
+
+#else
+/*
+In 32 bit systems, an object will be marked as immortal by setting all of the
+lower 30 bits of the reference count field, which is equal to: 0x3FFFFFFF
-#define PyVarObject_HEAD_INIT(type, size) \
- { PyObject_HEAD_INIT(type) (size) },
+Using the lower 30 bits makes the value backwards compatible by allowing
+C-Extensions without the updated checks in Py_INCREF and Py_DECREF to safely
+increase and decrease the objects reference count. The object would lose its
+immortality, but the execution would still be correct.
+
+Reference count increases and decreases will first go through an immortality
+check by comparing the reference count field to the immortality reference count.
+*/
+#define _Py_IMMORTAL_REFCNT (UINT_MAX >> 2)
+#endif
+
+// Make all internal uses of PyObject_HEAD_INIT immortal while preserving the
+// C-API expectation that the refcnt will be set to 1.
+#ifdef Py_BUILD_CORE
+#define PyObject_HEAD_INIT(type) \
+ { \
+ _PyObject_EXTRA_INIT \
+ { _Py_IMMORTAL_REFCNT }, \
+ (type) \
+ },
+#else
+#define PyObject_HEAD_INIT(type) \
+ { \
+ _PyObject_EXTRA_INIT \
+ { 1 }, \
+ (type) \
+ },
+#endif /* Py_BUILD_CORE */
+
+#define PyVarObject_HEAD_INIT(type, size) \
+ { \
+ PyObject_HEAD_INIT(type) \
+ (size) \
+ },
/* PyObject_VAR_HEAD defines the initial segment of all variable-size
* container objects. These end with a declaration of an array with 1
*/
struct _object {
_PyObject_HEAD_EXTRA
- Py_ssize_t ob_refcnt;
+ union {
+ Py_ssize_t ob_refcnt;
+#if SIZEOF_VOID_P > 4
+ PY_UINT32_T ob_refcnt_split[2];
+#endif
+ };
PyTypeObject *ob_type;
};
# define Py_SIZE(ob) Py_SIZE(_PyObject_CAST(ob))
#endif
+static inline Py_ALWAYS_INLINE int _Py_IsImmortal(PyObject *op)
+{
+#if SIZEOF_VOID_P > 4
+ return _Py_CAST(PY_INT32_T, op->ob_refcnt) < 0;
+#else
+ return op->ob_refcnt == _Py_IMMORTAL_REFCNT;
+#endif
+}
+#define _Py_IsImmortal(op) _Py_IsImmortal(_PyObject_CAST(op))
static inline int Py_IS_TYPE(PyObject *ob, PyTypeObject *type) {
return Py_TYPE(ob) == type;
static inline void Py_SET_REFCNT(PyObject *ob, Py_ssize_t refcnt) {
+ // This immortal check is for code that is unaware of immortal objects.
+ // The runtime tracks these objects and we should avoid as much
+ // as possible having extensions inadvertently change the refcnt
+ // of an immortalized object.
+ if (_Py_IsImmortal(ob)) {
+ return;
+ }
ob->ob_refcnt = refcnt;
}
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 < 0x030b0000
PyAPI_FUNC(void) _Py_IncRef(PyObject *);
PyAPI_FUNC(void) _Py_DecRef(PyObject *);
-static inline void Py_INCREF(PyObject *op)
+static inline Py_ALWAYS_INLINE void Py_INCREF(PyObject *op)
{
#if defined(Py_REF_DEBUG) && defined(Py_LIMITED_API) && Py_LIMITED_API+0 >= 0x030A0000
// Stable ABI for Python 3.10 built in debug mode.
_Py_IncRef(op);
#else
- _Py_INCREF_STAT_INC();
// Non-limited C API and limited C API for Python 3.9 and older access
// directly PyObject.ob_refcnt.
+#if SIZEOF_VOID_P > 4
+ // Portable saturated add, branching on the carry flag and set low bits
+ PY_UINT32_T cur_refcnt = op->ob_refcnt_split[PY_BIG_ENDIAN];
+ PY_UINT32_T new_refcnt = cur_refcnt + 1;
+ if (new_refcnt == 0) {
+ return;
+ }
+ op->ob_refcnt_split[PY_BIG_ENDIAN] = new_refcnt;
+#else
+ // Explicitly check immortality against the immortal value
+ if (_Py_IsImmortal(op)) {
+ return;
+ }
+ op->ob_refcnt++;
+#endif
+ _Py_INCREF_STAT_INC();
#ifdef Py_REF_DEBUG
_Py_INC_REFTOTAL();
-#endif // Py_REF_DEBUG
- op->ob_refcnt++;
+#endif
#endif
}
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 < 0x030b0000
#elif defined(Py_REF_DEBUG)
static inline void Py_DECREF(const char *filename, int lineno, PyObject *op)
{
+ if (_Py_IsImmortal(op)) {
+ return;
+ }
_Py_DECREF_STAT_INC();
_Py_DEC_REFTOTAL();
if (--op->ob_refcnt != 0) {
#define Py_DECREF(op) Py_DECREF(__FILE__, __LINE__, _PyObject_CAST(op))
#else
-static inline void Py_DECREF(PyObject *op)
+static inline Py_ALWAYS_INLINE void Py_DECREF(PyObject *op)
{
- _Py_DECREF_STAT_INC();
// Non-limited C API and limited C API for Python 3.9 and older access
// directly PyObject.ob_refcnt.
+ if (_Py_IsImmortal(op)) {
+ return;
+ }
+ _Py_DECREF_STAT_INC();
if (--op->ob_refcnt == 0) {
_Py_Dealloc(op);
}
#define Py_IsNone(x) Py_Is((x), Py_None)
/* Macro for returning Py_None from a function */
-#define Py_RETURN_NONE return Py_NewRef(Py_None)
+#define Py_RETURN_NONE return Py_None
/*
Py_NotImplemented is a singleton used to signal that an operation is
#define Py_NotImplemented (&_Py_NotImplementedStruct)
/* Macro for returning Py_NotImplemented from a function */
-#define Py_RETURN_NOTIMPLEMENTED return Py_NewRef(Py_NotImplemented)
+#define Py_RETURN_NOTIMPLEMENTED return Py_NotImplemented
/* Rich comparison opcodes */
#define Py_LT 0
# define Py_LOCAL_INLINE(type) static inline type
#endif
-// bpo-28126: Py_MEMCPY is kept for backwards compatibility,
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 < 0x030b0000
# define Py_MEMCPY memcpy
#endif
import sys
import types
-import unittest
+# Note: This test file can't import `unittest` since the runtime can't
+# currently guarantee that it will not leak memory. Doing so will mark
+# the test as passing but with reference leaks. This can safely import
+# the `unittest` library once there's a strict guarantee of no leaks
+# during runtime shutdown.
# bpo-46417: Test that structseq types used by the sys module are still
# valid when Py_Finalize()/Py_Initialize() are called multiple times.
-class TestStructSeq(unittest.TestCase):
+class TestStructSeq:
# test PyTypeObject members
- def check_structseq(self, obj_type):
+ def _check_structseq(self, obj_type):
# ob_refcnt
- self.assertGreaterEqual(sys.getrefcount(obj_type), 1)
+ assert sys.getrefcount(obj_type) > 1
# tp_base
- self.assertTrue(issubclass(obj_type, tuple))
+ assert issubclass(obj_type, tuple)
# tp_bases
- self.assertEqual(obj_type.__bases__, (tuple,))
+ assert obj_type.__bases__ == (tuple,)
# tp_dict
- self.assertIsInstance(obj_type.__dict__, types.MappingProxyType)
+ assert isinstance(obj_type.__dict__, types.MappingProxyType)
# tp_mro
- self.assertEqual(obj_type.__mro__, (obj_type, tuple, object))
+ assert obj_type.__mro__ == (obj_type, tuple, object)
# tp_name
- self.assertIsInstance(type.__name__, str)
+ assert isinstance(type.__name__, str)
# tp_subclasses
- self.assertEqual(obj_type.__subclasses__(), [])
+ assert obj_type.__subclasses__() == []
def test_sys_attrs(self):
for attr_name in (
'thread_info', # ThreadInfoType
'version_info', # VersionInfoType
):
- with self.subTest(attr=attr_name):
- attr = getattr(sys, attr_name)
- self.check_structseq(type(attr))
+ attr = getattr(sys, attr_name)
+ self._check_structseq(type(attr))
def test_sys_funcs(self):
func_names = ['get_asyncgen_hooks'] # AsyncGenHooksType
if hasattr(sys, 'getwindowsversion'):
func_names.append('getwindowsversion') # WindowsVersionType
for func_name in func_names:
- with self.subTest(func=func_name):
- func = getattr(sys, func_name)
- obj = func()
- self.check_structseq(type(obj))
+ func = getattr(sys, func_name)
+ obj = func()
+ self._check_structseq(type(obj))
try:
- unittest.main()
+ tests = TestStructSeq()
+ tests.test_sys_attrs()
+ tests.test_sys_funcs()
except SystemExit as exc:
if exc.args[0] != 0:
raise
fd_deltas = [0] * repcount
getallocatedblocks = sys.getallocatedblocks
gettotalrefcount = sys.gettotalrefcount
+ getunicodeinternedsize = sys.getunicodeinternedsize
fd_count = os_helper.fd_count
# initialize variables to make pyflakes quiet
- rc_before = alloc_before = fd_before = 0
+ rc_before = alloc_before = fd_before = interned_before = 0
if not ns.quiet:
print("beginning", repcount, "repetitions", file=sys.stderr)
dash_R_cleanup(fs, ps, pic, zdc, abcs)
support.gc_collect()
- # Read memory statistics immediately after the garbage collection
- alloc_after = getallocatedblocks()
- rc_after = gettotalrefcount()
+ # Read memory statistics immediately after the garbage collection.
+ # Also, readjust the reference counts and alloc blocks by ignoring
+ # any strings that might have been interned during test_func. These
+ # strings will be deallocated at runtime shutdown
+ interned_after = getunicodeinternedsize()
+ alloc_after = getallocatedblocks() - interned_after
+ rc_after = gettotalrefcount() - interned_after * 2
fd_after = fd_count()
if not ns.quiet:
alloc_before = alloc_after
rc_before = rc_after
fd_before = fd_after
+ interned_before = interned_after
if not ns.quiet:
print(file=sys.stderr)
from types import AsyncGeneratorType, FunctionType, CellType
from operator import neg
from test import support
-from test.support import (swap_attr, maybe_get_event_loop_policy)
+from test.support import (cpython_only, swap_attr, maybe_get_event_loop_policy)
from test.support.os_helper import (EnvironmentVarGuard, TESTFN, unlink)
from test.support.script_helper import assert_python_ok
from test.support.warnings_helper import check_warnings
self.assertEqual(["before", "after"], out.decode().splitlines())
+@cpython_only
+class ImmortalTests(unittest.TestCase):
+ def test_immortal(self):
+ none_refcount = sys.getrefcount(None)
+ true_refcount = sys.getrefcount(True)
+ false_refcount = sys.getrefcount(False)
+ smallint_refcount = sys.getrefcount(100)
+
+ # Assert that all of these immortal instances have large ref counts.
+ self.assertGreater(none_refcount, 2 ** 15)
+ self.assertGreater(true_refcount, 2 ** 15)
+ self.assertGreater(false_refcount, 2 ** 15)
+ self.assertGreater(smallint_refcount, 2 ** 15)
+
+ # Confirm that the refcount doesn't change even with a new ref to them.
+ l = [None, True, False, 100]
+ self.assertEqual(sys.getrefcount(None), none_refcount)
+ self.assertEqual(sys.getrefcount(True), true_refcount)
+ self.assertEqual(sys.getrefcount(False), false_refcount)
+ self.assertEqual(sys.getrefcount(100), smallint_refcount)
+
+
class TestType(unittest.TestCase):
def test_new_type(self):
A = type('A', (), {})
pythonapi.PyLong_AsLong.restype = c_long
res = pythonapi.PyLong_AsLong(42)
- self.assertEqual(grc(res), ref42 + 1)
+ # Small int refcnts don't change
+ self.assertEqual(grc(res), ref42)
del res
self.assertEqual(grc(42), ref42)
self.assertRaises(TypeError, sys.getrefcount)
c = sys.getrefcount(None)
n = None
- self.assertEqual(sys.getrefcount(None), c+1)
+ # Singleton refcnts don't change
+ self.assertEqual(sys.getrefcount(None), c)
del n
self.assertEqual(sys.getrefcount(None), c)
if hasattr(sys, "gettotalrefcount"):
ld_library_path_env = "DYLD_LIBRARY_PATH"
else:
ld_library_path_env = "LD_LIBRARY_PATH"
- subprocess.check_call(cmd,
- env={"PYTHONPATH": pythonpath,
- ld_library_path_env: ld_library_path})
+ # Note that in address sanitizer mode, the current runtime
+ # implementation leaks memory due to not being able to correctly
+ # clean all unicode objects during runtime shutdown. Therefore,
+ # this uses subprocess.run instead of subprocess.check_call to
+ # maintain the core of the test while not failing due to the refleaks.
+ # This should be able to use check_call once all refleaks are fixed.
+ subprocess.run(cmd,
+ env={"PYTHONPATH": pythonpath,
+ ld_library_path_env: ld_library_path})
envpy = os.path.join(self.env_dir, self.bindir, self.exe)
# Now check the venv created from the non-installed python has
# correct zip path in pythonpath.
--- /dev/null
+The implementation of PEP-683 which adds Immortal Objects by using a fixed
+reference count that skips reference counting to make objects truly
+immutable.
static void
update_refs(PyGC_Head *containers)
{
+ PyGC_Head *next;
PyGC_Head *gc = GC_NEXT(containers);
- for (; gc != containers; gc = GC_NEXT(gc)) {
+
+ while (gc != containers) {
+ next = GC_NEXT(gc);
+ /* Move any object that might have become immortal to the
+ * permanent generation as the reference count is not accurately
+ * reflecting the actual number of live references to this object
+ */
+ if (_Py_IsImmortal(FROM_GC(gc))) {
+ gc_list_move(gc, &get_gc_state()->permanent_generation.head);
+ gc = next;
+ continue;
+ }
gc_reset_refs(gc, Py_REFCNT(FROM_GC(gc)));
/* Python's cyclic gc should never see an incoming refcount
* of 0: if something decref'ed to 0, it should have been
* check instead of an assert?
*/
_PyObject_ASSERT(FROM_GC(gc), gc_get_refs(gc) != 0);
+ gc = next;
}
}
0, /* nb_index */
};
-static void _Py_NO_RETURN
-bool_dealloc(PyObject* Py_UNUSED(ignore))
+static void
+bool_dealloc(PyObject *boolean)
{
- _Py_FatalRefcountError("deallocating True or False");
+ /* This should never get called, but we also don't want to SEGV if
+ * we accidentally decref Booleans out of existence. Instead,
+ * since bools are immortal, re-set the reference count.
+ */
+ _Py_SetImmortal(boolean);
}
/* The type object for bool. Note that this cannot be subclassed! */
const unsigned char *e;
int cased, previous_is_cased;
- /* Shortcut for single character strings */
- if (len == 1)
- return PyBool_FromLong(Py_ISUPPER(*p));
+ if (len == 1) {
+ if (Py_ISUPPER(*p)) {
+ Py_RETURN_TRUE;
+ }
+ Py_RETURN_FALSE;
+ }
/* Special case for empty strings */
if (len == 0)
get_small_int(sdigit ival)
{
assert(IS_SMALL_INT(ival));
- PyObject *v = (PyObject *)&_PyLong_SMALL_INTS[_PY_NSMALLNEGINTS + ival];
- return Py_NewRef(v);
+ return (PyObject *)&_PyLong_SMALL_INTS[_PY_NSMALLNEGINTS + ival];
}
static PyLongObject *
Py_RETURN_RICHCOMPARE(result, 0, op);
}
+static void
+long_dealloc(PyObject *self)
+{
+ /* This should never get called, but we also don't want to SEGV if
+ * we accidentally decref small Ints out of existence. Instead,
+ * since small Ints are immortal, re-set the reference count.
+ */
+ PyLongObject *pylong = (PyLongObject*)self;
+ if (pylong && _PyLong_IsCompact(pylong)) {
+ stwodigits ival = medium_value(pylong);
+ if (IS_SMALL_INT(ival)) {
+ PyLongObject *small_pylong = (PyLongObject *)get_small_int((sdigit)ival);
+ if (pylong == small_pylong) {
+ _Py_SetImmortal(self);
+ return;
+ }
+ }
+ }
+ Py_TYPE(self)->tp_free(self);
+}
+
static Py_hash_t
long_hash(PyLongObject *v)
{
"int", /* tp_name */
offsetof(PyLongObject, long_value.ob_digit), /* tp_basicsize */
sizeof(digit), /* tp_itemsize */
- 0, /* tp_dealloc */
+ long_dealloc, /* tp_dealloc */
0, /* tp_vectorcall_offset */
0, /* tp_getattr */
0, /* tp_setattr */
return PyUnicode_FromString("None");
}
-static void _Py_NO_RETURN
-none_dealloc(PyObject* Py_UNUSED(ignore))
+static void
+none_dealloc(PyObject* none)
{
- _Py_FatalRefcountError("deallocating None");
+ /* This should never get called, but we also don't want to SEGV if
+ * we accidentally decref None out of existence. Instead,
+ * since None is an immortal object, re-set the reference count.
+ */
+ _Py_SetImmortal(none);
}
static PyObject *
"NoneType",
0,
0,
- none_dealloc, /*tp_dealloc*/ /*never called*/
+ none_dealloc, /*tp_dealloc*/
0, /*tp_vectorcall_offset*/
0, /*tp_getattr*/
0, /*tp_setattr*/
};
PyObject _Py_NoneStruct = {
- _PyObject_EXTRA_INIT
- 1, &_PyNone_Type
+ _PyObject_EXTRA_INIT
+ { _Py_IMMORTAL_REFCNT },
+ &_PyNone_Type
};
/* NotImplemented is an object that can be used to signal that an
Py_RETURN_NOTIMPLEMENTED;
}
-static void _Py_NO_RETURN
-notimplemented_dealloc(PyObject* ignore)
+static void
+notimplemented_dealloc(PyObject *notimplemented)
{
/* This should never get called, but we also don't want to SEGV if
- * we accidentally decref NotImplemented out of existence.
+ * we accidentally decref NotImplemented out of existence. Instead,
+ * since Notimplemented is an immortal object, re-set the reference count.
*/
- Py_FatalError("deallocating NotImplemented");
+ _Py_SetImmortal(notimplemented);
}
static int
PyObject _Py_NotImplementedStruct = {
_PyObject_EXTRA_INIT
- 1, &_PyNotImplemented_Type
+ { _Py_IMMORTAL_REFCNT },
+ &_PyNotImplemented_Type
};
extern PyTypeObject _Py_GenericAliasIterType;
if (_PyRuntime.tracemalloc.config.tracing) {
_PyTraceMalloc_NewReference(op);
}
- Py_SET_REFCNT(op, 1);
+ // Skip the immortal object check in Py_SET_REFCNT; always set refcnt to 1
+ op->ob_refcnt = 1;
#ifdef Py_TRACE_REFS
_Py_AddToAllObjects(op, 1);
#endif
};
static PyObject _dummy_struct = {
- _PyObject_EXTRA_INIT
- 2, &_PySetDummy_Type
+ _PyObject_EXTRA_INIT
+ { _Py_IMMORTAL_REFCNT },
+ &_PySetDummy_Type
};
return Py_NewRef(Py_Ellipsis);
}
+static void
+ellipsis_dealloc(PyObject *ellipsis)
+{
+ /* This should never get called, but we also don't want to SEGV if
+ * we accidentally decref Ellipsis out of existence. Instead,
+ * since Ellipsis is an immortal object, re-set the reference count.
+ */
+ _Py_SetImmortal(ellipsis);
+}
+
static PyObject *
ellipsis_repr(PyObject *op)
{
"ellipsis", /* tp_name */
0, /* tp_basicsize */
0, /* tp_itemsize */
- 0, /*never called*/ /* tp_dealloc */
+ ellipsis_dealloc, /* tp_dealloc */
0, /* tp_vectorcall_offset */
0, /* tp_getattr */
0, /* tp_setattr */
PyObject _Py_EllipsisObject = {
_PyObject_EXTRA_INIT
- 1, &PyEllipsis_Type
+ { _Py_IMMORTAL_REFCNT },
+ &PyEllipsis_Type
};
entry->version = 0;
// Set to None so _PyType_Lookup() can use Py_SETREF(),
// rather than using slower Py_XSETREF().
- // (See _PyType_FixCacheRefcounts() about the refcount.)
entry->name = Py_None;
entry->value = NULL;
}
}
-// This is the temporary fix used by pycore_create_interpreter(),
-// in pylifecycle.c. _PyType_InitCache() is called before the GIL
-// has been created (for the main interpreter) and without the
-// "current" thread state set. This causes crashes when the
-// reftotal is updated, so we don't modify the refcount in
-// _PyType_InitCache(), and instead do it later by calling
-// _PyType_FixCacheRefcounts().
-// XXX This workaround should be removed once we have immortal
-// objects (PEP 683).
-void
-_PyType_FixCacheRefcounts(void)
-{
- _Py_RefcntAdd(Py_None, (1 << MCACHE_SIZE_EXP));
-}
-
static unsigned int
_PyType_ClearCache(PyInterpreterState *interp)
to strings in this dictionary are *not* counted in the string's ob_refcnt.
When the interned string reaches a refcnt of 0 the string deallocation
function will delete the reference from this dictionary.
- Another way to look at this is that to say that the actual reference
- count of a string is: s->ob_refcnt + (s->state ? 2 : 0)
*/
static inline PyObject *get_interned_dict(PyInterpreterState *interp)
{
return _Py_INTERP_CACHED_OBJECT(interp, interned_strings);
}
+Py_ssize_t
+_PyUnicode_InternedSize()
+{
+ return PyObject_Length(get_interned_dict(_PyInterpreterState_GET()));
+}
+
static int
init_interned_dict(PyInterpreterState *interp)
{
static void
unicode_dealloc(PyObject *unicode)
{
- PyInterpreterState *interp = _PyInterpreterState_GET();
#ifdef Py_DEBUG
if (!unicode_is_finalizing() && unicode_is_singleton(unicode)) {
_Py_FatalRefcountError("deallocating an Unicode singleton");
}
#endif
+ /* This should never get called, but we also don't want to SEGV if
+ * we accidentally decref an immortal string out of existence. Since
+ * the string is an immortal object, just re-set the reference count.
+ */
if (PyUnicode_CHECK_INTERNED(unicode)) {
- /* Revive the dead object temporarily. PyDict_DelItem() removes two
- references (key and value) which were ignored by
- PyUnicode_InternInPlace(). Use refcnt=3 rather than refcnt=2
- to prevent calling unicode_dealloc() again. Adjust refcnt after
- PyDict_DelItem(). */
- assert(Py_REFCNT(unicode) == 0);
- Py_SET_REFCNT(unicode, 3);
- PyObject *interned = get_interned_dict(interp);
- assert(interned != NULL);
- if (PyDict_DelItem(interned, unicode) != 0) {
- _PyErr_WriteUnraisableMsg("deletion of interned string failed",
- NULL);
- }
- assert(Py_REFCNT(unicode) == 1);
- Py_SET_REFCNT(unicode, 0);
+ _Py_SetImmortal(unicode);
+ return;
}
-
if (_PyUnicode_HAS_UTF8_MEMORY(unicode)) {
PyObject_Free(_PyUnicode_UTF8(unicode));
}
return;
}
- /* The two references in interned dict (key and value) are not counted by
- refcnt. unicode_dealloc() and _PyUnicode_ClearInterned() take care of
- this. */
- Py_SET_REFCNT(s, Py_REFCNT(s) - 2);
- _PyUnicode_STATE(s).interned = 1;
+ if (_Py_IsImmortal(s)) {
+ _PyUnicode_STATE(*p).interned = SSTATE_INTERNED_IMMORTAL_STATIC;
+ return;
+ }
+#ifdef Py_REF_DEBUG
+ /* The reference count value excluding the 2 references from the
+ interned dictionary should be excluded from the RefTotal. The
+ decrements to these objects will not be registered so they
+ need to be accounted for in here. */
+ for (Py_ssize_t i = 0; i < Py_REFCNT(s) - 2; i++) {
+ _Py_DecRefTotal(_PyInterpreterState_GET());
+ }
+#endif
+ _Py_SetImmortal(s);
+ _PyUnicode_STATE(*p).interned = SSTATE_INTERNED_IMMORTAL;
}
void
}
assert(PyDict_CheckExact(interned));
- /* Interned unicode strings are not forcibly deallocated; rather, we give
- them their stolen references back, and then clear and DECREF the
- interned dict. */
-
+ /* TODO:
+ * Currently, the runtime is not able to guarantee that it can exit without
+ * allocations that carry over to a future initialization of Python within
+ * the same process. i.e:
+ * ./python -X showrefcount -c 'import itertools'
+ * [237 refs, 237 blocks]
+ *
+ * Therefore, this should remain disabled for until there is a strict guarantee
+ * that no memory will be left after `Py_Finalize`.
+ */
+#ifdef Py_DEBUG
+ /* For all non-singleton interned strings, restore the two valid references
+ to that instance from within the intern string dictionary and let the
+ normal reference counting process clean up these instances. */
#ifdef INTERNED_STATS
fprintf(stderr, "releasing %zd interned strings\n",
PyDict_GET_SIZE(interned));
Py_ssize_t pos = 0;
PyObject *s, *ignored_value;
while (PyDict_Next(interned, &pos, &s, &ignored_value)) {
- assert(PyUnicode_CHECK_INTERNED(s));
- // Restore the two references (key and value) ignored
- // by PyUnicode_InternInPlace().
- Py_SET_REFCNT(s, Py_REFCNT(s) + 2);
+ assert(PyUnicode_IS_READY(s));
+ switch (PyUnicode_CHECK_INTERNED(s)) {
+ case SSTATE_INTERNED_IMMORTAL:
+ // Skip the Immortal Instance check and restore
+ // the two references (key and value) ignored
+ // by PyUnicode_InternInPlace().
+ s->ob_refcnt = 2;
#ifdef INTERNED_STATS
- total_length += PyUnicode_GET_LENGTH(s);
+ total_length += PyUnicode_GET_LENGTH(s);
#endif
-
- _PyUnicode_STATE(s).interned = 0;
+ break;
+ case SSTATE_INTERNED_IMMORTAL_STATIC:
+ break;
+ case SSTATE_INTERNED_MORTAL:
+ /* fall through */
+ case SSTATE_NOT_INTERNED:
+ /* fall through */
+ default:
+ Py_UNREACHABLE();
+ }
+ _PyUnicode_STATE(s).interned = SSTATE_NOT_INTERNED;
}
#ifdef INTERNED_STATS
fprintf(stderr,
total_length);
#endif
+ struct _Py_unicode_state *state = &interp->unicode;
+ struct _Py_unicode_ids *ids = &state->ids;
+ for (Py_ssize_t i=0; i < ids->size; i++) {
+ Py_XINCREF(ids->array[i]);
+ }
+#endif /* Py_DEBUG */
clear_interned_dict(interp);
}
str1 = _PyUnicode_FromId(&PyId_test_unicode_id_init);
assert(str1 != NULL);
- assert(Py_REFCNT(str1) == 1);
+ assert(_Py_IsImmortal(str1));
str2 = PyUnicode_FromString("test_unicode_id_init");
assert(str2 != NULL);
assert(PyUnicode_Compare(str1, str2) == 0);
- // str1 is a borrowed reference
Py_DECREF(str2);
Py_Finalize();
#undef Py_DECREF
#define Py_DECREF(arg) \
do { \
- _Py_DECREF_STAT_INC(); \
PyObject *op = _PyObject_CAST(arg); \
+ if (_Py_IsImmortal(op)) { \
+ break; \
+ } \
+ _Py_DECREF_STAT_INC(); \
if (--op->ob_refcnt == 0) { \
destructor dealloc = Py_TYPE(op)->tp_dealloc; \
(*dealloc)(op); \
#undef _Py_DECREF_SPECIALIZED
#define _Py_DECREF_SPECIALIZED(arg, dealloc) \
do { \
- _Py_DECREF_STAT_INC(); \
PyObject *op = _PyObject_CAST(arg); \
+ if (_Py_IsImmortal(op)) { \
+ break; \
+ } \
+ _Py_DECREF_STAT_INC(); \
if (--op->ob_refcnt == 0) { \
destructor d = (destructor)(dealloc); \
d(op); \
return return_value;
}
+PyDoc_STRVAR(sys_getunicodeinternedsize__doc__,
+"getunicodeinternedsize($module, /)\n"
+"--\n"
+"\n"
+"Return the number of elements of the unicode interned dictionary");
+
+#define SYS_GETUNICODEINTERNEDSIZE_METHODDEF \
+ {"getunicodeinternedsize", (PyCFunction)sys_getunicodeinternedsize, METH_NOARGS, sys_getunicodeinternedsize__doc__},
+
+static Py_ssize_t
+sys_getunicodeinternedsize_impl(PyObject *module);
+
+static PyObject *
+sys_getunicodeinternedsize(PyObject *module, PyObject *Py_UNUSED(ignored))
+{
+ PyObject *return_value = NULL;
+ Py_ssize_t _return_value;
+
+ _return_value = sys_getunicodeinternedsize_impl(module);
+ if ((_return_value == -1) && PyErr_Occurred()) {
+ goto exit;
+ }
+ return_value = PyLong_FromSsize_t(_return_value);
+
+exit:
+ return return_value;
+}
+
PyDoc_STRVAR(sys__getframe__doc__,
"_getframe($module, depth=0, /)\n"
"--\n"
#ifndef SYS_GETANDROIDAPILEVEL_METHODDEF
#define SYS_GETANDROIDAPILEVEL_METHODDEF
#endif /* !defined(SYS_GETANDROIDAPILEVEL_METHODDEF) */
-/*[clinic end generated code: output=5c761f14326ced54 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=6d598acc26237fbe input=a9049054013a1b77]*/
static PyObject DISABLE =
{
- .ob_refcnt = _PyObject_IMMORTAL_REFCNT,
+ .ob_refcnt = _Py_IMMORTAL_REFCNT,
.ob_type = &PyBaseObject_Type
};
PyObject _PyInstrumentation_MISSING =
{
- .ob_refcnt = _PyObject_IMMORTAL_REFCNT,
+ .ob_refcnt = _Py_IMMORTAL_REFCNT,
.ob_type = &PyBaseObject_Type
};
PyTypeObject _PyLegacyEventHandler_Type = {
- _PyVarObject_IMMORTAL_INIT(&PyType_Type, 0),
+ PyVarObject_HEAD_INIT(&PyType_Type, 0)
"sys.legacy_event_handler",
sizeof(_PyLegacyEventHandler),
.tp_dealloc = (destructor)PyObject_Free,
PyStatus status;
PyObject *sysmod = NULL;
- // This is a temporary fix until we have immortal objects.
- // (See _PyType_InitCache() in typeobject.c.)
- extern void _PyType_FixCacheRefcounts(void);
- _PyType_FixCacheRefcounts();
-
// Create singletons before the first PyType_Ready() call, since
// PyType_Ready() uses singletons like the Unicode empty string (tp_doc)
// and the empty tuple singletons (tp_bases).
return _Py_GetAllocatedBlocks();
}
+/*[clinic input]
+sys.getunicodeinternedsize -> Py_ssize_t
+
+Return the number of elements of the unicode interned dictionary
+[clinic start generated code]*/
+
+static Py_ssize_t
+sys_getunicodeinternedsize_impl(PyObject *module)
+/*[clinic end generated code: output=ad0e4c9738ed4129 input=726298eaa063347a]*/
+{
+ return _PyUnicode_InternedSize();
+}
/*[clinic input]
sys._getframe
SYS_GETDEFAULTENCODING_METHODDEF
SYS_GETDLOPENFLAGS_METHODDEF
SYS_GETALLOCATEDBLOCKS_METHODDEF
+ SYS_GETUNICODEINTERNEDSIZE_METHODDEF
SYS_GETFILESYSTEMENCODING_METHODDEF
SYS_GETFILESYSTEMENCODEERRORS_METHODDEF
#ifdef Py_TRACE_REFS
def object_head(self, typename: str) -> None:
with self.block(".ob_base =", ","):
- self.write(f".ob_refcnt = 999999999,")
+ self.write(f".ob_refcnt = _Py_IMMORTAL_REFCNT,")
self.write(f".ob_type = &{typename},")
def object_var_head(self, typename: str, size: int) -> None: