Skip to content

Commit 780e7ec

Browse files
Track refcounts leaked by interpreters.
1 parent 2719cba commit 780e7ec

File tree

5 files changed

+25
-0
lines changed

5 files changed

+25
-0
lines changed

Include/internal/pycore_object.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ static inline void _PyObject_GC_UNTRACK(
227227
#endif
228228

229229
#ifdef Py_REF_DEBUG
230+
extern void _PyInterpreterState_FinalizeRefTotal(PyInterpreterState *);
230231
extern void _Py_FinalizeRefTotal(_PyRuntimeState *);
231232
extern void _PyDebug_PrintTotalRefs(void);
232233
#endif

Include/internal/pycore_object_state.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ extern "C" {
88
# error "this header requires Py_BUILD_CORE define"
99
#endif
1010

11+
struct _py_object_runtime_state {
12+
#ifdef Py_REF_DEBUG
13+
Py_ssize_t interpreter_leaks;
14+
#else
15+
int _not_used;
16+
#endif
17+
};
18+
1119
struct _py_object_state {
1220
#ifdef Py_REF_DEBUG
1321
Py_ssize_t reftotal;

Include/internal/pycore_runtime.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ extern "C" {
1616
#include "pycore_global_objects.h" // struct _Py_global_objects
1717
#include "pycore_import.h" // struct _import_runtime_state
1818
#include "pycore_interp.h" // PyInterpreterState
19+
#include "pycore_object_state.h" // struct _py_object_runtime_state
1920
#include "pycore_parser.h" // struct _parser_runtime_state
2021
#include "pycore_pymem.h" // struct _pymem_allocators
2122
#include "pycore_pyhash.h" // struct pyhash_runtime_state
@@ -151,6 +152,7 @@ typedef struct pyruntimestate {
151152
void *open_code_userdata;
152153
_Py_AuditHookEntry *audit_hook_head;
153154

155+
struct _py_object_runtime_state object_state;
154156
struct _Py_float_runtime_state float_state;
155157
struct _Py_unicode_runtime_state unicode_state;
156158
struct _Py_dict_runtime_state dict_state;

Objects/object.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,14 @@ void
9999
_Py_FinalizeRefTotal(_PyRuntimeState *runtime)
100100
{
101101
last_final_reftotal = get_global_reftotal(runtime);
102+
runtime->object_state.interpreter_leaks = 0;
103+
}
104+
105+
void
106+
_PyInterpreterState_FinalizeRefTotal(PyInterpreterState *interp)
107+
{
108+
interp->runtime->object_state.interpreter_leaks += REFTOTAL(interp);
109+
REFTOTAL(interp) = 0;
102110
}
103111

104112
static inline Py_ssize_t
@@ -125,6 +133,7 @@ get_global_reftotal(_PyRuntimeState *runtime)
125133
/* Add in the updated value from the legacy _Py_RefTotal. */
126134
total += get_legacy_reftotal();
127135
total += last_final_reftotal;
136+
total += runtime->object_state.interpreter_leaks;
128137

129138
return total;
130139
}

Python/pystate.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,9 @@ _PyRuntimeState_Init(_PyRuntimeState *runtime)
482482
void
483483
_PyRuntimeState_Fini(_PyRuntimeState *runtime)
484484
{
485+
/* The count is cleared by _Py_FinalizeRefTotal(). */
486+
assert(runtime->object_state.interpreter_leaks == 0);
487+
485488
if (gilstate_tss_initialized(runtime)) {
486489
gilstate_tss_fini(runtime);
487490
}
@@ -855,6 +858,8 @@ interpreter_clear(PyInterpreterState *interp, PyThreadState *tstate)
855858
// XXX Once we have one allocator per interpreter (i.e.
856859
// per-interpreter GC) we must ensure that all of the interpreter's
857860
// objects have been cleaned up at the point.
861+
862+
_PyInterpreterState_FinalizeRefTotal(interp);
858863
}
859864

860865

0 commit comments

Comments
 (0)