diff --git a/Doc/data/python3.14.abi b/Doc/data/python3.14.abi index 7dc43dd58ce2d5..78f380ee46d4a5 100644 --- a/Doc/data/python3.14.abi +++ b/Doc/data/python3.14.abi @@ -1817,7 +1817,7 @@ - + @@ -4202,30 +4202,30 @@ - + - + - + - + - + - + @@ -8404,7 +8404,7 @@ - + @@ -21024,7 +21024,7 @@ - + @@ -21059,324 +21059,327 @@ - + - + + + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -21388,385 +21391,385 @@ - - + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - - - + - + - + - - + + - - + + - + - - + + - - + + - + - + - + - + - + - - + + - + - + - - + + - - + + - + - + - + - - + + - - + + - - + + - - + + - - + + - + - + - + - - + + - - + + - - - - - + + - + + + + - - + + - - + + - + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - + - + - + - + - + - + - - - - + - - + + - + + + + - + - + - - + + - - + + - - + + - - + + - - + + - + + + + - + - + - + - + - + - + - + @@ -22235,7 +22238,7 @@ - + @@ -25879,7 +25882,7 @@ - + @@ -26900,21 +26903,21 @@ - + - - - - + + + + - - - - - + + + + + @@ -27832,6 +27835,20 @@ + + + + + + + + + + + + + + @@ -27842,16 +27859,16 @@ - - - - + + + + - - - - + + + + @@ -27987,7 +28004,7 @@ - + @@ -28099,7 +28116,7 @@ - + @@ -28124,17 +28141,17 @@ - - + + - - + + - - - + + + @@ -28246,15 +28263,6 @@ - - - - - - - - - @@ -28270,11 +28278,6 @@ - - - - - @@ -30087,7 +30090,7 @@ - + @@ -30636,7 +30639,7 @@ - + diff --git a/Include/internal/pycore_gc.h b/Include/internal/pycore_gc.h index e105677cd2e674..bfe52f42f1141c 100644 --- a/Include/internal/pycore_gc.h +++ b/Include/internal/pycore_gc.h @@ -223,12 +223,14 @@ static inline void _PyObject_GC_TRACK( "object is in generation which is garbage collected", filename, lineno, __func__); - PyGC_Head *generation0 = _PyInterpreterState_GET()->gc.generation0; + struct _gc_runtime_state *gcstate = &_PyInterpreterState_GET()->gc; + PyGC_Head *generation0 = gcstate->generation0; PyGC_Head *last = (PyGC_Head*)(generation0->_gc_prev); _PyGCHead_SET_NEXT(last, gc); _PyGCHead_SET_PREV(gc, last); _PyGCHead_SET_NEXT(gc, generation0); generation0->_gc_prev = (uintptr_t)gc; + gcstate->heap_size++; #endif } @@ -263,6 +265,8 @@ static inline void _PyObject_GC_UNTRACK( _PyGCHead_SET_PREV(next, prev); gc->_gc_next = 0; gc->_gc_prev &= _PyGC_PREV_MASK_FINALIZED; + struct _gc_runtime_state *gcstate = &_PyInterpreterState_GET()->gc; + gcstate->heap_size--; #endif } diff --git a/Include/internal/pycore_interp_structs.h b/Include/internal/pycore_interp_structs.h index f186caab42bbf6..972bf7bf6e8054 100644 --- a/Include/internal/pycore_interp_structs.h +++ b/Include/internal/pycore_interp_structs.h @@ -227,6 +227,9 @@ struct _gc_runtime_state { /* a list of callbacks to be invoked when collection is performed */ PyObject *callbacks; + /* The number of live objects. */ + Py_ssize_t heap_size; + /* This is the number of objects that survived the last full collection. It approximates the number of long lived objects tracked by the GC. @@ -261,7 +264,8 @@ struct _gc_runtime_state { { .threshold = 2000, }, \ { .threshold = 10, }, \ { .threshold = 10, }, \ - }, + }, \ + .heap_size = 0, #else #define GC_GENERATION_INIT \ .young = { .threshold = 2000, }, \ diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py index cd9b34cb79da51..0fe63332d15c9c 100644 --- a/Lib/test/test_gc.py +++ b/Lib/test/test_gc.py @@ -1220,6 +1220,15 @@ def test_tuple_untrack_counts(self): # Use n // 2 just in case some other objects were collected. self.assertTrue(new_count - count > (n // 2)) + @requires_gil_enabled('need generational GC') + @unittest.skipIf(_testinternalcapi is None, "requires _testinternalcapi") + def test_heap_size(self): + count = _testinternalcapi.get_tracked_heap_size() + l = [] + self.assertEqual(count + 1, _testinternalcapi.get_tracked_heap_size()) + del l + self.assertEqual(count, _testinternalcapi.get_tracked_heap_size()) + class GCCallbackTests(unittest.TestCase): def setUp(self): diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index ae4da5fa55e487..158c26d442ed13 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -2353,8 +2353,7 @@ has_deferred_refcount(PyObject *self, PyObject *op) static PyObject * get_tracked_heap_size(PyObject *self, PyObject *Py_UNUSED(ignored)) { - // Generational GC doesn't track heap_size, return -1. - return PyLong_FromInt64(-1); + return PyLong_FromInt64(PyInterpreterState_Get()->gc.heap_size); } static PyObject * diff --git a/Python/gc.c b/Python/gc.c index 0559dfb11f0e41..469282683604b8 100644 --- a/Python/gc.c +++ b/Python/gc.c @@ -1981,6 +1981,8 @@ PyObject_GC_Del(void *op) PyGC_Head *g = AS_GC(op); if (_PyObject_GC_IS_TRACKED(op)) { gc_list_remove(g); + GCState *gcstate = get_gc_state(); + gcstate->heap_size--; #ifdef Py_DEBUG PyObject *exc = PyErr_GetRaisedException(); if (PyErr_WarnExplicitFormat(PyExc_ResourceWarning, "gc", 0,