Skip to content

Commit 7436874

Browse files
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. #102304
1 parent 4bb1dd3 commit 7436874

File tree

13 files changed

+117
-40
lines changed

13 files changed

+117
-40
lines changed

Include/cpython/object.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ PyAPI_FUNC(void) _Py_ForgetReference(PyObject *);
1515
PyAPI_FUNC(Py_ssize_t) _Py_GetGlobalRefTotal(void);
1616
# define _Py_GetRefTotal() _Py_GetGlobalRefTotal()
1717
PyAPI_FUNC(Py_ssize_t) _Py_GetLegacyRefTotal(void);
18+
PyAPI_FUNC(Py_ssize_t) _PyInterpreterState_GetRefTotal(PyInterpreterState *);
1819
#endif
1920

2021

Include/internal/pycore_interp.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ extern "C" {
2525
#include "pycore_import.h" // struct _import_state
2626
#include "pycore_list.h" // struct _Py_list_state
2727
#include "pycore_global_objects.h" // struct _Py_interp_static_objects
28+
#include "pycore_object_state.h" // struct _py_object_state
2829
#include "pycore_tuple.h" // struct _Py_tuple_state
2930
#include "pycore_typeobject.h" // struct type_cache
3031
#include "pycore_unicodeobject.h" // struct _Py_unicode_state
@@ -138,6 +139,7 @@ struct _is {
138139
// One bit is set for each non-NULL entry in code_watchers
139140
uint8_t active_code_watchers;
140141

142+
struct _py_object_state object_state;
141143
struct _Py_unicode_state unicode;
142144
struct _Py_float_state float_state;
143145
struct _Py_long_state long_state;

Include/internal/pycore_object.h

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,18 +43,19 @@ PyAPI_FUNC(void) _Py_NO_RETURN _Py_FatalRefcountErrorFunc(
4343
built against the pre-3.12 stable ABI. */
4444
PyAPI_DATA(Py_ssize_t) _Py_RefTotal;
4545

46-
extern void _Py_AddRefTotal(Py_ssize_t);
47-
extern void _Py_IncRefTotal(void);
48-
extern void _Py_DecRefTotal(void);
46+
extern void _Py_AddRefTotal(PyInterpreterState *, Py_ssize_t);
47+
extern void _Py_IncRefTotal(PyInterpreterState *);
48+
extern void _Py_DecRefTotal(PyInterpreterState *);
4949

50-
# define _Py_DEC_REFTOTAL() _PyRuntime.object_state.reftotal--
50+
# define _Py_DEC_REFTOTAL(interp) \
51+
interp->object_state.reftotal--
5152
#endif
5253

5354
// Increment reference count by n
5455
static inline void _Py_RefcntAdd(PyObject* op, Py_ssize_t n)
5556
{
5657
#ifdef Py_REF_DEBUG
57-
_Py_AddRefTotal(n);
58+
_Py_AddRefTotal(_PyInterpreterState_GET(), n);
5859
#endif
5960
op->ob_refcnt += n;
6061
}
@@ -65,7 +66,7 @@ _Py_DECREF_SPECIALIZED(PyObject *op, const destructor destruct)
6566
{
6667
_Py_DECREF_STAT_INC();
6768
#ifdef Py_REF_DEBUG
68-
_Py_DEC_REFTOTAL();
69+
_Py_DEC_REFTOTAL(_PyInterpreterState_GET());
6970
#endif
7071
if (--op->ob_refcnt != 0) {
7172
assert(op->ob_refcnt > 0);
@@ -83,7 +84,7 @@ _Py_DECREF_NO_DEALLOC(PyObject *op)
8384
{
8485
_Py_DECREF_STAT_INC();
8586
#ifdef Py_REF_DEBUG
86-
_Py_DEC_REFTOTAL();
87+
_Py_DEC_REFTOTAL(_PyInterpreterState_GET());
8788
#endif
8889
op->ob_refcnt--;
8990
#ifdef Py_DEBUG
@@ -226,6 +227,7 @@ static inline void _PyObject_GC_UNTRACK(
226227
#endif
227228

228229
#ifdef Py_REF_DEBUG
230+
extern void _PyInterpreterState_FinalizeRefTotal(PyInterpreterState *);
229231
extern void _Py_FinalizeRefTotal(_PyRuntimeState *);
230232
extern void _PyDebug_PrintTotalRefs(void);
231233
#endif

Include/internal/pycore_object_state.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@ extern "C" {
99
#endif
1010

1111
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+
19+
struct _py_object_state {
1220
#ifdef Py_REF_DEBUG
1321
Py_ssize_t reftotal;
1422
#else

Objects/bytesobject.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3067,7 +3067,7 @@ _PyBytes_Resize(PyObject **pv, Py_ssize_t newsize)
30673067
PyObject_Realloc(v, PyBytesObject_SIZE + newsize);
30683068
if (*pv == NULL) {
30693069
#ifdef Py_REF_DEBUG
3070-
_Py_DecRefTotal();
3070+
_Py_DecRefTotal(_PyInterpreterState_GET());
30713071
#endif
30723072
PyObject_Free(v);
30733073
PyErr_NoMemory();

Objects/dictobject.c

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ static inline void
304304
dictkeys_incref(PyDictKeysObject *dk)
305305
{
306306
#ifdef Py_REF_DEBUG
307-
_Py_IncRefTotal();
307+
_Py_IncRefTotal(_PyInterpreterState_GET());
308308
#endif
309309
dk->dk_refcnt++;
310310
}
@@ -314,7 +314,7 @@ dictkeys_decref(PyInterpreterState *interp, PyDictKeysObject *dk)
314314
{
315315
assert(dk->dk_refcnt > 0);
316316
#ifdef Py_REF_DEBUG
317-
_Py_DecRefTotal();
317+
_Py_DecRefTotal(_PyInterpreterState_GET());
318318
#endif
319319
if (--dk->dk_refcnt == 0) {
320320
free_keys_object(interp, dk);
@@ -634,7 +634,7 @@ new_keys_object(PyInterpreterState *interp, uint8_t log2_size, bool unicode)
634634
}
635635
}
636636
#ifdef Py_REF_DEBUG
637-
_Py_IncRefTotal();
637+
_Py_IncRefTotal(_PyInterpreterState_GET());
638638
#endif
639639
dk->dk_refcnt = 1;
640640
dk->dk_log2_size = log2_size;
@@ -824,7 +824,7 @@ clone_combined_dict_keys(PyDictObject *orig)
824824
we have it now; calling dictkeys_incref would be an error as
825825
keys->dk_refcnt is already set to 1 (after memcpy). */
826826
#ifdef Py_REF_DEBUG
827-
_Py_IncRefTotal();
827+
_Py_IncRefTotal(_PyInterpreterState_GET());
828828
#endif
829829
return keys;
830830
}
@@ -1530,7 +1530,7 @@ dictresize(PyInterpreterState *interp, PyDictObject *mp,
15301530
// We can not use free_keys_object here because key's reference
15311531
// are moved already.
15321532
#ifdef Py_REF_DEBUG
1533-
_Py_DecRefTotal();
1533+
_Py_DecRefTotal(_PyInterpreterState_GET());
15341534
#endif
15351535
if (oldkeys == Py_EMPTY_KEYS) {
15361536
oldkeys->dk_refcnt--;

Objects/object.c

Lines changed: 57 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -66,25 +66,25 @@ get_legacy_reftotal(void)
6666

6767
#ifdef Py_REF_DEBUG
6868

69-
# define REFTOTAL(runtime) \
70-
(runtime)->object_state.reftotal
69+
# define REFTOTAL(interp) \
70+
interp->object_state.reftotal
7171

7272
static inline void
73-
reftotal_increment(_PyRuntimeState *runtime)
73+
reftotal_increment(PyInterpreterState *interp)
7474
{
75-
REFTOTAL(runtime)++;
75+
REFTOTAL(interp)++;
7676
}
7777

7878
static inline void
79-
reftotal_decrement(_PyRuntimeState *runtime)
79+
reftotal_decrement(PyInterpreterState *interp)
8080
{
81-
REFTOTAL(runtime)--;
81+
REFTOTAL(interp)--;
8282
}
8383

8484
static inline void
85-
reftotal_add(_PyRuntimeState *runtime, Py_ssize_t n)
85+
reftotal_add(PyInterpreterState *interp, Py_ssize_t n)
8686
{
87-
REFTOTAL(runtime) += n;
87+
REFTOTAL(interp) += n;
8888
}
8989

9090
static inline Py_ssize_t get_global_reftotal(_PyRuntimeState *);
@@ -99,15 +99,43 @@ void
9999
_Py_FinalizeRefTotal(_PyRuntimeState *runtime)
100100
{
101101
last_final_reftotal = get_global_reftotal(runtime);
102-
REFTOTAL(runtime) = 0;
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;
110+
}
111+
112+
static inline Py_ssize_t
113+
get_reftotal(PyInterpreterState *interp)
114+
{
115+
/* For a single interpreter, we ignore the legacy _Py_RefTotal,
116+
since we can't determine which interpreter updated it. */
117+
return REFTOTAL(interp);
103118
}
104119

105120
static inline Py_ssize_t
106121
get_global_reftotal(_PyRuntimeState *runtime)
107122
{
108-
/* For an update from _Py_RefTotal first. */
109-
Py_ssize_t legacy = get_legacy_reftotal();
110-
return REFTOTAL(runtime) + legacy + last_final_reftotal;
123+
Py_ssize_t total = 0;
124+
125+
/* Add up the total from each interpreter. */
126+
HEAD_LOCK(&_PyRuntime);
127+
PyInterpreterState *interp = PyInterpreterState_Head();
128+
for (; interp != NULL; interp = PyInterpreterState_Next(interp)) {
129+
total += REFTOTAL(interp);
130+
}
131+
HEAD_UNLOCK(&_PyRuntime);
132+
133+
/* Add in the updated value from the legacy _Py_RefTotal. */
134+
total += get_legacy_reftotal();
135+
total += last_final_reftotal;
136+
total += runtime->object_state.interpreter_leaks;
137+
138+
return total;
111139
}
112140

113141
#undef REFTOTAL
@@ -118,7 +146,8 @@ _PyDebug_PrintTotalRefs(void) {
118146
fprintf(stderr,
119147
"[%zd refs, %zd blocks]\n",
120148
get_global_reftotal(runtime), _Py_GetAllocatedBlocks());
121-
/* It may be helpful to also print the "legacy" reftotal separately. */
149+
/* It may be helpful to also print the "legacy" reftotal separately.
150+
Likewise for the total for each interpreter. */
122151
}
123152
#endif /* Py_REF_DEBUG */
124153

@@ -177,32 +206,32 @@ _Py_NegativeRefcount(const char *filename, int lineno, PyObject *op)
177206
void
178207
_Py_IncRefTotal_DO_NOT_USE_THIS(void)
179208
{
180-
reftotal_increment(&_PyRuntime);
209+
reftotal_increment(_PyInterpreterState_GET());
181210
}
182211

183212
/* This is used strictly by Py_DECREF(). */
184213
void
185214
_Py_DecRefTotal_DO_NOT_USE_THIS(void)
186215
{
187-
reftotal_decrement(&_PyRuntime);
216+
reftotal_decrement(_PyInterpreterState_GET());
188217
}
189218

190219
void
191-
_Py_IncRefTotal(void)
220+
_Py_IncRefTotal(PyInterpreterState *interp)
192221
{
193-
reftotal_increment(&_PyRuntime);
222+
reftotal_increment(interp);
194223
}
195224

196225
void
197-
_Py_DecRefTotal(void)
226+
_Py_DecRefTotal(PyInterpreterState *interp)
198227
{
199-
reftotal_decrement(&_PyRuntime);
228+
reftotal_decrement(interp);
200229
}
201230

202231
void
203-
_Py_AddRefTotal(Py_ssize_t n)
232+
_Py_AddRefTotal(PyInterpreterState *interp, Py_ssize_t n)
204233
{
205-
reftotal_add(&_PyRuntime, n);
234+
reftotal_add(interp, n);
206235
}
207236

208237
/* This includes the legacy total
@@ -219,6 +248,12 @@ _Py_GetLegacyRefTotal(void)
219248
return get_legacy_reftotal();
220249
}
221250

251+
Py_ssize_t
252+
_PyInterpreterState_GetRefTotal(PyInterpreterState *interp)
253+
{
254+
return get_reftotal(interp);
255+
}
256+
222257
#endif /* Py_REF_DEBUG */
223258

224259
void
@@ -2128,7 +2163,7 @@ void
21282163
_Py_NewReference(PyObject *op)
21292164
{
21302165
#ifdef Py_REF_DEBUG
2131-
reftotal_increment(&_PyRuntime);
2166+
reftotal_increment(_PyInterpreterState_GET());
21322167
#endif
21332168
new_reference(op);
21342169
}

Objects/structseq.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -592,7 +592,7 @@ _PyStructSequence_FiniType(PyTypeObject *type)
592592
// Don't use Py_DECREF(): static type must not be deallocated
593593
Py_SET_REFCNT(type, 0);
594594
#ifdef Py_REF_DEBUG
595-
_Py_DecRefTotal();
595+
_Py_DecRefTotal(_PyInterpreterState_GET());
596596
#endif
597597

598598
// Make sure that _PyStructSequence_InitType() will initialize

Objects/tupleobject.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -944,7 +944,7 @@ _PyTuple_Resize(PyObject **pv, Py_ssize_t newsize)
944944
if (sv == NULL) {
945945
*pv = NULL;
946946
#ifdef Py_REF_DEBUG
947-
_Py_DecRefTotal();
947+
_Py_DecRefTotal(_PyInterpreterState_GET());
948948
#endif
949949
PyObject_GC_Del(v);
950950
return -1;

Objects/typeobject.c

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,11 +317,27 @@ _PyType_InitCache(PyInterpreterState *interp)
317317
entry->version = 0;
318318
// Set to None so _PyType_Lookup() can use Py_SETREF(),
319319
// rather than using slower Py_XSETREF().
320-
entry->name = Py_NewRef(Py_None);
320+
// (See _PyType_FixCacheRefcounts() about the refcount.)
321+
entry->name = Py_None;
321322
entry->value = NULL;
322323
}
323324
}
324325

326+
// This is the temporary fix used by pycore_create_interpreter(),
327+
// in pylifecycle.c. _PyType_InitCache() is called before the GIL
328+
// has been created (for the main interpreter) and without the
329+
// "current" thread state set. This causes crashes when the
330+
// reftotal is updated, so we don't modify the refcount in
331+
// _PyType_InitCache(), and instead do it later by calling
332+
// _PyType_FixCacheRefcounts().
333+
// XXX This workaround should be removed once we have immortal
334+
// objects (PEP 683).
335+
void
336+
_PyType_FixCacheRefcounts(void)
337+
{
338+
_Py_RefcntAdd(Py_None, (1 << MCACHE_SIZE_EXP));
339+
}
340+
325341

326342
static unsigned int
327343
_PyType_ClearCache(PyInterpreterState *interp)

0 commit comments

Comments
 (0)