From 005a1435856c1e706b10c8ad15ecd41143d2e283 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 8 Mar 2023 14:29:39 -0700 Subject: [PATCH 01/18] _Py_RefTotal -> REFTOTAL. --- Objects/object.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/Objects/object.c b/Objects/object.c index 38da4d497a96e7..ce387a54b63d19 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -54,32 +54,40 @@ _PyObject_CheckConsistency(PyObject *op, int check_content) #ifdef Py_REF_DEBUG +// XXX Move this to _PyRuntimeState. Py_ssize_t _Py_RefTotal; +#endif + +#ifdef Py_REF_DEBUG + +# define REFTOTAL _Py_RefTotal static inline void reftotal_increment(void) { - _Py_RefTotal++; + REFTOTAL++; } static inline void reftotal_decrement(void) { - _Py_RefTotal--; + REFTOTAL--; } void _Py_AddRefTotal(Py_ssize_t n) { - _Py_RefTotal += n; + REFTOTAL += n; } Py_ssize_t _Py_GetRefTotal(void) { - return _Py_RefTotal; + return REFTOTAL; } +#undef REFTOTAL + void _PyDebug_PrintTotalRefs(void) { fprintf(stderr, @@ -197,6 +205,9 @@ _Py_DecRef(PyObject *o) Py_DECREF(o); } + +/**************************************/ + PyObject * PyObject_Init(PyObject *op, PyTypeObject *tp) { From b5c783beb28cc79006c7951c6a7c29272b767860 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 8 Mar 2023 14:55:39 -0700 Subject: [PATCH 02/18] Separate the internal helper functions. --- Objects/object.c | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/Objects/object.c b/Objects/object.c index ce387a54b63d19..1efc1759f6732b 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -74,14 +74,14 @@ reftotal_decrement(void) REFTOTAL--; } -void -_Py_AddRefTotal(Py_ssize_t n) +static inline void +reftotal_add(Py_ssize_t n) { REFTOTAL += n; } -Py_ssize_t -_Py_GetRefTotal(void) +static inline Py_ssize_t +get_global_reftotal(void) { return REFTOTAL; } @@ -92,7 +92,7 @@ void _PyDebug_PrintTotalRefs(void) { fprintf(stderr, "[%zd refs, %zd blocks]\n", - _Py_GetRefTotal(), _Py_GetAllocatedBlocks()); + get_global_reftotal(), _Py_GetAllocatedBlocks()); } #endif /* Py_REF_DEBUG */ @@ -173,6 +173,18 @@ _Py_DecRefTotal(void) reftotal_decrement(); } +void +_Py_AddRefTotal(Py_ssize_t n) +{ + reftotal_add(n); +} + +Py_ssize_t +_Py_GetRefTotal(void) +{ + return get_global_reftotal(); +} + #endif /* Py_REF_DEBUG */ void From d286080b7f47de4580e8ee6f1bc8f04b2759c416 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 8 Mar 2023 15:17:26 -0700 Subject: [PATCH 03/18] Drop the Py_BUILD_CORE case. --- Include/object.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Include/object.h b/Include/object.h index 844b9c4a51c3e4..ba9741fc5a03d8 100644 --- a/Include/object.h +++ b/Include/object.h @@ -494,11 +494,6 @@ you can count such references to the type object.) extern Py_ssize_t _Py_RefTotal; # define _Py_INC_REFTOTAL() _Py_RefTotal++ # define _Py_DEC_REFTOTAL() _Py_RefTotal-- -# elif defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) -extern void _Py_IncRefTotal(void); -extern void _Py_DecRefTotal(void); -# define _Py_INC_REFTOTAL() _Py_IncRefTotal() -# define _Py_DEC_REFTOTAL() _Py_DecRefTotal() # elif !defined(Py_LIMITED_API) || Py_LIMITED_API+0 > 0x030C0000 extern void _Py_IncRefTotal_DO_NOT_USE_THIS(void); extern void _Py_DecRefTotal_DO_NOT_USE_THIS(void); From 31a14ed3620fd29510845af254c496aafc5147a7 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 8 Mar 2023 15:19:24 -0700 Subject: [PATCH 04/18] Add a blank line. --- Include/internal/pycore_object.h | 1 + 1 file changed, 1 insertion(+) diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h index e15685f174ebcf..ac7936e193b38a 100644 --- a/Include/internal/pycore_object.h +++ b/Include/internal/pycore_object.h @@ -46,6 +46,7 @@ PyAPI_DATA(Py_ssize_t) _Py_RefTotal; extern void _Py_AddRefTotal(Py_ssize_t); extern void _Py_IncRefTotal(void); extern void _Py_DecRefTotal(void); + # define _Py_DEC_REFTOTAL() _Py_RefTotal-- #endif From 8ca39834537ae30cca3453475fd6a6b58ebd4741 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 8 Mar 2023 12:43:30 -0700 Subject: [PATCH 05/18] _Py_GetRefTotal() -> _Py_GetGlobalRefTotal(). --- Include/cpython/object.h | 4 +++- Objects/object.c | 2 +- Python/sysmodule.c | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Include/cpython/object.h b/Include/cpython/object.h index 7b687d311359c3..2d2f0452bd624f 100644 --- a/Include/cpython/object.h +++ b/Include/cpython/object.h @@ -11,7 +11,9 @@ PyAPI_FUNC(void) _Py_ForgetReference(PyObject *); #endif #ifdef Py_REF_DEBUG -PyAPI_FUNC(Py_ssize_t) _Py_GetRefTotal(void); +/* These are useful as debugging aids when chasing down refleaks. */ +PyAPI_FUNC(Py_ssize_t) _Py_GetGlobalRefTotal(void); +# define _Py_GetRefTotal() _Py_GetGlobalRefTotal() #endif diff --git a/Objects/object.c b/Objects/object.c index 1efc1759f6732b..d13128fd4bd4c9 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -180,7 +180,7 @@ _Py_AddRefTotal(Py_ssize_t n) } Py_ssize_t -_Py_GetRefTotal(void) +_Py_GetGlobalRefTotal(void) { return get_global_reftotal(); } diff --git a/Python/sysmodule.c b/Python/sysmodule.c index 207abb964bcac9..6dcf5c6d71c734 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -1850,7 +1850,7 @@ static Py_ssize_t sys_gettotalrefcount_impl(PyObject *module) /*[clinic end generated code: output=4103886cf17c25bc input=53b744faa5d2e4f6]*/ { - return _Py_GetRefTotal(); + return _Py_GetGlobalRefTotal(); } #endif /* Py_REF_DEBUG */ From bff6a6d3775e5f0e54ea896ed1809d0c66f42326 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 14 Nov 2022 17:10:34 -0700 Subject: [PATCH 06/18] Move _Py_RefTotal to _PyRuntimeState. --- Include/internal/pycore_object.h | 2 +- Include/internal/pycore_object_state.h | 23 +++++++++++++++++++++++ Include/internal/pycore_runtime.h | 2 ++ Makefile.pre.in | 1 + Objects/object.c | 4 ++-- PCbuild/pythoncore.vcxproj | 1 + PCbuild/pythoncore.vcxproj.filters | 3 +++ Tools/c-analyzer/cpython/ignored.tsv | 1 - 8 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 Include/internal/pycore_object_state.h diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h index ac7936e193b38a..be1cfc95135cf2 100644 --- a/Include/internal/pycore_object.h +++ b/Include/internal/pycore_object.h @@ -47,7 +47,7 @@ extern void _Py_AddRefTotal(Py_ssize_t); extern void _Py_IncRefTotal(void); extern void _Py_DecRefTotal(void); -# define _Py_DEC_REFTOTAL() _Py_RefTotal-- +# define _Py_DEC_REFTOTAL() _PyRuntime.object_state.reftotal-- #endif // Increment reference count by n diff --git a/Include/internal/pycore_object_state.h b/Include/internal/pycore_object_state.h new file mode 100644 index 00000000000000..4e5862a11eddc5 --- /dev/null +++ b/Include/internal/pycore_object_state.h @@ -0,0 +1,23 @@ +#ifndef Py_INTERNAL_OBJECT_STATE_H +#define Py_INTERNAL_OBJECT_STATE_H +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef Py_BUILD_CORE +# error "this header requires Py_BUILD_CORE define" +#endif + +struct _py_object_runtime_state { +#ifdef Py_REF_DEBUG + Py_ssize_t reftotal; +#else + int _not_used; +#endif +}; + + +#ifdef __cplusplus +} +#endif +#endif /* !Py_INTERNAL_OBJECT_STATE_H */ diff --git a/Include/internal/pycore_runtime.h b/Include/internal/pycore_runtime.h index e0e3d4ace0cfde..ba4ff2db87acf1 100644 --- a/Include/internal/pycore_runtime.h +++ b/Include/internal/pycore_runtime.h @@ -16,6 +16,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 @@ -151,6 +152,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; struct _Py_dict_runtime_state dict_state; diff --git a/Makefile.pre.in b/Makefile.pre.in index 1a1853bf3d7871..e855dd9920e77c 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -1698,6 +1698,7 @@ PYTHON_HEADERS= \ $(srcdir)/Include/internal/pycore_moduleobject.h \ $(srcdir)/Include/internal/pycore_namespace.h \ $(srcdir)/Include/internal/pycore_object.h \ + $(srcdir)/Include/internal/pycore_object_state.h \ $(srcdir)/Include/internal/pycore_obmalloc.h \ $(srcdir)/Include/internal/pycore_obmalloc_init.h \ $(srcdir)/Include/internal/pycore_pathconfig.h \ diff --git a/Objects/object.c b/Objects/object.c index d13128fd4bd4c9..805582aad75681 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -54,13 +54,13 @@ _PyObject_CheckConsistency(PyObject *op, int check_content) #ifdef Py_REF_DEBUG -// XXX Move this to _PyRuntimeState. +/* We keep the legacy symbol around for backward compatibility. */ Py_ssize_t _Py_RefTotal; #endif #ifdef Py_REF_DEBUG -# define REFTOTAL _Py_RefTotal +# define REFTOTAL (_PyRuntime.object_state.reftotal) static inline void reftotal_increment(void) diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj index 85dc8caa458ed9..dd535721d201a8 100644 --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -238,6 +238,7 @@ + diff --git a/PCbuild/pythoncore.vcxproj.filters b/PCbuild/pythoncore.vcxproj.filters index 98e7d59ba1020c..09b26a137fc083 100644 --- a/PCbuild/pythoncore.vcxproj.filters +++ b/PCbuild/pythoncore.vcxproj.filters @@ -618,6 +618,9 @@ Include\internal + + Include\internal + Include\internal diff --git a/Tools/c-analyzer/cpython/ignored.tsv b/Tools/c-analyzer/cpython/ignored.tsv index 700ddf2851839e..f3e60c159da429 100644 --- a/Tools/c-analyzer/cpython/ignored.tsv +++ b/Tools/c-analyzer/cpython/ignored.tsv @@ -141,7 +141,6 @@ Modules/syslogmodule.c - S_log_open - ##----------------------- ## kept for stable ABI compatibility -# XXX should be per-interpreter, without impacting stable ABI extensions Objects/object.c - _Py_RefTotal - ##----------------------- From 5ab4c4b8b63e7ce7631d5570b7e9cade44830130 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 12 Dec 2022 17:45:17 -0700 Subject: [PATCH 07/18] Include the legacy total in _Py_GetGlobalRefTotal(). --- Include/cpython/object.h | 1 + Include/internal/pycore_object_state.h | 1 + Objects/object.c | 19 ++++++++++++++++++- 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/Include/cpython/object.h b/Include/cpython/object.h index 2d2f0452bd624f..0438612edd1dfe 100644 --- a/Include/cpython/object.h +++ b/Include/cpython/object.h @@ -14,6 +14,7 @@ PyAPI_FUNC(void) _Py_ForgetReference(PyObject *); /* These are useful as debugging aids when chasing down refleaks. */ PyAPI_FUNC(Py_ssize_t) _Py_GetGlobalRefTotal(void); # define _Py_GetRefTotal() _Py_GetGlobalRefTotal() +PyAPI_FUNC(Py_ssize_t) _Py_GetLegacyRefTotal(void); #endif diff --git a/Include/internal/pycore_object_state.h b/Include/internal/pycore_object_state.h index 4e5862a11eddc5..61f7abf2002537 100644 --- a/Include/internal/pycore_object_state.h +++ b/Include/internal/pycore_object_state.h @@ -10,6 +10,7 @@ extern "C" { struct _py_object_runtime_state { #ifdef Py_REF_DEBUG + Py_ssize_t last_legacy_reftotal; Py_ssize_t reftotal; #else int _not_used; diff --git a/Objects/object.c b/Objects/object.c index 805582aad75681..fbdf09bc0a8186 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -56,6 +56,12 @@ _PyObject_CheckConsistency(PyObject *op, int check_content) #ifdef Py_REF_DEBUG /* We keep the legacy symbol around for backward compatibility. */ Py_ssize_t _Py_RefTotal; + +static inline Py_ssize_t +get_legacy_reftotal(void) +{ + return _Py_RefTotal; +} #endif #ifdef Py_REF_DEBUG @@ -83,7 +89,10 @@ reftotal_add(Py_ssize_t n) static inline Py_ssize_t get_global_reftotal(void) { - return REFTOTAL; + Py_ssize_t last = _PyRuntime.object_state.last_legacy_reftotal; + Py_ssize_t legacy = get_legacy_reftotal(); + _PyRuntime.object_state.last_legacy_reftotal = legacy; + return REFTOTAL + legacy - last; } #undef REFTOTAL @@ -93,6 +102,7 @@ _PyDebug_PrintTotalRefs(void) { fprintf(stderr, "[%zd refs, %zd blocks]\n", get_global_reftotal(), _Py_GetAllocatedBlocks()); + /* It may be helpful to also print the "legacy" reftotal separately. */ } #endif /* Py_REF_DEBUG */ @@ -179,12 +189,19 @@ _Py_AddRefTotal(Py_ssize_t n) reftotal_add(n); } +/* This includes the legacy total. */ Py_ssize_t _Py_GetGlobalRefTotal(void) { return get_global_reftotal(); } +Py_ssize_t +_Py_GetLegacyRefTotal(void) +{ + return get_legacy_reftotal(); +} + #endif /* Py_REF_DEBUG */ void From cc5a99a98d0f4f809f5628037597b3b2775f3b59 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 9 Mar 2023 10:07:52 -0700 Subject: [PATCH 08/18] Fix get_global_refcount(). --- Objects/object.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Objects/object.c b/Objects/object.c index fbdf09bc0a8186..369780e5445116 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -92,7 +92,8 @@ get_global_reftotal(void) Py_ssize_t last = _PyRuntime.object_state.last_legacy_reftotal; Py_ssize_t legacy = get_legacy_reftotal(); _PyRuntime.object_state.last_legacy_reftotal = legacy; - return REFTOTAL + legacy - last; + REFTOTAL += legacy - last; + return REFTOTAL; } #undef REFTOTAL From f382c4a2d0e33d12a71e4ae6bfa297a9c0546104 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 9 Mar 2023 10:00:27 -0700 Subject: [PATCH 09/18] Stop being so clever with PyAPI_FUNC(). --- Include/object.h | 4 ++-- Objects/object.c | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Include/object.h b/Include/object.h index ba9741fc5a03d8..fc577353c1cc13 100644 --- a/Include/object.h +++ b/Include/object.h @@ -495,8 +495,8 @@ extern Py_ssize_t _Py_RefTotal; # define _Py_INC_REFTOTAL() _Py_RefTotal++ # define _Py_DEC_REFTOTAL() _Py_RefTotal-- # elif !defined(Py_LIMITED_API) || Py_LIMITED_API+0 > 0x030C0000 -extern void _Py_IncRefTotal_DO_NOT_USE_THIS(void); -extern void _Py_DecRefTotal_DO_NOT_USE_THIS(void); +PyAPI_FUNC(void) _Py_IncRefTotal_DO_NOT_USE_THIS(void); +PyAPI_FUNC(void) _Py_DecRefTotal_DO_NOT_USE_THIS(void); # define _Py_INC_REFTOTAL() _Py_IncRefTotal_DO_NOT_USE_THIS() # define _Py_DEC_REFTOTAL() _Py_DecRefTotal_DO_NOT_USE_THIS() # endif diff --git a/Objects/object.c b/Objects/object.c index 369780e5445116..a4298931afff7b 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -158,15 +158,15 @@ _Py_NegativeRefcount(const char *filename, int lineno, PyObject *op) filename, lineno, __func__); } -/* This is exposed strictly for use in Py_INCREF(). */ -PyAPI_FUNC(void) +/* This is used strictly by Py_INCREF(). */ +void _Py_IncRefTotal_DO_NOT_USE_THIS(void) { reftotal_increment(); } -/* This is exposed strictly for use in Py_DECREF(). */ -PyAPI_FUNC(void) +/* This is used strictly by Py_DECREF(). */ +void _Py_DecRefTotal_DO_NOT_USE_THIS(void) { reftotal_decrement(); From 38e086077fa81e32b76db4b0415bf2a1a8969761 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 9 Mar 2023 12:10:23 -0700 Subject: [PATCH 10/18] Drop unnecessary incr/decr to the reftotal. --- Objects/object.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Objects/object.c b/Objects/object.c index a4298931afff7b..1831437e11beef 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -220,18 +220,12 @@ Py_DecRef(PyObject *o) void _Py_IncRef(PyObject *o) { -#ifdef Py_REF_DEBUG - reftotal_increment(); -#endif Py_INCREF(o); } void _Py_DecRef(PyObject *o) { -#ifdef Py_REF_DEBUG - reftotal_decrement(); -#endif Py_DECREF(o); } From 7dc494d2b56ee6808dcfa712c24f21331a5d02b0 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 17 Mar 2023 13:57:17 -0600 Subject: [PATCH 11/18] Preserve the reftotal past runtime finalization. --- Include/internal/pycore_object.h | 1 + Objects/object.c | 44 +++++++++++++++++++++----------- Python/pylifecycle.c | 1 + 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h index be1cfc95135cf2..9ca88264a29b12 100644 --- a/Include/internal/pycore_object.h +++ b/Include/internal/pycore_object.h @@ -226,6 +226,7 @@ static inline void _PyObject_GC_UNTRACK( #endif #ifdef Py_REF_DEBUG +extern void _Py_SetFinalRefTotal(_PyRuntimeState *); extern void _PyDebug_PrintTotalRefs(void); #endif diff --git a/Objects/object.c b/Objects/object.c index 1831437e11beef..75f0e4c269ca08 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -66,43 +66,56 @@ get_legacy_reftotal(void) #ifdef Py_REF_DEBUG -# define REFTOTAL (_PyRuntime.object_state.reftotal) +# define REFTOTAL ((runtime)->object_state.reftotal) static inline void -reftotal_increment(void) +reftotal_increment(_PyRuntimeState *runtime) { REFTOTAL++; } static inline void -reftotal_decrement(void) +reftotal_decrement(_PyRuntimeState *runtime) { REFTOTAL--; } static inline void -reftotal_add(Py_ssize_t n) +reftotal_add(_PyRuntimeState *runtime, Py_ssize_t n) { REFTOTAL += n; } +/* We preserve the number of refs leaked during runtime finalization, + so they can be reported if the runtime is initialized again. */ +// XXX We don't lose any information by dropping this, +// so we should consider doing so. +static Py_ssize_t last_final_reftotal = 0; + +void +_Py_SetFinalRefTotal(_PyRuntimeState *runtime) +{ + last_final_reftotal += REFTOTAL; +} + static inline Py_ssize_t -get_global_reftotal(void) +get_global_reftotal(_PyRuntimeState *runtime) { Py_ssize_t last = _PyRuntime.object_state.last_legacy_reftotal; Py_ssize_t legacy = get_legacy_reftotal(); _PyRuntime.object_state.last_legacy_reftotal = legacy; REFTOTAL += legacy - last; - return REFTOTAL; + return REFTOTAL + last_final_reftotal; } #undef REFTOTAL void _PyDebug_PrintTotalRefs(void) { + _PyRuntimeState *runtime = &_PyRuntime; fprintf(stderr, "[%zd refs, %zd blocks]\n", - get_global_reftotal(), _Py_GetAllocatedBlocks()); + get_global_reftotal(runtime), _Py_GetAllocatedBlocks()); /* It may be helpful to also print the "legacy" reftotal separately. */ } #endif /* Py_REF_DEBUG */ @@ -162,39 +175,40 @@ _Py_NegativeRefcount(const char *filename, int lineno, PyObject *op) void _Py_IncRefTotal_DO_NOT_USE_THIS(void) { - reftotal_increment(); + reftotal_increment(&_PyRuntime); } /* This is used strictly by Py_DECREF(). */ void _Py_DecRefTotal_DO_NOT_USE_THIS(void) { - reftotal_decrement(); + reftotal_decrement(&_PyRuntime); } void _Py_IncRefTotal(void) { - reftotal_increment(); + reftotal_increment(&_PyRuntime); } void _Py_DecRefTotal(void) { - reftotal_decrement(); + reftotal_decrement(&_PyRuntime); } void _Py_AddRefTotal(Py_ssize_t n) { - reftotal_add(n); + reftotal_add(&_PyRuntime, n); } -/* This includes the legacy total. */ +/* This includes the legacy total + and any carried over from the last runtime init/fini cycle. */ Py_ssize_t _Py_GetGlobalRefTotal(void) { - return get_global_reftotal(); + return get_global_reftotal(&_PyRuntime); } Py_ssize_t @@ -2106,7 +2120,7 @@ void _Py_NewReference(PyObject *op) { #ifdef Py_REF_DEBUG - reftotal_increment(); + reftotal_increment(&_PyRuntime); #endif new_reference(op); } diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index e80dd30c89dfd0..4dc957b2f50a5c 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -1930,6 +1930,7 @@ Py_FinalizeEx(void) if (show_ref_count) { _PyDebug_PrintTotalRefs(); } + _Py_SetFinalRefTotal(runtime); #endif #ifdef Py_TRACE_REFS From 136be3a34dd359d0d21cd30f5b44efb6acd3c8e6 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 17 Mar 2023 14:02:10 -0600 Subject: [PATCH 12/18] Avoid double-counting the final reftotal. --- Include/internal/pycore_object.h | 2 +- Objects/object.c | 3 ++- Python/pylifecycle.c | 2 +- Python/pystate.c | 3 +++ 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h index 9ca88264a29b12..15cf3b25914f17 100644 --- a/Include/internal/pycore_object.h +++ b/Include/internal/pycore_object.h @@ -226,7 +226,7 @@ static inline void _PyObject_GC_UNTRACK( #endif #ifdef Py_REF_DEBUG -extern void _Py_SetFinalRefTotal(_PyRuntimeState *); +extern void _Py_ClearRefTotal(_PyRuntimeState *); extern void _PyDebug_PrintTotalRefs(void); #endif diff --git a/Objects/object.c b/Objects/object.c index 75f0e4c269ca08..f03d57972f27fe 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -93,9 +93,10 @@ reftotal_add(_PyRuntimeState *runtime, Py_ssize_t n) static Py_ssize_t last_final_reftotal = 0; void -_Py_SetFinalRefTotal(_PyRuntimeState *runtime) +_Py_ClearRefTotal(_PyRuntimeState *runtime) { last_final_reftotal += REFTOTAL; + REFTOTAL = 0; } static inline Py_ssize_t diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 4dc957b2f50a5c..3e7371fef94568 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -1930,7 +1930,7 @@ Py_FinalizeEx(void) if (show_ref_count) { _PyDebug_PrintTotalRefs(); } - _Py_SetFinalRefTotal(runtime); + _Py_ClearRefTotal(runtime); #endif #ifdef Py_TRACE_REFS diff --git a/Python/pystate.c b/Python/pystate.c index 28606e4f32f71c..c8ac1a67a287e7 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -506,6 +506,9 @@ _PyRuntimeState_Fini(_PyRuntimeState *runtime) #undef FREE_LOCK PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc); + + /* The reftotal is cleared by _Py_ClearRefTotal(). */ + assert(runtime->object_state.reftotal == 0); } #ifdef HAVE_FORK From 0abec9c7cecc0d52e8d318fdaddca24466eb1811 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 17 Mar 2023 14:47:12 -0600 Subject: [PATCH 13/18] Factor out sync_legacy_reftotal(). --- Objects/object.c | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/Objects/object.c b/Objects/object.c index f03d57972f27fe..1f7afdcec14e15 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -26,6 +26,9 @@ extern "C" { #endif +#define OBJSTATE(runtime) \ + (runtime)->object_state + /* Defined in tracemalloc.c */ extern void _PyMem_DumpTraceback(int fd, const void *ptr); @@ -57,16 +60,25 @@ _PyObject_CheckConsistency(PyObject *op, int check_content) /* We keep the legacy symbol around for backward compatibility. */ Py_ssize_t _Py_RefTotal; +static inline void +sync_legacy_reftotal(_PyRuntimeState *runtime) +{ + Py_ssize_t last = OBJSTATE(runtime).last_legacy_reftotal; + OBJSTATE(runtime).last_legacy_reftotal = _Py_RefTotal; + OBJSTATE(runtime).reftotal += _Py_RefTotal - last; +} + static inline Py_ssize_t -get_legacy_reftotal(void) +get_legacy_reftotal(_PyRuntimeState *runtime) { + sync_legacy_reftotal(runtime); return _Py_RefTotal; } #endif #ifdef Py_REF_DEBUG -# define REFTOTAL ((runtime)->object_state.reftotal) +# define REFTOTAL (OBJSTATE(runtime).reftotal) static inline void reftotal_increment(_PyRuntimeState *runtime) @@ -86,6 +98,8 @@ reftotal_add(_PyRuntimeState *runtime, Py_ssize_t n) REFTOTAL += n; } +static inline Py_ssize_t get_global_reftotal(_PyRuntimeState *); + /* We preserve the number of refs leaked during runtime finalization, so they can be reported if the runtime is initialized again. */ // XXX We don't lose any information by dropping this, @@ -95,17 +109,16 @@ static Py_ssize_t last_final_reftotal = 0; void _Py_ClearRefTotal(_PyRuntimeState *runtime) { - last_final_reftotal += REFTOTAL; + last_final_reftotal += get_global_reftotal(runtime); REFTOTAL = 0; + _Py_RefTotal = 0; } static inline Py_ssize_t get_global_reftotal(_PyRuntimeState *runtime) { - Py_ssize_t last = _PyRuntime.object_state.last_legacy_reftotal; - Py_ssize_t legacy = get_legacy_reftotal(); - _PyRuntime.object_state.last_legacy_reftotal = legacy; - REFTOTAL += legacy - last; + /* For an update from _Py_RefTotal first. */ + sync_legacy_reftotal(runtime); return REFTOTAL + last_final_reftotal; } @@ -215,7 +228,7 @@ _Py_GetGlobalRefTotal(void) Py_ssize_t _Py_GetLegacyRefTotal(void) { - return get_legacy_reftotal(); + return get_legacy_reftotal(&_PyRuntime); } #endif /* Py_REF_DEBUG */ From 6e4425cfdc1eb562722775e099a9dc87b511fac8 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 17 Mar 2023 15:25:50 -0600 Subject: [PATCH 14/18] Drop last_legacy_reftotal. --- Include/internal/pycore_object_state.h | 1 - Objects/object.c | 18 ++++-------------- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/Include/internal/pycore_object_state.h b/Include/internal/pycore_object_state.h index 61f7abf2002537..4e5862a11eddc5 100644 --- a/Include/internal/pycore_object_state.h +++ b/Include/internal/pycore_object_state.h @@ -10,7 +10,6 @@ extern "C" { struct _py_object_runtime_state { #ifdef Py_REF_DEBUG - Py_ssize_t last_legacy_reftotal; Py_ssize_t reftotal; #else int _not_used; diff --git a/Objects/object.c b/Objects/object.c index 1f7afdcec14e15..cbdaffc731318e 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -60,18 +60,9 @@ _PyObject_CheckConsistency(PyObject *op, int check_content) /* We keep the legacy symbol around for backward compatibility. */ Py_ssize_t _Py_RefTotal; -static inline void -sync_legacy_reftotal(_PyRuntimeState *runtime) -{ - Py_ssize_t last = OBJSTATE(runtime).last_legacy_reftotal; - OBJSTATE(runtime).last_legacy_reftotal = _Py_RefTotal; - OBJSTATE(runtime).reftotal += _Py_RefTotal - last; -} - static inline Py_ssize_t -get_legacy_reftotal(_PyRuntimeState *runtime) +get_legacy_reftotal(void) { - sync_legacy_reftotal(runtime); return _Py_RefTotal; } #endif @@ -111,15 +102,14 @@ _Py_ClearRefTotal(_PyRuntimeState *runtime) { last_final_reftotal += get_global_reftotal(runtime); REFTOTAL = 0; - _Py_RefTotal = 0; } static inline Py_ssize_t get_global_reftotal(_PyRuntimeState *runtime) { /* For an update from _Py_RefTotal first. */ - sync_legacy_reftotal(runtime); - return REFTOTAL + last_final_reftotal; + Py_ssize_t legacy = get_legacy_reftotal(); + return REFTOTAL + legacy + last_final_reftotal; } #undef REFTOTAL @@ -228,7 +218,7 @@ _Py_GetGlobalRefTotal(void) Py_ssize_t _Py_GetLegacyRefTotal(void) { - return get_legacy_reftotal(&_PyRuntime); + return get_legacy_reftotal(); } #endif /* Py_REF_DEBUG */ From 49f150233d4860a712b14e5806a01a241af62e72 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 17 Mar 2023 15:28:30 -0600 Subject: [PATCH 15/18] Fix the REFTOTAL macro. --- Objects/object.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/Objects/object.c b/Objects/object.c index cbdaffc731318e..3219b6134e0cc8 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -26,9 +26,6 @@ extern "C" { #endif -#define OBJSTATE(runtime) \ - (runtime)->object_state - /* Defined in tracemalloc.c */ extern void _PyMem_DumpTraceback(int fd, const void *ptr); @@ -69,24 +66,25 @@ get_legacy_reftotal(void) #ifdef Py_REF_DEBUG -# define REFTOTAL (OBJSTATE(runtime).reftotal) +# define REFTOTAL(runtime) \ + (runtime)->object_state.reftotal static inline void reftotal_increment(_PyRuntimeState *runtime) { - REFTOTAL++; + REFTOTAL(runtime)++; } static inline void reftotal_decrement(_PyRuntimeState *runtime) { - REFTOTAL--; + REFTOTAL(runtime)--; } static inline void reftotal_add(_PyRuntimeState *runtime, Py_ssize_t n) { - REFTOTAL += n; + REFTOTAL(runtime) += n; } static inline Py_ssize_t get_global_reftotal(_PyRuntimeState *); @@ -101,7 +99,7 @@ void _Py_ClearRefTotal(_PyRuntimeState *runtime) { last_final_reftotal += get_global_reftotal(runtime); - REFTOTAL = 0; + REFTOTAL(runtime) = 0; } static inline Py_ssize_t @@ -109,7 +107,7 @@ get_global_reftotal(_PyRuntimeState *runtime) { /* For an update from _Py_RefTotal first. */ Py_ssize_t legacy = get_legacy_reftotal(); - return REFTOTAL + legacy + last_final_reftotal; + return REFTOTAL(runtime) + legacy + last_final_reftotal; } #undef REFTOTAL From f874ae020e33cd0f80838ba0589af9ed7e584018 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 17 Mar 2023 15:42:59 -0600 Subject: [PATCH 16/18] _Py_ClearRefTotal() -> _Py_FinalizeRefTotal(). --- Include/internal/pycore_object.h | 2 +- Objects/object.c | 2 +- Python/pylifecycle.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Include/internal/pycore_object.h b/Include/internal/pycore_object.h index 15cf3b25914f17..f82eba48096dc6 100644 --- a/Include/internal/pycore_object.h +++ b/Include/internal/pycore_object.h @@ -226,7 +226,7 @@ static inline void _PyObject_GC_UNTRACK( #endif #ifdef Py_REF_DEBUG -extern void _Py_ClearRefTotal(_PyRuntimeState *); +extern void _Py_FinalizeRefTotal(_PyRuntimeState *); extern void _PyDebug_PrintTotalRefs(void); #endif diff --git a/Objects/object.c b/Objects/object.c index 3219b6134e0cc8..9bfab4b4a311ee 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -96,7 +96,7 @@ static inline Py_ssize_t get_global_reftotal(_PyRuntimeState *); static Py_ssize_t last_final_reftotal = 0; void -_Py_ClearRefTotal(_PyRuntimeState *runtime) +_Py_FinalizeRefTotal(_PyRuntimeState *runtime) { last_final_reftotal += get_global_reftotal(runtime); REFTOTAL(runtime) = 0; diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 3e7371fef94568..f84582744b8c47 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -1930,7 +1930,7 @@ Py_FinalizeEx(void) if (show_ref_count) { _PyDebug_PrintTotalRefs(); } - _Py_ClearRefTotal(runtime); + _Py_FinalizeRefTotal(runtime); #endif #ifdef Py_TRACE_REFS From 2fb32594d355cae2afa2ada130f1533cdb889274 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 17 Mar 2023 15:44:59 -0600 Subject: [PATCH 17/18] Add a new global to the ingored list. --- Tools/c-analyzer/cpython/ignored.tsv | 1 + 1 file changed, 1 insertion(+) diff --git a/Tools/c-analyzer/cpython/ignored.tsv b/Tools/c-analyzer/cpython/ignored.tsv index f3e60c159da429..a70a42da69fd0a 100644 --- a/Tools/c-analyzer/cpython/ignored.tsv +++ b/Tools/c-analyzer/cpython/ignored.tsv @@ -299,6 +299,7 @@ Objects/genobject.c - NON_INIT_CORO_MSG - Objects/longobject.c - _PyLong_DigitValue - Objects/object.c - _Py_SwappedOp - Objects/object.c - _Py_abstract_hack - +Objects/object.c - last_final_reftotal - Objects/object.c - static_types - Objects/obmalloc.c - _PyMem - Objects/obmalloc.c - _PyMem_Debug - From 2a11934d05632208839c21e0aae2afa45feb187c Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 17 Mar 2023 16:23:17 -0600 Subject: [PATCH 18/18] Fix _Py_FinalizeRefTotal(). --- Objects/object.c | 2 +- Python/pystate.c | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Objects/object.c b/Objects/object.c index 9bfab4b4a311ee..1994e934cc12e6 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -98,7 +98,7 @@ static Py_ssize_t last_final_reftotal = 0; void _Py_FinalizeRefTotal(_PyRuntimeState *runtime) { - last_final_reftotal += get_global_reftotal(runtime); + last_final_reftotal = get_global_reftotal(runtime); REFTOTAL(runtime) = 0; } diff --git a/Python/pystate.c b/Python/pystate.c index c8ac1a67a287e7..bc955b75d0352c 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -482,6 +482,9 @@ _PyRuntimeState_Init(_PyRuntimeState *runtime) void _PyRuntimeState_Fini(_PyRuntimeState *runtime) { + /* The reftotal is cleared by _Py_FinalizeRefTotal(). */ + assert(runtime->object_state.reftotal == 0); + if (gilstate_tss_initialized(runtime)) { gilstate_tss_fini(runtime); } @@ -506,9 +509,6 @@ _PyRuntimeState_Fini(_PyRuntimeState *runtime) #undef FREE_LOCK PyMem_SetAllocator(PYMEM_DOMAIN_RAW, &old_alloc); - - /* The reftotal is cleared by _Py_ClearRefTotal(). */ - assert(runtime->object_state.reftotal == 0); } #ifdef HAVE_FORK