From a35bceb7ab4e2007658f295fe550551f0098d26f Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 12 Dec 2022 17:58:34 -0700 Subject: [PATCH 1/6] Move _Py_RefTotal to PyInterpreterState. --- Include/cpython/object.h | 1 + Include/internal/pycore_interp.h | 2 + Include/internal/pycore_object.h | 15 +++--- Include/internal/pycore_object_state.h | 2 +- Include/internal/pycore_runtime.h | 2 - Objects/bytesobject.c | 2 +- Objects/dictobject.c | 10 ++-- Objects/object.c | 70 ++++++++++++++++++-------- Objects/structseq.c | 2 +- Objects/tupleobject.c | 2 +- Python/pystate.c | 5 -- Python/sysmodule.c | 2 + 12 files changed, 70 insertions(+), 45 deletions(-) diff --git a/Include/cpython/object.h b/Include/cpython/object.h index 0438612edd1dfe..859ffb91e223dc 100644 --- a/Include/cpython/object.h +++ b/Include/cpython/object.h @@ -15,6 +15,7 @@ PyAPI_FUNC(void) _Py_ForgetReference(PyObject *); PyAPI_FUNC(Py_ssize_t) _Py_GetGlobalRefTotal(void); # define _Py_GetRefTotal() _Py_GetGlobalRefTotal() PyAPI_FUNC(Py_ssize_t) _Py_GetLegacyRefTotal(void); +PyAPI_FUNC(Py_ssize_t) _PyInterpreterState_GetRefTotal(PyInterpreterState *); #endif diff --git a/Include/internal/pycore_interp.h b/Include/internal/pycore_interp.h index 84303318d21811..1f2c0db2eb5f27 100644 --- a/Include/internal/pycore_interp.h +++ b/Include/internal/pycore_interp.h @@ -25,6 +25,7 @@ extern "C" { #include "pycore_import.h" // struct _import_state #include "pycore_list.h" // struct _Py_list_state #include "pycore_global_objects.h" // struct _Py_interp_static_objects +#include "pycore_object_state.h" // struct _py_object_state #include "pycore_tuple.h" // struct _Py_tuple_state #include "pycore_typeobject.h" // struct type_cache #include "pycore_unicodeobject.h" // struct _Py_unicode_state @@ -138,6 +139,7 @@ struct _is { // One bit is set for each non-NULL entry in code_watchers uint8_t active_code_watchers; + struct _py_object_state object_state; struct _Py_unicode_state unicode; struct _Py_float_state float_state; struct _Py_long_state long_state; diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h index b985eff8a8a08b..4bb82cfe4c4bdc 100644 --- a/Include/internal/pycore_object.h +++ b/Include/internal/pycore_object.h @@ -43,18 +43,19 @@ PyAPI_FUNC(void) _Py_NO_RETURN _Py_FatalRefcountErrorFunc( built against the pre-3.12 stable ABI. */ PyAPI_DATA(Py_ssize_t) _Py_RefTotal; -extern void _Py_AddRefTotal(Py_ssize_t); -extern void _Py_IncRefTotal(void); -extern void _Py_DecRefTotal(void); +extern void _Py_AddRefTotal(PyInterpreterState *, Py_ssize_t); +extern void _Py_IncRefTotal(PyInterpreterState *); +extern void _Py_DecRefTotal(PyInterpreterState *); -# define _Py_DEC_REFTOTAL() _PyRuntime.object_state.reftotal-- +# define _Py_DEC_REFTOTAL(interp) \ + interp->object_state.reftotal-- #endif // Increment reference count by n static inline void _Py_RefcntAdd(PyObject* op, Py_ssize_t n) { #ifdef Py_REF_DEBUG - _Py_AddRefTotal(n); + _Py_AddRefTotal(_PyInterpreterState_GET(), n); #endif op->ob_refcnt += n; } @@ -65,7 +66,7 @@ _Py_DECREF_SPECIALIZED(PyObject *op, const destructor destruct) { _Py_DECREF_STAT_INC(); #ifdef Py_REF_DEBUG - _Py_DEC_REFTOTAL(); + _Py_DEC_REFTOTAL(_PyInterpreterState_GET()); #endif if (--op->ob_refcnt != 0) { assert(op->ob_refcnt > 0); @@ -83,7 +84,7 @@ _Py_DECREF_NO_DEALLOC(PyObject *op) { _Py_DECREF_STAT_INC(); #ifdef Py_REF_DEBUG - _Py_DEC_REFTOTAL(); + _Py_DEC_REFTOTAL(_PyInterpreterState_GET()); #endif op->ob_refcnt--; #ifdef Py_DEBUG diff --git a/Include/internal/pycore_object_state.h b/Include/internal/pycore_object_state.h index 4e5862a11eddc5..f2fe33c3d30287 100644 --- a/Include/internal/pycore_object_state.h +++ b/Include/internal/pycore_object_state.h @@ -8,7 +8,7 @@ extern "C" { # error "this header requires Py_BUILD_CORE define" #endif -struct _py_object_runtime_state { +struct _py_object_state { #ifdef Py_REF_DEBUG Py_ssize_t reftotal; #else diff --git a/Include/internal/pycore_runtime.h b/Include/internal/pycore_runtime.h index de757dfa93bc26..520109ca440444 100644 --- a/Include/internal/pycore_runtime.h +++ b/Include/internal/pycore_runtime.h @@ -15,7 +15,6 @@ extern "C" { #include "pycore_global_objects.h" // struct _Py_global_objects #include "pycore_import.h" // struct _import_runtime_state #include "pycore_interp.h" // PyInterpreterState -#include "pycore_object_state.h" // struct _py_object_runtime_state #include "pycore_parser.h" // struct _parser_runtime_state #include "pycore_pymem.h" // struct _pymem_allocators #include "pycore_pyhash.h" // struct pyhash_runtime_state @@ -151,7 +150,6 @@ typedef struct pyruntimestate { void *open_code_userdata; _Py_AuditHookEntry *audit_hook_head; - struct _py_object_runtime_state object_state; struct _Py_float_runtime_state float_state; struct _Py_unicode_runtime_state unicode_state; diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c index 687a654bdae137..2d8dab6f378006 100644 --- a/Objects/bytesobject.c +++ b/Objects/bytesobject.c @@ -3067,7 +3067,7 @@ _PyBytes_Resize(PyObject **pv, Py_ssize_t newsize) PyObject_Realloc(v, PyBytesObject_SIZE + newsize); if (*pv == NULL) { #ifdef Py_REF_DEBUG - _Py_DecRefTotal(); + _Py_DecRefTotal(_PyInterpreterState_GET()); #endif PyObject_Free(v); PyErr_NoMemory(); diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 53f9a380346a0d..2ef520044340ee 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -304,7 +304,7 @@ static inline void dictkeys_incref(PyDictKeysObject *dk) { #ifdef Py_REF_DEBUG - _Py_IncRefTotal(); + _Py_IncRefTotal(_PyInterpreterState_GET()); #endif dk->dk_refcnt++; } @@ -314,7 +314,7 @@ dictkeys_decref(PyInterpreterState *interp, PyDictKeysObject *dk) { assert(dk->dk_refcnt > 0); #ifdef Py_REF_DEBUG - _Py_DecRefTotal(); + _Py_DecRefTotal(_PyInterpreterState_GET()); #endif if (--dk->dk_refcnt == 0) { free_keys_object(interp, dk); @@ -634,7 +634,7 @@ new_keys_object(PyInterpreterState *interp, uint8_t log2_size, bool unicode) } } #ifdef Py_REF_DEBUG - _Py_IncRefTotal(); + _Py_IncRefTotal(_PyInterpreterState_GET()); #endif dk->dk_refcnt = 1; dk->dk_log2_size = log2_size; @@ -824,7 +824,7 @@ clone_combined_dict_keys(PyDictObject *orig) we have it now; calling dictkeys_incref would be an error as keys->dk_refcnt is already set to 1 (after memcpy). */ #ifdef Py_REF_DEBUG - _Py_IncRefTotal(); + _Py_IncRefTotal(_PyInterpreterState_GET()); #endif return keys; } @@ -1530,7 +1530,7 @@ dictresize(PyInterpreterState *interp, PyDictObject *mp, // We can not use free_keys_object here because key's reference // are moved already. #ifdef Py_REF_DEBUG - _Py_DecRefTotal(); + _Py_DecRefTotal(_PyInterpreterState_GET()); #endif if (oldkeys == Py_EMPTY_KEYS) { oldkeys->dk_refcnt--; diff --git a/Objects/object.c b/Objects/object.c index 95f7c966a414de..b283203f5f86bb 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -66,25 +66,25 @@ get_legacy_reftotal(void) #ifdef Py_REF_DEBUG -# define REFTOTAL(runtime) \ - (runtime)->object_state.reftotal +# define REFTOTAL(interp) \ + interp->object_state.reftotal static inline void -reftotal_increment(_PyRuntimeState *runtime) +reftotal_increment(PyInterpreterState *interp) { - REFTOTAL(runtime)++; + REFTOTAL(interp)++; } static inline void -reftotal_decrement(_PyRuntimeState *runtime) +reftotal_decrement(PyInterpreterState *interp) { - REFTOTAL(runtime)--; + REFTOTAL(interp)--; } static inline void -reftotal_add(_PyRuntimeState *runtime, Py_ssize_t n) +reftotal_add(PyInterpreterState *interp, Py_ssize_t n) { - REFTOTAL(runtime) += n; + REFTOTAL(interp) += n; } static inline Py_ssize_t get_global_reftotal(_PyRuntimeState *); @@ -99,15 +99,34 @@ void _Py_FinalizeRefTotal(_PyRuntimeState *runtime) { last_final_reftotal = get_global_reftotal(runtime); - REFTOTAL(runtime) = 0; +} + +static inline Py_ssize_t +get_reftotal(PyInterpreterState *interp) +{ + /* For a single interpreter, we ignore the legacy _Py_RefTotal, + since we can't determine which interpreter updated it. */ + return REFTOTAL(interp); } static inline Py_ssize_t get_global_reftotal(_PyRuntimeState *runtime) { - /* For an update from _Py_RefTotal first. */ - Py_ssize_t legacy = get_legacy_reftotal(); - return REFTOTAL(runtime) + legacy + last_final_reftotal; + Py_ssize_t total = 0; + + /* Add up the total from each interpreter. */ + HEAD_LOCK(&_PyRuntime); + PyInterpreterState *interp = PyInterpreterState_Head(); + for (; interp != NULL; interp = PyInterpreterState_Next(interp)) { + total += REFTOTAL(interp); + } + HEAD_UNLOCK(&_PyRuntime); + + /* Add in the updated value from the legacy _Py_RefTotal. */ + total += get_legacy_reftotal(); + total += last_final_reftotal; + + return total; } #undef REFTOTAL @@ -118,7 +137,8 @@ _PyDebug_PrintTotalRefs(void) { fprintf(stderr, "[%zd refs, %zd blocks]\n", get_global_reftotal(runtime), _Py_GetAllocatedBlocks()); - /* It may be helpful to also print the "legacy" reftotal separately. */ + /* It may be helpful to also print the "legacy" reftotal separately. + Likewise for the total for each interpreter. */ } #endif /* Py_REF_DEBUG */ @@ -177,32 +197,32 @@ _Py_NegativeRefcount(const char *filename, int lineno, PyObject *op) void _Py_IncRefTotal_DO_NOT_USE_THIS(void) { - reftotal_increment(&_PyRuntime); + reftotal_increment(_PyInterpreterState_GET()); } /* This is used strictly by Py_DECREF(). */ void _Py_DecRefTotal_DO_NOT_USE_THIS(void) { - reftotal_decrement(&_PyRuntime); + reftotal_decrement(_PyInterpreterState_GET()); } void -_Py_IncRefTotal(void) +_Py_IncRefTotal(PyInterpreterState *interp) { - reftotal_increment(&_PyRuntime); + reftotal_increment(interp); } void -_Py_DecRefTotal(void) +_Py_DecRefTotal(PyInterpreterState *interp) { - reftotal_decrement(&_PyRuntime); + reftotal_decrement(interp); } void -_Py_AddRefTotal(Py_ssize_t n) +_Py_AddRefTotal(PyInterpreterState *interp, Py_ssize_t n) { - reftotal_add(&_PyRuntime, n); + reftotal_add(interp, n); } /* This includes the legacy total @@ -219,6 +239,12 @@ _Py_GetLegacyRefTotal(void) return get_legacy_reftotal(); } +Py_ssize_t +_PyInterpreterState_GetRefTotal(PyInterpreterState *interp) +{ + return get_reftotal(interp); +} + #endif /* Py_REF_DEBUG */ void @@ -2128,7 +2154,7 @@ void _Py_NewReference(PyObject *op) { #ifdef Py_REF_DEBUG - reftotal_increment(&_PyRuntime); + reftotal_increment(_PyInterpreterState_GET()); #endif new_reference(op); } diff --git a/Objects/structseq.c b/Objects/structseq.c index c20962ecd82563..2a5343815866d3 100644 --- a/Objects/structseq.c +++ b/Objects/structseq.c @@ -592,7 +592,7 @@ _PyStructSequence_FiniType(PyTypeObject *type) // Don't use Py_DECREF(): static type must not be deallocated Py_SET_REFCNT(type, 0); #ifdef Py_REF_DEBUG - _Py_DecRefTotal(); + _Py_DecRefTotal(_PyInterpreterState_GET()); #endif // Make sure that _PyStructSequence_InitType() will initialize diff --git a/Objects/tupleobject.c b/Objects/tupleobject.c index 59c0251639d3dd..61fab4078d66ba 100644 --- a/Objects/tupleobject.c +++ b/Objects/tupleobject.c @@ -944,7 +944,7 @@ _PyTuple_Resize(PyObject **pv, Py_ssize_t newsize) if (sv == NULL) { *pv = NULL; #ifdef Py_REF_DEBUG - _Py_DecRefTotal(); + _Py_DecRefTotal(_PyInterpreterState_GET()); #endif PyObject_GC_Del(v); return -1; diff --git a/Python/pystate.c b/Python/pystate.c index 60adb54685ce68..3a2966c54a4c3b 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -482,11 +482,6 @@ _PyRuntimeState_Init(_PyRuntimeState *runtime) void _PyRuntimeState_Fini(_PyRuntimeState *runtime) { -#ifdef Py_REF_DEBUG - /* The reftotal is cleared by _Py_FinalizeRefTotal(). */ - assert(runtime->object_state.reftotal == 0); -#endif - if (gilstate_tss_initialized(runtime)) { gilstate_tss_fini(runtime); } diff --git a/Python/sysmodule.c b/Python/sysmodule.c index 20761738b527cb..4afb0f1d0b5ed2 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -1854,6 +1854,8 @@ static Py_ssize_t sys_gettotalrefcount_impl(PyObject *module) /*[clinic end generated code: output=4103886cf17c25bc input=53b744faa5d2e4f6]*/ { + /* It may make sense to return the total for the current interpreter + or have a second function that does so. */ return _Py_GetGlobalRefTotal(); } From cf58f54bd8d486dab1af267255c1fcc9c38d47f6 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 9 Mar 2023 12:06:08 -0700 Subject: [PATCH 2/6] Deal with Py_None getting incref'ed before the GIL is created. --- Objects/typeobject.c | 19 ++++++++++++++++++- Python/pylifecycle.c | 5 +++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index f0654c239f6635..6900a83a38577e 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -309,6 +309,7 @@ type_cache_clear(struct type_cache *cache, PyObject *value) void _PyType_InitCache(PyInterpreterState *interp) { + int is_main = _Py_IsMainInterpreter(interp); struct type_cache *cache = &interp->types.type_cache; for (Py_ssize_t i = 0; i < (1 << MCACHE_SIZE_EXP); i++) { struct type_cache_entry *entry = &cache->hashtable[i]; @@ -317,11 +318,27 @@ _PyType_InitCache(PyInterpreterState *interp) entry->version = 0; // Set to None so _PyType_Lookup() can use Py_SETREF(), // rather than using slower Py_XSETREF(). - entry->name = Py_NewRef(Py_None); + entry->name = Py_None; + // Currently, this happens before the GIL has been created, + // which is a problem without "immortality" (PEP 683). + // Until we have immortal objects, we must avoid an INCREF + // in the main interpreter here. Instead, we fix the refcount + // in the main interpreer as soon as the GIL has been created. + // (See pycore_create_interpreter() in pylifecycle.c.) + if (!is_main) { + Py_INCREF(Py_None); + } entry->value = NULL; } } +// This is the temporary fix used by pycore_create_interpreter(). +void +_PyType_FixCacheRefcounts(void) +{ + _Py_RefcntAdd(Py_None, (1 << MCACHE_SIZE_EXP)); +} + static unsigned int _PyType_ClearCache(PyInterpreterState *interp) diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 731f340001b4e0..e6db87240f2e15 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -635,6 +635,11 @@ pycore_create_interpreter(_PyRuntimeState *runtime, return status; } + // This is a temporary fix until we have immortal objects. + // (See _PyType_InitCache() in typeobject.c.) + extern void _PyType_FixCacheRefcounts(void); + _PyType_FixCacheRefcounts(); + *tstate_p = tstate; return _PyStatus_OK(); } From 8159be6d293ec99dbc71bb87ae30cc2bcfbd0b1d Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 20 Mar 2023 09:52:39 -0600 Subject: [PATCH 3/6] Fix _PyType_InitCache(). --- Objects/typeobject.c | 21 ++++++++++----------- Python/pylifecycle.c | 10 +++++----- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 6900a83a38577e..a37f97c71ec763 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -309,7 +309,6 @@ type_cache_clear(struct type_cache *cache, PyObject *value) void _PyType_InitCache(PyInterpreterState *interp) { - int is_main = _Py_IsMainInterpreter(interp); struct type_cache *cache = &interp->types.type_cache; for (Py_ssize_t i = 0; i < (1 << MCACHE_SIZE_EXP); i++) { struct type_cache_entry *entry = &cache->hashtable[i]; @@ -318,21 +317,21 @@ _PyType_InitCache(PyInterpreterState *interp) 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; - // Currently, this happens before the GIL has been created, - // which is a problem without "immortality" (PEP 683). - // Until we have immortal objects, we must avoid an INCREF - // in the main interpreter here. Instead, we fix the refcount - // in the main interpreer as soon as the GIL has been created. - // (See pycore_create_interpreter() in pylifecycle.c.) - if (!is_main) { - Py_INCREF(Py_None); - } entry->value = NULL; } } -// This is the temporary fix used by pycore_create_interpreter(). +// 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) { diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index e6db87240f2e15..20e8a76bcd0209 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -635,11 +635,6 @@ pycore_create_interpreter(_PyRuntimeState *runtime, return status; } - // This is a temporary fix until we have immortal objects. - // (See _PyType_InitCache() in typeobject.c.) - extern void _PyType_FixCacheRefcounts(void); - _PyType_FixCacheRefcounts(); - *tstate_p = tstate; return _PyStatus_OK(); } @@ -807,6 +802,11 @@ pycore_interp_init(PyThreadState *tstate) 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). From d7e51e544450e351d135f6a4385ff6ad6bf31237 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 17 Mar 2023 16:21:09 -0600 Subject: [PATCH 4/6] Track refcounts leaked by interpreters. --- Include/internal/pycore_object.h | 1 + Include/internal/pycore_object_state.h | 8 ++++++++ Include/internal/pycore_runtime.h | 2 ++ Objects/object.c | 9 +++++++++ Python/pystate.c | 7 +++++++ 5 files changed, 27 insertions(+) diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h index 4bb82cfe4c4bdc..d6bbafd4b6cccc 100644 --- a/Include/internal/pycore_object.h +++ b/Include/internal/pycore_object.h @@ -227,6 +227,7 @@ static inline void _PyObject_GC_UNTRACK( #endif #ifdef Py_REF_DEBUG +extern void _PyInterpreterState_FinalizeRefTotal(PyInterpreterState *); extern void _Py_FinalizeRefTotal(_PyRuntimeState *); extern void _PyDebug_PrintTotalRefs(void); #endif diff --git a/Include/internal/pycore_object_state.h b/Include/internal/pycore_object_state.h index f2fe33c3d30287..94005d77881432 100644 --- a/Include/internal/pycore_object_state.h +++ b/Include/internal/pycore_object_state.h @@ -8,6 +8,14 @@ extern "C" { # error "this header requires Py_BUILD_CORE define" #endif +struct _py_object_runtime_state { +#ifdef Py_REF_DEBUG + Py_ssize_t interpreter_leaks; +#else + int _not_used; +#endif +}; + struct _py_object_state { #ifdef Py_REF_DEBUG Py_ssize_t reftotal; diff --git a/Include/internal/pycore_runtime.h b/Include/internal/pycore_runtime.h index 520109ca440444..de757dfa93bc26 100644 --- a/Include/internal/pycore_runtime.h +++ b/Include/internal/pycore_runtime.h @@ -15,6 +15,7 @@ extern "C" { #include "pycore_global_objects.h" // struct _Py_global_objects #include "pycore_import.h" // struct _import_runtime_state #include "pycore_interp.h" // PyInterpreterState +#include "pycore_object_state.h" // struct _py_object_runtime_state #include "pycore_parser.h" // struct _parser_runtime_state #include "pycore_pymem.h" // struct _pymem_allocators #include "pycore_pyhash.h" // struct pyhash_runtime_state @@ -150,6 +151,7 @@ typedef struct pyruntimestate { void *open_code_userdata; _Py_AuditHookEntry *audit_hook_head; + struct _py_object_runtime_state object_state; struct _Py_float_runtime_state float_state; struct _Py_unicode_runtime_state unicode_state; diff --git a/Objects/object.c b/Objects/object.c index b283203f5f86bb..9dd5eb998217f6 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -99,6 +99,14 @@ void _Py_FinalizeRefTotal(_PyRuntimeState *runtime) { last_final_reftotal = get_global_reftotal(runtime); + runtime->object_state.interpreter_leaks = 0; +} + +void +_PyInterpreterState_FinalizeRefTotal(PyInterpreterState *interp) +{ + interp->runtime->object_state.interpreter_leaks += REFTOTAL(interp); + REFTOTAL(interp) = 0; } static inline Py_ssize_t @@ -125,6 +133,7 @@ get_global_reftotal(_PyRuntimeState *runtime) /* Add in the updated value from the legacy _Py_RefTotal. */ total += get_legacy_reftotal(); total += last_final_reftotal; + total += runtime->object_state.interpreter_leaks; return total; } diff --git a/Python/pystate.c b/Python/pystate.c index 3a2966c54a4c3b..fcab09f0aa95c4 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -482,6 +482,11 @@ _PyRuntimeState_Init(_PyRuntimeState *runtime) void _PyRuntimeState_Fini(_PyRuntimeState *runtime) { +#ifdef Py_REF_DEBUG + /* The count is cleared by _Py_FinalizeRefTotal(). */ + assert(runtime->object_state.interpreter_leaks == 0); +#endif + if (gilstate_tss_initialized(runtime)) { gilstate_tss_fini(runtime); } @@ -856,6 +861,8 @@ interpreter_clear(PyInterpreterState *interp, PyThreadState *tstate) // XXX Once we have one allocator per interpreter (i.e. // per-interpreter GC) we must ensure that all of the interpreter's // objects have been cleaned up at the point. + + _PyInterpreterState_FinalizeRefTotal(interp); } From f64202d4326ae6f37d2b0dd327fb528a0177a3f2 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 20 Mar 2023 11:52:01 -0600 Subject: [PATCH 5/6] Move _PyInterpreterState_FinalizeRefTotal() down to PyInterpreterState_Delete(). --- Python/pystate.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Python/pystate.c b/Python/pystate.c index fcab09f0aa95c4..3b131ed386ca09 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -861,8 +861,6 @@ interpreter_clear(PyInterpreterState *interp, PyThreadState *tstate) // XXX Once we have one allocator per interpreter (i.e. // per-interpreter GC) we must ensure that all of the interpreter's // objects have been cleaned up at the point. - - _PyInterpreterState_FinalizeRefTotal(interp); } @@ -906,6 +904,10 @@ PyInterpreterState_Delete(PyInterpreterState *interp) _PyEval_FiniState(&interp->ceval); + // XXX This call should be done at the end of clear_interpreter(), + // but currently some objects get decref'ed after that. + _PyInterpreterState_FinalizeRefTotal(interp); + HEAD_LOCK(runtime); PyInterpreterState **p; for (p = &interpreters->head; ; p = &(*p)->next) { From 475db68496720489248ced51e2029df554d95c36 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 20 Mar 2023 12:51:36 -0600 Subject: [PATCH 6/6] Guard for Py_REF_DEBUG. --- Python/pystate.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Python/pystate.c b/Python/pystate.c index 3b131ed386ca09..b17efdbefd124c 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -904,9 +904,11 @@ PyInterpreterState_Delete(PyInterpreterState *interp) _PyEval_FiniState(&interp->ceval); +#ifdef Py_REF_DEBUG // XXX This call should be done at the end of clear_interpreter(), // but currently some objects get decref'ed after that. _PyInterpreterState_FinalizeRefTotal(interp); +#endif HEAD_LOCK(runtime); PyInterpreterState **p;