Skip to content

gh-102304: Move the Total Refcount to PyInterpreterState #102545

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Mar 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Include/cpython/object.h
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down
2 changes: 2 additions & 0 deletions Include/internal/pycore_interp.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down
16 changes: 9 additions & 7 deletions Include/internal/pycore_object.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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);
Expand All @@ -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
Expand Down Expand Up @@ -226,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
Expand Down
8 changes: 8 additions & 0 deletions Include/internal/pycore_object_state.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ extern "C" {
#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;
#else
Expand Down
2 changes: 1 addition & 1 deletion Objects/bytesobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
10 changes: 5 additions & 5 deletions Objects/dictobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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++;
}
Expand All @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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--;
Expand Down
79 changes: 57 additions & 22 deletions Objects/object.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 *);
Expand All @@ -99,15 +99,43 @@ void
_Py_FinalizeRefTotal(_PyRuntimeState *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
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;
total += runtime->object_state.interpreter_leaks;

return total;
}

#undef REFTOTAL
Expand All @@ -118,7 +146,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 */

Expand Down Expand Up @@ -177,32 +206,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
Expand All @@ -219,6 +248,12 @@ _Py_GetLegacyRefTotal(void)
return get_legacy_reftotal();
}

Py_ssize_t
_PyInterpreterState_GetRefTotal(PyInterpreterState *interp)
{
return get_reftotal(interp);
}

#endif /* Py_REF_DEBUG */

void
Expand Down Expand Up @@ -2128,7 +2163,7 @@ void
_Py_NewReference(PyObject *op)
{
#ifdef Py_REF_DEBUG
reftotal_increment(&_PyRuntime);
reftotal_increment(_PyInterpreterState_GET());
#endif
new_reference(op);
}
Expand Down
2 changes: 1 addition & 1 deletion Objects/structseq.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion Objects/tupleobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
18 changes: 17 additions & 1 deletion Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -317,11 +317,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);
// (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)
Expand Down
5 changes: 5 additions & 0 deletions Python/pylifecycle.c
Original file line number Diff line number Diff line change
Expand Up @@ -802,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).
Expand Down
10 changes: 8 additions & 2 deletions Python/pystate.c
Original file line number Diff line number Diff line change
Expand Up @@ -483,8 +483,8 @@ void
_PyRuntimeState_Fini(_PyRuntimeState *runtime)
{
#ifdef Py_REF_DEBUG
/* The reftotal is cleared by _Py_FinalizeRefTotal(). */
assert(runtime->object_state.reftotal == 0);
/* The count is cleared by _Py_FinalizeRefTotal(). */
assert(runtime->object_state.interpreter_leaks == 0);
#endif

if (gilstate_tss_initialized(runtime)) {
Expand Down Expand Up @@ -904,6 +904,12 @@ 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;
for (p = &interpreters->head; ; p = &(*p)->next) {
Expand Down
2 changes: 2 additions & 0 deletions Python/sysmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand Down