gh-102304: Move the Total Refcount to PyInterpreterState (gh-102545)

Moving it valuable with a per-interpreter GIL.  However, it is also useful without one, since it allows us to identify refleaks within a single interpreter or where references are escaping an interpreter.  This becomes more important as we move the obmalloc state to PyInterpreterState.

https://github.com/python/cpython/issues/102304
This commit is contained in:
Eric Snow 2023-03-21 11:46:09 -06:00 committed by GitHub
parent 4bb1dd3c5c
commit 743687434c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 117 additions and 40 deletions

View File

@ -15,6 +15,7 @@ PyAPI_FUNC(void) _Py_ForgetReference(PyObject *);
PyAPI_FUNC(Py_ssize_t) _Py_GetGlobalRefTotal(void); PyAPI_FUNC(Py_ssize_t) _Py_GetGlobalRefTotal(void);
# define _Py_GetRefTotal() _Py_GetGlobalRefTotal() # define _Py_GetRefTotal() _Py_GetGlobalRefTotal()
PyAPI_FUNC(Py_ssize_t) _Py_GetLegacyRefTotal(void); PyAPI_FUNC(Py_ssize_t) _Py_GetLegacyRefTotal(void);
PyAPI_FUNC(Py_ssize_t) _PyInterpreterState_GetRefTotal(PyInterpreterState *);
#endif #endif

View File

@ -25,6 +25,7 @@ extern "C" {
#include "pycore_import.h" // struct _import_state #include "pycore_import.h" // struct _import_state
#include "pycore_list.h" // struct _Py_list_state #include "pycore_list.h" // struct _Py_list_state
#include "pycore_global_objects.h" // struct _Py_interp_static_objects #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_tuple.h" // struct _Py_tuple_state
#include "pycore_typeobject.h" // struct type_cache #include "pycore_typeobject.h" // struct type_cache
#include "pycore_unicodeobject.h" // struct _Py_unicode_state #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 // One bit is set for each non-NULL entry in code_watchers
uint8_t active_code_watchers; uint8_t active_code_watchers;
struct _py_object_state object_state;
struct _Py_unicode_state unicode; struct _Py_unicode_state unicode;
struct _Py_float_state float_state; struct _Py_float_state float_state;
struct _Py_long_state long_state; struct _Py_long_state long_state;

View File

@ -43,18 +43,19 @@ PyAPI_FUNC(void) _Py_NO_RETURN _Py_FatalRefcountErrorFunc(
built against the pre-3.12 stable ABI. */ built against the pre-3.12 stable ABI. */
PyAPI_DATA(Py_ssize_t) _Py_RefTotal; PyAPI_DATA(Py_ssize_t) _Py_RefTotal;
extern void _Py_AddRefTotal(Py_ssize_t); extern void _Py_AddRefTotal(PyInterpreterState *, Py_ssize_t);
extern void _Py_IncRefTotal(void); extern void _Py_IncRefTotal(PyInterpreterState *);
extern void _Py_DecRefTotal(void); extern void _Py_DecRefTotal(PyInterpreterState *);
# define _Py_DEC_REFTOTAL() _PyRuntime.object_state.reftotal-- # define _Py_DEC_REFTOTAL(interp) \
interp->object_state.reftotal--
#endif #endif
// Increment reference count by n // Increment reference count by n
static inline void _Py_RefcntAdd(PyObject* op, Py_ssize_t n) static inline void _Py_RefcntAdd(PyObject* op, Py_ssize_t n)
{ {
#ifdef Py_REF_DEBUG #ifdef Py_REF_DEBUG
_Py_AddRefTotal(n); _Py_AddRefTotal(_PyInterpreterState_GET(), n);
#endif #endif
op->ob_refcnt += n; op->ob_refcnt += n;
} }
@ -65,7 +66,7 @@ _Py_DECREF_SPECIALIZED(PyObject *op, const destructor destruct)
{ {
_Py_DECREF_STAT_INC(); _Py_DECREF_STAT_INC();
#ifdef Py_REF_DEBUG #ifdef Py_REF_DEBUG
_Py_DEC_REFTOTAL(); _Py_DEC_REFTOTAL(_PyInterpreterState_GET());
#endif #endif
if (--op->ob_refcnt != 0) { if (--op->ob_refcnt != 0) {
assert(op->ob_refcnt > 0); assert(op->ob_refcnt > 0);
@ -83,7 +84,7 @@ _Py_DECREF_NO_DEALLOC(PyObject *op)
{ {
_Py_DECREF_STAT_INC(); _Py_DECREF_STAT_INC();
#ifdef Py_REF_DEBUG #ifdef Py_REF_DEBUG
_Py_DEC_REFTOTAL(); _Py_DEC_REFTOTAL(_PyInterpreterState_GET());
#endif #endif
op->ob_refcnt--; op->ob_refcnt--;
#ifdef Py_DEBUG #ifdef Py_DEBUG
@ -226,6 +227,7 @@ static inline void _PyObject_GC_UNTRACK(
#endif #endif
#ifdef Py_REF_DEBUG #ifdef Py_REF_DEBUG
extern void _PyInterpreterState_FinalizeRefTotal(PyInterpreterState *);
extern void _Py_FinalizeRefTotal(_PyRuntimeState *); extern void _Py_FinalizeRefTotal(_PyRuntimeState *);
extern void _PyDebug_PrintTotalRefs(void); extern void _PyDebug_PrintTotalRefs(void);
#endif #endif

View File

@ -9,6 +9,14 @@ extern "C" {
#endif #endif
struct _py_object_runtime_state { 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 #ifdef Py_REF_DEBUG
Py_ssize_t reftotal; Py_ssize_t reftotal;
#else #else

View File

@ -3067,7 +3067,7 @@ _PyBytes_Resize(PyObject **pv, Py_ssize_t newsize)
PyObject_Realloc(v, PyBytesObject_SIZE + newsize); PyObject_Realloc(v, PyBytesObject_SIZE + newsize);
if (*pv == NULL) { if (*pv == NULL) {
#ifdef Py_REF_DEBUG #ifdef Py_REF_DEBUG
_Py_DecRefTotal(); _Py_DecRefTotal(_PyInterpreterState_GET());
#endif #endif
PyObject_Free(v); PyObject_Free(v);
PyErr_NoMemory(); PyErr_NoMemory();

View File

@ -304,7 +304,7 @@ static inline void
dictkeys_incref(PyDictKeysObject *dk) dictkeys_incref(PyDictKeysObject *dk)
{ {
#ifdef Py_REF_DEBUG #ifdef Py_REF_DEBUG
_Py_IncRefTotal(); _Py_IncRefTotal(_PyInterpreterState_GET());
#endif #endif
dk->dk_refcnt++; dk->dk_refcnt++;
} }
@ -314,7 +314,7 @@ dictkeys_decref(PyInterpreterState *interp, PyDictKeysObject *dk)
{ {
assert(dk->dk_refcnt > 0); assert(dk->dk_refcnt > 0);
#ifdef Py_REF_DEBUG #ifdef Py_REF_DEBUG
_Py_DecRefTotal(); _Py_DecRefTotal(_PyInterpreterState_GET());
#endif #endif
if (--dk->dk_refcnt == 0) { if (--dk->dk_refcnt == 0) {
free_keys_object(interp, dk); free_keys_object(interp, dk);
@ -634,7 +634,7 @@ new_keys_object(PyInterpreterState *interp, uint8_t log2_size, bool unicode)
} }
} }
#ifdef Py_REF_DEBUG #ifdef Py_REF_DEBUG
_Py_IncRefTotal(); _Py_IncRefTotal(_PyInterpreterState_GET());
#endif #endif
dk->dk_refcnt = 1; dk->dk_refcnt = 1;
dk->dk_log2_size = log2_size; 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 we have it now; calling dictkeys_incref would be an error as
keys->dk_refcnt is already set to 1 (after memcpy). */ keys->dk_refcnt is already set to 1 (after memcpy). */
#ifdef Py_REF_DEBUG #ifdef Py_REF_DEBUG
_Py_IncRefTotal(); _Py_IncRefTotal(_PyInterpreterState_GET());
#endif #endif
return keys; return keys;
} }
@ -1530,7 +1530,7 @@ dictresize(PyInterpreterState *interp, PyDictObject *mp,
// We can not use free_keys_object here because key's reference // We can not use free_keys_object here because key's reference
// are moved already. // are moved already.
#ifdef Py_REF_DEBUG #ifdef Py_REF_DEBUG
_Py_DecRefTotal(); _Py_DecRefTotal(_PyInterpreterState_GET());
#endif #endif
if (oldkeys == Py_EMPTY_KEYS) { if (oldkeys == Py_EMPTY_KEYS) {
oldkeys->dk_refcnt--; oldkeys->dk_refcnt--;

View File

@ -66,25 +66,25 @@ get_legacy_reftotal(void)
#ifdef Py_REF_DEBUG #ifdef Py_REF_DEBUG
# define REFTOTAL(runtime) \ # define REFTOTAL(interp) \
(runtime)->object_state.reftotal interp->object_state.reftotal
static inline void static inline void
reftotal_increment(_PyRuntimeState *runtime) reftotal_increment(PyInterpreterState *interp)
{ {
REFTOTAL(runtime)++; REFTOTAL(interp)++;
} }
static inline void static inline void
reftotal_decrement(_PyRuntimeState *runtime) reftotal_decrement(PyInterpreterState *interp)
{ {
REFTOTAL(runtime)--; REFTOTAL(interp)--;
} }
static inline void 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 *); static inline Py_ssize_t get_global_reftotal(_PyRuntimeState *);
@ -99,15 +99,43 @@ void
_Py_FinalizeRefTotal(_PyRuntimeState *runtime) _Py_FinalizeRefTotal(_PyRuntimeState *runtime)
{ {
last_final_reftotal = get_global_reftotal(runtime); last_final_reftotal = get_global_reftotal(runtime);
REFTOTAL(runtime) = 0; 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
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 static inline Py_ssize_t
get_global_reftotal(_PyRuntimeState *runtime) get_global_reftotal(_PyRuntimeState *runtime)
{ {
/* For an update from _Py_RefTotal first. */ Py_ssize_t total = 0;
Py_ssize_t legacy = get_legacy_reftotal();
return REFTOTAL(runtime) + legacy + last_final_reftotal; /* 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;
total += runtime->object_state.interpreter_leaks;
return total;
} }
#undef REFTOTAL #undef REFTOTAL
@ -118,7 +146,8 @@ _PyDebug_PrintTotalRefs(void) {
fprintf(stderr, fprintf(stderr,
"[%zd refs, %zd blocks]\n", "[%zd refs, %zd blocks]\n",
get_global_reftotal(runtime), _Py_GetAllocatedBlocks()); 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 */ #endif /* Py_REF_DEBUG */
@ -177,32 +206,32 @@ _Py_NegativeRefcount(const char *filename, int lineno, PyObject *op)
void void
_Py_IncRefTotal_DO_NOT_USE_THIS(void) _Py_IncRefTotal_DO_NOT_USE_THIS(void)
{ {
reftotal_increment(&_PyRuntime); reftotal_increment(_PyInterpreterState_GET());
} }
/* This is used strictly by Py_DECREF(). */ /* This is used strictly by Py_DECREF(). */
void void
_Py_DecRefTotal_DO_NOT_USE_THIS(void) _Py_DecRefTotal_DO_NOT_USE_THIS(void)
{ {
reftotal_decrement(&_PyRuntime); reftotal_decrement(_PyInterpreterState_GET());
} }
void void
_Py_IncRefTotal(void) _Py_IncRefTotal(PyInterpreterState *interp)
{ {
reftotal_increment(&_PyRuntime); reftotal_increment(interp);
} }
void void
_Py_DecRefTotal(void) _Py_DecRefTotal(PyInterpreterState *interp)
{ {
reftotal_decrement(&_PyRuntime); reftotal_decrement(interp);
} }
void 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 /* This includes the legacy total
@ -219,6 +248,12 @@ _Py_GetLegacyRefTotal(void)
return get_legacy_reftotal(); return get_legacy_reftotal();
} }
Py_ssize_t
_PyInterpreterState_GetRefTotal(PyInterpreterState *interp)
{
return get_reftotal(interp);
}
#endif /* Py_REF_DEBUG */ #endif /* Py_REF_DEBUG */
void void
@ -2128,7 +2163,7 @@ void
_Py_NewReference(PyObject *op) _Py_NewReference(PyObject *op)
{ {
#ifdef Py_REF_DEBUG #ifdef Py_REF_DEBUG
reftotal_increment(&_PyRuntime); reftotal_increment(_PyInterpreterState_GET());
#endif #endif
new_reference(op); new_reference(op);
} }

View File

@ -592,7 +592,7 @@ _PyStructSequence_FiniType(PyTypeObject *type)
// Don't use Py_DECREF(): static type must not be deallocated // Don't use Py_DECREF(): static type must not be deallocated
Py_SET_REFCNT(type, 0); Py_SET_REFCNT(type, 0);
#ifdef Py_REF_DEBUG #ifdef Py_REF_DEBUG
_Py_DecRefTotal(); _Py_DecRefTotal(_PyInterpreterState_GET());
#endif #endif
// Make sure that _PyStructSequence_InitType() will initialize // Make sure that _PyStructSequence_InitType() will initialize

View File

@ -944,7 +944,7 @@ _PyTuple_Resize(PyObject **pv, Py_ssize_t newsize)
if (sv == NULL) { if (sv == NULL) {
*pv = NULL; *pv = NULL;
#ifdef Py_REF_DEBUG #ifdef Py_REF_DEBUG
_Py_DecRefTotal(); _Py_DecRefTotal(_PyInterpreterState_GET());
#endif #endif
PyObject_GC_Del(v); PyObject_GC_Del(v);
return -1; return -1;

View File

@ -317,11 +317,27 @@ _PyType_InitCache(PyInterpreterState *interp)
entry->version = 0; entry->version = 0;
// Set to None so _PyType_Lookup() can use Py_SETREF(), // Set to None so _PyType_Lookup() can use Py_SETREF(),
// rather than using slower Py_XSETREF(). // rather than using slower Py_XSETREF().
entry->name = Py_NewRef(Py_None); // (See _PyType_FixCacheRefcounts() about the refcount.)
entry->name = Py_None;
entry->value = NULL; 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 static unsigned int
_PyType_ClearCache(PyInterpreterState *interp) _PyType_ClearCache(PyInterpreterState *interp)

View File

@ -802,6 +802,11 @@ pycore_interp_init(PyThreadState *tstate)
PyStatus status; PyStatus status;
PyObject *sysmod = NULL; 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 // Create singletons before the first PyType_Ready() call, since
// PyType_Ready() uses singletons like the Unicode empty string (tp_doc) // PyType_Ready() uses singletons like the Unicode empty string (tp_doc)
// and the empty tuple singletons (tp_bases). // and the empty tuple singletons (tp_bases).

View File

@ -483,8 +483,8 @@ void
_PyRuntimeState_Fini(_PyRuntimeState *runtime) _PyRuntimeState_Fini(_PyRuntimeState *runtime)
{ {
#ifdef Py_REF_DEBUG #ifdef Py_REF_DEBUG
/* The reftotal is cleared by _Py_FinalizeRefTotal(). */ /* The count is cleared by _Py_FinalizeRefTotal(). */
assert(runtime->object_state.reftotal == 0); assert(runtime->object_state.interpreter_leaks == 0);
#endif #endif
if (gilstate_tss_initialized(runtime)) { if (gilstate_tss_initialized(runtime)) {
@ -904,6 +904,12 @@ PyInterpreterState_Delete(PyInterpreterState *interp)
_PyEval_FiniState(&interp->ceval); _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); HEAD_LOCK(runtime);
PyInterpreterState **p; PyInterpreterState **p;
for (p = &interpreters->head; ; p = &(*p)->next) { for (p = &interpreters->head; ; p = &(*p)->next) {

View File

@ -1854,6 +1854,8 @@ static Py_ssize_t
sys_gettotalrefcount_impl(PyObject *module) sys_gettotalrefcount_impl(PyObject *module)
/*[clinic end generated code: output=4103886cf17c25bc input=53b744faa5d2e4f6]*/ /*[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(); return _Py_GetGlobalRefTotal();
} }