From e53e0a0269cd749f282fa1f7089474cd0d7a5aff Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Thu, 4 Jan 2024 19:36:11 +0000 Subject: [PATCH 1/5] Move Modules/gcmodule.c to Python/gc.c --- Modules/gcmodule.c => Python/gc.c | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Modules/gcmodule.c => Python/gc.c (100%) diff --git a/Modules/gcmodule.c b/Python/gc.c similarity index 100% rename from Modules/gcmodule.c rename to Python/gc.c From fa068b5b9ca51e75d3bedf7331b8aca43b6638b1 Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Thu, 4 Jan 2024 19:38:53 +0000 Subject: [PATCH 2/5] gh-113688: Split up gcmodule.c This splits part of Modules/gcmodule.c of into Python/gc.c, which now contains the core garbage collection implementation. The Python module remain in the Modules/gcmodule.c file. --- Include/internal/pycore_gc.h | 31 ++ Lib/test/test_gc.py | 2 +- Makefile.pre.in | 1 + Modules/gcmodule.c | 518 ++++++++++++++++++++++ PCbuild/_freeze_module.vcxproj | 1 + PCbuild/_freeze_module.vcxproj.filters | 3 + PCbuild/pythoncore.vcxproj | 1 + PCbuild/pythoncore.vcxproj.filters | 3 + Python/gc.c | 567 ++----------------------- 9 files changed, 600 insertions(+), 527 deletions(-) create mode 100644 Modules/gcmodule.c diff --git a/Include/internal/pycore_gc.h b/Include/internal/pycore_gc.h index 2d33aa76d78229..2a79c403803ed1 100644 --- a/Include/internal/pycore_gc.h +++ b/Include/internal/pycore_gc.h @@ -64,6 +64,26 @@ static inline int _PyObject_GC_MAY_BE_TRACKED(PyObject *obj) { #define _PyGC_PREV_SHIFT (2) #define _PyGC_PREV_MASK (((uintptr_t) -1) << _PyGC_PREV_SHIFT) +/* set for debugging information */ +#define _PyGC_DEBUG_STATS (1<<0) /* print collection statistics */ +#define _PyGC_DEBUG_COLLECTABLE (1<<1) /* print collectable objects */ +#define _PyGC_DEBUG_UNCOLLECTABLE (1<<2) /* print uncollectable objects */ +#define _PyGC_DEBUG_SAVEALL (1<<5) /* save all garbage in gc.garbage */ +#define _PyGC_DEBUG_LEAK _PyGC_DEBUG_COLLECTABLE | \ + _PyGC_DEBUG_UNCOLLECTABLE | \ + _PyGC_DEBUG_SAVEALL + +typedef enum { + // GC was triggered by heap allocation + _Py_GC_REASON_HEAP, + + // GC was called during shutdown + _Py_GC_REASON_SHUTDOWN, + + // GC was called by gc.collect() or PyGC_Collect() + _Py_GC_REASON_MANUAL +} _PyGC_Reason; + // Lowest bit of _gc_next is used for flags only in GC. // But it is always 0 for normal code. static inline PyGC_Head* _PyGCHead_NEXT(PyGC_Head *gc) { @@ -203,8 +223,19 @@ struct _gc_runtime_state { extern void _PyGC_InitState(struct _gc_runtime_state *); +extern Py_ssize_t _PyGC_Collect(PyThreadState *tstate, int generation, + _PyGC_Reason reason); extern Py_ssize_t _PyGC_CollectNoFail(PyThreadState *tstate); +/* Freeze objects tracked by the GC and ignore them in future collections. */ +extern void _PyGC_Freeze(PyInterpreterState *interp); +/* Unfreezes objects placing them in the oldest generation */ +extern void _PyGC_Unfreeze(PyInterpreterState *interp); +/* Number of frozen objects */ +extern Py_ssize_t _PyGC_GetFreezeCount(PyInterpreterState *interp); + +extern PyObject *_PyGC_GetObjects(PyInterpreterState *interp, Py_ssize_t generation); +extern PyObject *_PyGC_GetReferrers(PyInterpreterState *interp, PyObject *objs); // Functions to clear types free lists extern void _PyTuple_ClearFreeList(PyInterpreterState *interp); diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py index db7cb9ace6e5f3..1d71dd9e262a6a 100644 --- a/Lib/test/test_gc.py +++ b/Lib/test/test_gc.py @@ -1225,7 +1225,7 @@ def test_refcount_errors(self): p.stderr.close() # Verify that stderr has a useful error message: self.assertRegex(stderr, - br'gcmodule\.c:[0-9]+: gc_decref: Assertion "gc_get_refs\(g\) > 0" failed.') + br'gc\.c:[0-9]+: gc_decref: Assertion "gc_get_refs\(g\) > 0" failed.') self.assertRegex(stderr, br'refcount is too small') # "address : 0x7fb5062efc18" diff --git a/Makefile.pre.in b/Makefile.pre.in index 6a64547e97d266..44e8e4bd3f9557 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -417,6 +417,7 @@ PYTHON_OBJS= \ Python/frame.o \ Python/frozenmain.o \ Python/future.o \ + Python/gc.o \ Python/getargs.o \ Python/getcompiler.o \ Python/getcopyright.o \ diff --git a/Modules/gcmodule.c b/Modules/gcmodule.c new file mode 100644 index 00000000000000..9a827cb79d73ab --- /dev/null +++ b/Modules/gcmodule.c @@ -0,0 +1,518 @@ +/* + * Python interface to the garbage collector. + * + * See Python/gc.c for the implementation of the garbage collector. + */ + +#include "Python.h" +#include "pycore_gc.h" +#include "pycore_object.h" // _PyObject_IS_GC() +#include "pycore_pystate.h" // _PyInterpreterState_GET() + +typedef struct _gc_runtime_state GCState; + +static GCState * +get_gc_state(void) +{ + PyInterpreterState *interp = _PyInterpreterState_GET(); + return &interp->gc; +} + +/*[clinic input] +module gc +[clinic start generated code]*/ +/*[clinic end generated code: output=da39a3ee5e6b4b0d input=b5c9690ecc842d79]*/ +#include "clinic/gcmodule.c.h" + +/*[clinic input] +gc.enable + +Enable automatic garbage collection. +[clinic start generated code]*/ + +static PyObject * +gc_enable_impl(PyObject *module) +/*[clinic end generated code: output=45a427e9dce9155c input=81ac4940ca579707]*/ +{ + PyGC_Enable(); + Py_RETURN_NONE; +} + +/*[clinic input] +gc.disable + +Disable automatic garbage collection. +[clinic start generated code]*/ + +static PyObject * +gc_disable_impl(PyObject *module) +/*[clinic end generated code: output=97d1030f7aa9d279 input=8c2e5a14e800d83b]*/ +{ + PyGC_Disable(); + Py_RETURN_NONE; +} + +/*[clinic input] +gc.isenabled -> bool + +Returns true if automatic garbage collection is enabled. +[clinic start generated code]*/ + +static int +gc_isenabled_impl(PyObject *module) +/*[clinic end generated code: output=1874298331c49130 input=30005e0422373b31]*/ +{ + return PyGC_IsEnabled(); +} + +/*[clinic input] +gc.collect -> Py_ssize_t + + generation: int(c_default="NUM_GENERATIONS - 1") = 2 + +Run the garbage collector. + +With no arguments, run a full collection. The optional argument +may be an integer specifying which generation to collect. A ValueError +is raised if the generation number is invalid. + +The number of unreachable objects is returned. +[clinic start generated code]*/ + +static Py_ssize_t +gc_collect_impl(PyObject *module, int generation) +/*[clinic end generated code: output=b697e633043233c7 input=40720128b682d879]*/ +{ + PyThreadState *tstate = _PyThreadState_GET(); + + if (generation < 0 || generation >= NUM_GENERATIONS) { + _PyErr_SetString(tstate, PyExc_ValueError, "invalid generation"); + return -1; + } + + return _PyGC_Collect(tstate, generation, _Py_GC_REASON_MANUAL); +} + +/*[clinic input] +gc.set_debug + + flags: int + An integer that can have the following bits turned on: + DEBUG_STATS - Print statistics during collection. + DEBUG_COLLECTABLE - Print collectable objects found. + DEBUG_UNCOLLECTABLE - Print unreachable but uncollectable objects + found. + DEBUG_SAVEALL - Save objects to gc.garbage rather than freeing them. + DEBUG_LEAK - Debug leaking programs (everything but STATS). + / + +Set the garbage collection debugging flags. + +Debugging information is written to sys.stderr. +[clinic start generated code]*/ + +static PyObject * +gc_set_debug_impl(PyObject *module, int flags) +/*[clinic end generated code: output=7c8366575486b228 input=5e5ce15e84fbed15]*/ +{ + GCState *gcstate = get_gc_state(); + gcstate->debug = flags; + Py_RETURN_NONE; +} + +/*[clinic input] +gc.get_debug -> int + +Get the garbage collection debugging flags. +[clinic start generated code]*/ + +static int +gc_get_debug_impl(PyObject *module) +/*[clinic end generated code: output=91242f3506cd1e50 input=91a101e1c3b98366]*/ +{ + GCState *gcstate = get_gc_state(); + return gcstate->debug; +} + +PyDoc_STRVAR(gc_set_thresh__doc__, +"set_threshold(threshold0, [threshold1, threshold2]) -> None\n" +"\n" +"Sets the collection thresholds. Setting threshold0 to zero disables\n" +"collection.\n"); + +static PyObject * +gc_set_threshold(PyObject *self, PyObject *args) +{ + GCState *gcstate = get_gc_state(); + if (!PyArg_ParseTuple(args, "i|ii:set_threshold", + &gcstate->generations[0].threshold, + &gcstate->generations[1].threshold, + &gcstate->generations[2].threshold)) + return NULL; + for (int i = 3; i < NUM_GENERATIONS; i++) { + /* generations higher than 2 get the same threshold */ + gcstate->generations[i].threshold = gcstate->generations[2].threshold; + } + Py_RETURN_NONE; +} + +/*[clinic input] +gc.get_threshold + +Return the current collection thresholds. +[clinic start generated code]*/ + +static PyObject * +gc_get_threshold_impl(PyObject *module) +/*[clinic end generated code: output=7902bc9f41ecbbd8 input=286d79918034d6e6]*/ +{ + GCState *gcstate = get_gc_state(); + return Py_BuildValue("(iii)", + gcstate->generations[0].threshold, + gcstate->generations[1].threshold, + gcstate->generations[2].threshold); +} + +/*[clinic input] +gc.get_count + +Return a three-tuple of the current collection counts. +[clinic start generated code]*/ + +static PyObject * +gc_get_count_impl(PyObject *module) +/*[clinic end generated code: output=354012e67b16398f input=a392794a08251751]*/ +{ + GCState *gcstate = get_gc_state(); + return Py_BuildValue("(iii)", + gcstate->generations[0].count, + gcstate->generations[1].count, + gcstate->generations[2].count); +} + +PyDoc_STRVAR(gc_get_referrers__doc__, +"get_referrers(*objs) -> list\n\ +Return the list of objects that directly refer to any of objs."); + +static PyObject * +gc_get_referrers(PyObject *self, PyObject *args) +{ + if (PySys_Audit("gc.get_referrers", "(O)", args) < 0) { + return NULL; + } + + PyInterpreterState *interp = _PyInterpreterState_GET(); + return _PyGC_GetReferrers(interp, args); +} + +/* Append obj to list; return true if error (out of memory), false if OK. */ +static int +referentsvisit(PyObject *obj, void *arg) +{ + PyObject *list = arg; + return PyList_Append(list, obj) < 0; +} + +PyDoc_STRVAR(gc_get_referents__doc__, +"get_referents(*objs) -> list\n\ +Return the list of objects that are directly referred to by objs."); + +static PyObject * +gc_get_referents(PyObject *self, PyObject *args) +{ + Py_ssize_t i; + if (PySys_Audit("gc.get_referents", "(O)", args) < 0) { + return NULL; + } + PyObject *result = PyList_New(0); + + if (result == NULL) + return NULL; + + for (i = 0; i < PyTuple_GET_SIZE(args); i++) { + traverseproc traverse; + PyObject *obj = PyTuple_GET_ITEM(args, i); + + if (!_PyObject_IS_GC(obj)) + continue; + traverse = Py_TYPE(obj)->tp_traverse; + if (! traverse) + continue; + if (traverse(obj, referentsvisit, result)) { + Py_DECREF(result); + return NULL; + } + } + return result; +} + +/*[clinic input] +gc.get_objects + generation: Py_ssize_t(accept={int, NoneType}, c_default="-1") = None + Generation to extract the objects from. + +Return a list of objects tracked by the collector (excluding the list returned). + +If generation is not None, return only the objects tracked by the collector +that are in that generation. +[clinic start generated code]*/ + +static PyObject * +gc_get_objects_impl(PyObject *module, Py_ssize_t generation) +/*[clinic end generated code: output=48b35fea4ba6cb0e input=ef7da9df9806754c]*/ +{ + if (PySys_Audit("gc.get_objects", "n", generation) < 0) { + return NULL; + } + + if (generation >= NUM_GENERATIONS) { + return PyErr_Format(PyExc_ValueError, + "generation parameter must be less than the number of " + "available generations (%i)", + NUM_GENERATIONS); + } + + if (generation < -1) { + PyErr_SetString(PyExc_ValueError, + "generation parameter cannot be negative"); + return NULL; + } + + PyInterpreterState *interp = _PyInterpreterState_GET(); + return _PyGC_GetObjects(interp, generation); +} + +/*[clinic input] +gc.get_stats + +Return a list of dictionaries containing per-generation statistics. +[clinic start generated code]*/ + +static PyObject * +gc_get_stats_impl(PyObject *module) +/*[clinic end generated code: output=a8ab1d8a5d26f3ab input=1ef4ed9d17b1a470]*/ +{ + int i; + struct gc_generation_stats stats[NUM_GENERATIONS], *st; + + /* To get consistent values despite allocations while constructing + the result list, we use a snapshot of the running stats. */ + GCState *gcstate = get_gc_state(); + for (i = 0; i < NUM_GENERATIONS; i++) { + stats[i] = gcstate->generation_stats[i]; + } + + PyObject *result = PyList_New(0); + if (result == NULL) + return NULL; + + for (i = 0; i < NUM_GENERATIONS; i++) { + PyObject *dict; + st = &stats[i]; + dict = Py_BuildValue("{snsnsn}", + "collections", st->collections, + "collected", st->collected, + "uncollectable", st->uncollectable + ); + if (dict == NULL) + goto error; + if (PyList_Append(result, dict)) { + Py_DECREF(dict); + goto error; + } + Py_DECREF(dict); + } + return result; + +error: + Py_XDECREF(result); + return NULL; +} + + +/*[clinic input] +gc.is_tracked + + obj: object + / + +Returns true if the object is tracked by the garbage collector. + +Simple atomic objects will return false. +[clinic start generated code]*/ + +static PyObject * +gc_is_tracked(PyObject *module, PyObject *obj) +/*[clinic end generated code: output=14f0103423b28e31 input=d83057f170ea2723]*/ +{ + PyObject *result; + + if (_PyObject_IS_GC(obj) && _PyObject_GC_IS_TRACKED(obj)) + result = Py_True; + else + result = Py_False; + return Py_NewRef(result); +} + +/*[clinic input] +gc.is_finalized + + obj: object + / + +Returns true if the object has been already finalized by the GC. +[clinic start generated code]*/ + +static PyObject * +gc_is_finalized(PyObject *module, PyObject *obj) +/*[clinic end generated code: output=e1516ac119a918ed input=201d0c58f69ae390]*/ +{ + if (_PyObject_IS_GC(obj) && _PyGC_FINALIZED(obj)) { + Py_RETURN_TRUE; + } + Py_RETURN_FALSE; +} + +/*[clinic input] +gc.freeze + +Freeze all current tracked objects and ignore them for future collections. + +This can be used before a POSIX fork() call to make the gc copy-on-write friendly. +Note: collection before a POSIX fork() call may free pages for future allocation +which can cause copy-on-write. +[clinic start generated code]*/ + +static PyObject * +gc_freeze_impl(PyObject *module) +/*[clinic end generated code: output=502159d9cdc4c139 input=b602b16ac5febbe5]*/ +{ + PyInterpreterState *interp = _PyInterpreterState_GET(); + _PyGC_Freeze(interp); + Py_RETURN_NONE; +} + +/*[clinic input] +gc.unfreeze + +Unfreeze all objects in the permanent generation. + +Put all objects in the permanent generation back into oldest generation. +[clinic start generated code]*/ + +static PyObject * +gc_unfreeze_impl(PyObject *module) +/*[clinic end generated code: output=1c15f2043b25e169 input=2dd52b170f4cef6c]*/ +{ + PyInterpreterState *interp = _PyInterpreterState_GET(); + _PyGC_Unfreeze(interp); + Py_RETURN_NONE; +} + +/*[clinic input] +gc.get_freeze_count -> Py_ssize_t + +Return the number of objects in the permanent generation. +[clinic start generated code]*/ + +static Py_ssize_t +gc_get_freeze_count_impl(PyObject *module) +/*[clinic end generated code: output=61cbd9f43aa032e1 input=45ffbc65cfe2a6ed]*/ +{ + PyInterpreterState *interp = _PyInterpreterState_GET(); + return _PyGC_GetFreezeCount(interp); +} + + +PyDoc_STRVAR(gc__doc__, +"This module provides access to the garbage collector for reference cycles.\n" +"\n" +"enable() -- Enable automatic garbage collection.\n" +"disable() -- Disable automatic garbage collection.\n" +"isenabled() -- Returns true if automatic collection is enabled.\n" +"collect() -- Do a full collection right now.\n" +"get_count() -- Return the current collection counts.\n" +"get_stats() -- Return list of dictionaries containing per-generation stats.\n" +"set_debug() -- Set debugging flags.\n" +"get_debug() -- Get debugging flags.\n" +"set_threshold() -- Set the collection thresholds.\n" +"get_threshold() -- Return the current the collection thresholds.\n" +"get_objects() -- Return a list of all objects tracked by the collector.\n" +"is_tracked() -- Returns true if a given object is tracked.\n" +"is_finalized() -- Returns true if a given object has been already finalized.\n" +"get_referrers() -- Return the list of objects that refer to an object.\n" +"get_referents() -- Return the list of objects that an object refers to.\n" +"freeze() -- Freeze all tracked objects and ignore them for future collections.\n" +"unfreeze() -- Unfreeze all objects in the permanent generation.\n" +"get_freeze_count() -- Return the number of objects in the permanent generation.\n"); + +static PyMethodDef GcMethods[] = { + GC_ENABLE_METHODDEF + GC_DISABLE_METHODDEF + GC_ISENABLED_METHODDEF + GC_SET_DEBUG_METHODDEF + GC_GET_DEBUG_METHODDEF + GC_GET_COUNT_METHODDEF + {"set_threshold", gc_set_threshold, METH_VARARGS, gc_set_thresh__doc__}, + GC_GET_THRESHOLD_METHODDEF + GC_COLLECT_METHODDEF + GC_GET_OBJECTS_METHODDEF + GC_GET_STATS_METHODDEF + GC_IS_TRACKED_METHODDEF + GC_IS_FINALIZED_METHODDEF + {"get_referrers", gc_get_referrers, METH_VARARGS, + gc_get_referrers__doc__}, + {"get_referents", gc_get_referents, METH_VARARGS, + gc_get_referents__doc__}, + GC_FREEZE_METHODDEF + GC_UNFREEZE_METHODDEF + GC_GET_FREEZE_COUNT_METHODDEF + {NULL, NULL} /* Sentinel */ +}; + +static int +gcmodule_exec(PyObject *module) +{ + GCState *gcstate = get_gc_state(); + + /* garbage and callbacks are initialized by _PyGC_Init() early in + * interpreter lifecycle. */ + assert(gcstate->garbage != NULL); + if (PyModule_AddObjectRef(module, "garbage", gcstate->garbage) < 0) { + return -1; + } + assert(gcstate->callbacks != NULL); + if (PyModule_AddObjectRef(module, "callbacks", gcstate->callbacks) < 0) { + return -1; + } + +#define ADD_INT(NAME) if (PyModule_AddIntConstant(module, #NAME, _PyGC_ ## NAME) < 0) { return -1; } + ADD_INT(DEBUG_STATS); + ADD_INT(DEBUG_COLLECTABLE); + ADD_INT(DEBUG_UNCOLLECTABLE); + ADD_INT(DEBUG_SAVEALL); + ADD_INT(DEBUG_LEAK); +#undef ADD_INT + return 0; +} + +static PyModuleDef_Slot gcmodule_slots[] = { + {Py_mod_exec, gcmodule_exec}, + {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, + {0, NULL} +}; + +static struct PyModuleDef gcmodule = { + PyModuleDef_HEAD_INIT, + .m_name = "gc", + .m_doc = gc__doc__, + .m_size = 0, // per interpreter state, see: get_gc_state() + .m_methods = GcMethods, + .m_slots = gcmodule_slots +}; + +PyMODINIT_FUNC +PyInit_gc(void) +{ + return PyModuleDef_Init(&gcmodule); +} diff --git a/PCbuild/_freeze_module.vcxproj b/PCbuild/_freeze_module.vcxproj index 292bfa76519507..f16a763772e42e 100644 --- a/PCbuild/_freeze_module.vcxproj +++ b/PCbuild/_freeze_module.vcxproj @@ -207,6 +207,7 @@ + diff --git a/PCbuild/_freeze_module.vcxproj.filters b/PCbuild/_freeze_module.vcxproj.filters index 1c5a6d623f4dad..7f03cfea1b3e6f 100644 --- a/PCbuild/_freeze_module.vcxproj.filters +++ b/PCbuild/_freeze_module.vcxproj.filters @@ -166,6 +166,9 @@ Source Files + + Source Files + Source Files diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj index be5b34220aa0bc..163adfdc51c6a8 100644 --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -566,6 +566,7 @@ $(GeneratedFrozenModulesDir)Python;%(AdditionalIncludeDirectories) + diff --git a/PCbuild/pythoncore.vcxproj.filters b/PCbuild/pythoncore.vcxproj.filters index a96ca24cf08b66..a45a0881f7113d 100644 --- a/PCbuild/pythoncore.vcxproj.filters +++ b/PCbuild/pythoncore.vcxproj.filters @@ -1280,6 +1280,9 @@ Python + + Python + Python diff --git a/Python/gc.c b/Python/gc.c index 2d1f381e622226..04a686e50ea331 100644 --- a/Python/gc.c +++ b/Python/gc.c @@ -37,12 +37,6 @@ typedef struct _gc_runtime_state GCState; -/*[clinic input] -module gc -[clinic start generated code]*/ -/*[clinic end generated code: output=da39a3ee5e6b4b0d input=b5c9690ecc842d79]*/ - - #ifdef Py_DEBUG # define GC_DEBUG #endif @@ -77,18 +71,6 @@ module gc // Automatically choose the generation that needs collecting. #define GENERATION_AUTO (-1) -typedef enum { - // GC was triggered by heap allocation - _Py_GC_REASON_HEAP, - - // GC was called during shutdown - _Py_GC_REASON_SHUTDOWN, - - // GC was called by gc.collect() or PyGC_Collect() - _Py_GC_REASON_MANUAL -} _PyGC_Reason; - - static inline int gc_is_collecting(PyGC_Head *g) { @@ -131,14 +113,6 @@ gc_decref(PyGC_Head *g) g->_gc_prev -= 1 << _PyGC_PREV_SHIFT; } -/* set for debugging information */ -#define DEBUG_STATS (1<<0) /* print collection statistics */ -#define DEBUG_COLLECTABLE (1<<1) /* print collectable objects */ -#define DEBUG_UNCOLLECTABLE (1<<2) /* print uncollectable objects */ -#define DEBUG_SAVEALL (1<<5) /* save all garbage in gc.garbage */ -#define DEBUG_LEAK DEBUG_COLLECTABLE | \ - DEBUG_UNCOLLECTABLE | \ - DEBUG_SAVEALL #define GEN_HEAD(gcstate, n) (&(gcstate)->generations[n].head) @@ -955,7 +929,7 @@ debug_cycle(const char *msg, PyObject *op) /* Handle uncollectable garbage (cycles with tp_del slots, and stuff reachable * only from such cycles). - * If DEBUG_SAVEALL, all objects in finalizers are appended to the module + * If _PyGC_DEBUG_SAVEALL, all objects in finalizers are appended to the module * garbage list (a Python list), else only the objects in finalizers with * __del__ methods are appended to garbage. All objects in finalizers are * merged into the old list regardless. @@ -972,7 +946,7 @@ handle_legacy_finalizers(PyThreadState *tstate, for (; gc != finalizers; gc = GC_NEXT(gc)) { PyObject *op = FROM_GC(gc); - if ((gcstate->debug & DEBUG_SAVEALL) || has_legacy_finalizer(op)) { + if ((gcstate->debug & _PyGC_DEBUG_SAVEALL) || has_legacy_finalizer(op)) { if (PyList_Append(gcstate->garbage, op) < 0) { _PyErr_Clear(tstate); break; @@ -1036,7 +1010,7 @@ delete_garbage(PyThreadState *tstate, GCState *gcstate, _PyObject_ASSERT_WITH_MSG(op, Py_REFCNT(op) > 0, "refcount is too small"); - if (gcstate->debug & DEBUG_SAVEALL) { + if (gcstate->debug & _PyGC_DEBUG_SAVEALL) { assert(gcstate->garbage != NULL); if (PyList_Append(gcstate->garbage, op) < 0) { _PyErr_Clear(tstate); @@ -1370,7 +1344,7 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason) invoke_gc_callback(tstate, "start", generation, 0, 0); } - if (gcstate->debug & DEBUG_STATS) { + if (gcstate->debug & _PyGC_DEBUG_STATS) { PySys_WriteStderr("gc: collecting generation %d...\n", generation); show_stats_each_generations(gcstate); t1 = _PyTime_GetPerfCounter(); @@ -1433,7 +1407,7 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason) validate_list(&unreachable, collecting_set_unreachable_clear); /* Print debugging information. */ - if (gcstate->debug & DEBUG_COLLECTABLE) { + if (gcstate->debug & _PyGC_DEBUG_COLLECTABLE) { for (gc = GC_NEXT(&unreachable); gc != &unreachable; gc = GC_NEXT(gc)) { debug_cycle("collectable", FROM_GC(gc)); } @@ -1465,10 +1439,10 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason) * debugging information. */ for (gc = GC_NEXT(&finalizers); gc != &finalizers; gc = GC_NEXT(gc)) { n++; - if (gcstate->debug & DEBUG_UNCOLLECTABLE) + if (gcstate->debug & _PyGC_DEBUG_UNCOLLECTABLE) debug_cycle("uncollectable", FROM_GC(gc)); } - if (gcstate->debug & DEBUG_STATS) { + if (gcstate->debug & _PyGC_DEBUG_STATS) { double d = _PyTime_AsSecondsDouble(_PyTime_GetPerfCounter() - t1); PySys_WriteStderr( "gc: done, %zd unreachable, %zd uncollectable, %.4fs elapsed\n", @@ -1525,174 +1499,6 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason) return n + m; } -#include "clinic/gcmodule.c.h" - -/*[clinic input] -gc.enable - -Enable automatic garbage collection. -[clinic start generated code]*/ - -static PyObject * -gc_enable_impl(PyObject *module) -/*[clinic end generated code: output=45a427e9dce9155c input=81ac4940ca579707]*/ -{ - PyGC_Enable(); - Py_RETURN_NONE; -} - -/*[clinic input] -gc.disable - -Disable automatic garbage collection. -[clinic start generated code]*/ - -static PyObject * -gc_disable_impl(PyObject *module) -/*[clinic end generated code: output=97d1030f7aa9d279 input=8c2e5a14e800d83b]*/ -{ - PyGC_Disable(); - Py_RETURN_NONE; -} - -/*[clinic input] -gc.isenabled -> bool - -Returns true if automatic garbage collection is enabled. -[clinic start generated code]*/ - -static int -gc_isenabled_impl(PyObject *module) -/*[clinic end generated code: output=1874298331c49130 input=30005e0422373b31]*/ -{ - return PyGC_IsEnabled(); -} - -/*[clinic input] -gc.collect -> Py_ssize_t - - generation: int(c_default="NUM_GENERATIONS - 1") = 2 - -Run the garbage collector. - -With no arguments, run a full collection. The optional argument -may be an integer specifying which generation to collect. A ValueError -is raised if the generation number is invalid. - -The number of unreachable objects is returned. -[clinic start generated code]*/ - -static Py_ssize_t -gc_collect_impl(PyObject *module, int generation) -/*[clinic end generated code: output=b697e633043233c7 input=40720128b682d879]*/ -{ - PyThreadState *tstate = _PyThreadState_GET(); - - if (generation < 0 || generation >= NUM_GENERATIONS) { - _PyErr_SetString(tstate, PyExc_ValueError, "invalid generation"); - return -1; - } - - return gc_collect_main(tstate, generation, _Py_GC_REASON_MANUAL); -} - -/*[clinic input] -gc.set_debug - - flags: int - An integer that can have the following bits turned on: - DEBUG_STATS - Print statistics during collection. - DEBUG_COLLECTABLE - Print collectable objects found. - DEBUG_UNCOLLECTABLE - Print unreachable but uncollectable objects - found. - DEBUG_SAVEALL - Save objects to gc.garbage rather than freeing them. - DEBUG_LEAK - Debug leaking programs (everything but STATS). - / - -Set the garbage collection debugging flags. - -Debugging information is written to sys.stderr. -[clinic start generated code]*/ - -static PyObject * -gc_set_debug_impl(PyObject *module, int flags) -/*[clinic end generated code: output=7c8366575486b228 input=5e5ce15e84fbed15]*/ -{ - GCState *gcstate = get_gc_state(); - gcstate->debug = flags; - Py_RETURN_NONE; -} - -/*[clinic input] -gc.get_debug -> int - -Get the garbage collection debugging flags. -[clinic start generated code]*/ - -static int -gc_get_debug_impl(PyObject *module) -/*[clinic end generated code: output=91242f3506cd1e50 input=91a101e1c3b98366]*/ -{ - GCState *gcstate = get_gc_state(); - return gcstate->debug; -} - -PyDoc_STRVAR(gc_set_thresh__doc__, -"set_threshold(threshold0, [threshold1, threshold2]) -> None\n" -"\n" -"Sets the collection thresholds. Setting threshold0 to zero disables\n" -"collection.\n"); - -static PyObject * -gc_set_threshold(PyObject *self, PyObject *args) -{ - GCState *gcstate = get_gc_state(); - if (!PyArg_ParseTuple(args, "i|ii:set_threshold", - &gcstate->generations[0].threshold, - &gcstate->generations[1].threshold, - &gcstate->generations[2].threshold)) - return NULL; - for (int i = 3; i < NUM_GENERATIONS; i++) { - /* generations higher than 2 get the same threshold */ - gcstate->generations[i].threshold = gcstate->generations[2].threshold; - } - Py_RETURN_NONE; -} - -/*[clinic input] -gc.get_threshold - -Return the current collection thresholds. -[clinic start generated code]*/ - -static PyObject * -gc_get_threshold_impl(PyObject *module) -/*[clinic end generated code: output=7902bc9f41ecbbd8 input=286d79918034d6e6]*/ -{ - GCState *gcstate = get_gc_state(); - return Py_BuildValue("(iii)", - gcstate->generations[0].threshold, - gcstate->generations[1].threshold, - gcstate->generations[2].threshold); -} - -/*[clinic input] -gc.get_count - -Return a three-tuple of the current collection counts. -[clinic start generated code]*/ - -static PyObject * -gc_get_count_impl(PyObject *module) -/*[clinic end generated code: output=354012e67b16398f input=a392794a08251751]*/ -{ - GCState *gcstate = get_gc_state(); - return Py_BuildValue("(iii)", - gcstate->generations[0].count, - gcstate->generations[1].count, - gcstate->generations[2].count); -} - static int referrersvisit(PyObject* obj, void *arg) { @@ -1723,25 +1529,17 @@ gc_referrers_for(PyObject *objs, PyGC_Head *list, PyObject *resultlist) return 1; /* no error */ } -PyDoc_STRVAR(gc_get_referrers__doc__, -"get_referrers(*objs) -> list\n\ -Return the list of objects that directly refer to any of objs."); - -static PyObject * -gc_get_referrers(PyObject *self, PyObject *args) +PyObject * +_PyGC_GetReferrers(PyInterpreterState *interp, PyObject *objs) { - if (PySys_Audit("gc.get_referrers", "(O)", args) < 0) { - return NULL; - } - PyObject *result = PyList_New(0); if (!result) { return NULL; } - GCState *gcstate = get_gc_state(); + GCState *gcstate = &interp->gc; for (int i = 0; i < NUM_GENERATIONS; i++) { - if (!(gc_referrers_for(args, GEN_HEAD(gcstate, i), result))) { + if (!(gc_referrers_for(objs, GEN_HEAD(gcstate, i), result))) { Py_DECREF(result); return NULL; } @@ -1749,351 +1547,62 @@ gc_get_referrers(PyObject *self, PyObject *args) return result; } -/* Append obj to list; return true if error (out of memory), false if OK. */ -static int -referentsvisit(PyObject *obj, void *arg) +PyObject * +_PyGC_GetObjects(PyInterpreterState *interp, Py_ssize_t generation) { - PyObject *list = arg; - return PyList_Append(list, obj) < 0; -} - -PyDoc_STRVAR(gc_get_referents__doc__, -"get_referents(*objs) -> list\n\ -Return the list of objects that are directly referred to by objs."); + assert(generation >= -1 && generation < NUM_GENERATIONS); + GCState *gcstate = &interp->gc; -static PyObject * -gc_get_referents(PyObject *self, PyObject *args) -{ - Py_ssize_t i; - if (PySys_Audit("gc.get_referents", "(O)", args) < 0) { - return NULL; - } PyObject *result = PyList_New(0); - - if (result == NULL) - return NULL; - - for (i = 0; i < PyTuple_GET_SIZE(args); i++) { - traverseproc traverse; - PyObject *obj = PyTuple_GET_ITEM(args, i); - - if (!_PyObject_IS_GC(obj)) - continue; - traverse = Py_TYPE(obj)->tp_traverse; - if (! traverse) - continue; - if (traverse(obj, referentsvisit, result)) { - Py_DECREF(result); - return NULL; - } - } - return result; -} - -/*[clinic input] -gc.get_objects - generation: Py_ssize_t(accept={int, NoneType}, c_default="-1") = None - Generation to extract the objects from. - -Return a list of objects tracked by the collector (excluding the list returned). - -If generation is not None, return only the objects tracked by the collector -that are in that generation. -[clinic start generated code]*/ - -static PyObject * -gc_get_objects_impl(PyObject *module, Py_ssize_t generation) -/*[clinic end generated code: output=48b35fea4ba6cb0e input=ef7da9df9806754c]*/ -{ - PyThreadState *tstate = _PyThreadState_GET(); - int i; - PyObject* result; - GCState *gcstate = &tstate->interp->gc; - - if (PySys_Audit("gc.get_objects", "n", generation) < 0) { - return NULL; - } - - result = PyList_New(0); if (result == NULL) { return NULL; } - /* If generation is passed, we extract only that generation */ - if (generation != -1) { - if (generation >= NUM_GENERATIONS) { - _PyErr_Format(tstate, PyExc_ValueError, - "generation parameter must be less than the number of " - "available generations (%i)", - NUM_GENERATIONS); - goto error; - } - - if (generation < 0) { - _PyErr_SetString(tstate, PyExc_ValueError, - "generation parameter cannot be negative"); - goto error; + if (generation == -1) { + /* If generation is -1, get all objects from all generations */ + for (int i = 0; i < NUM_GENERATIONS; i++) { + if (append_objects(result, GEN_HEAD(gcstate, i))) { + goto error; + } } - + } + else { if (append_objects(result, GEN_HEAD(gcstate, generation))) { goto error; } - - return result; } - /* If generation is not passed or None, get all objects from all generations */ - for (i = 0; i < NUM_GENERATIONS; i++) { - if (append_objects(result, GEN_HEAD(gcstate, i))) { - goto error; - } - } return result; - error: Py_DECREF(result); return NULL; } -/*[clinic input] -gc.get_stats - -Return a list of dictionaries containing per-generation statistics. -[clinic start generated code]*/ - -static PyObject * -gc_get_stats_impl(PyObject *module) -/*[clinic end generated code: output=a8ab1d8a5d26f3ab input=1ef4ed9d17b1a470]*/ -{ - int i; - struct gc_generation_stats stats[NUM_GENERATIONS], *st; - - /* To get consistent values despite allocations while constructing - the result list, we use a snapshot of the running stats. */ - GCState *gcstate = get_gc_state(); - for (i = 0; i < NUM_GENERATIONS; i++) { - stats[i] = gcstate->generation_stats[i]; - } - - PyObject *result = PyList_New(0); - if (result == NULL) - return NULL; - - for (i = 0; i < NUM_GENERATIONS; i++) { - PyObject *dict; - st = &stats[i]; - dict = Py_BuildValue("{snsnsn}", - "collections", st->collections, - "collected", st->collected, - "uncollectable", st->uncollectable - ); - if (dict == NULL) - goto error; - if (PyList_Append(result, dict)) { - Py_DECREF(dict); - goto error; - } - Py_DECREF(dict); - } - return result; - -error: - Py_XDECREF(result); - return NULL; -} - - -/*[clinic input] -gc.is_tracked - - obj: object - / - -Returns true if the object is tracked by the garbage collector. - -Simple atomic objects will return false. -[clinic start generated code]*/ - -static PyObject * -gc_is_tracked(PyObject *module, PyObject *obj) -/*[clinic end generated code: output=14f0103423b28e31 input=d83057f170ea2723]*/ -{ - PyObject *result; - - if (_PyObject_IS_GC(obj) && _PyObject_GC_IS_TRACKED(obj)) - result = Py_True; - else - result = Py_False; - return Py_NewRef(result); -} - -/*[clinic input] -gc.is_finalized - - obj: object - / - -Returns true if the object has been already finalized by the GC. -[clinic start generated code]*/ - -static PyObject * -gc_is_finalized(PyObject *module, PyObject *obj) -/*[clinic end generated code: output=e1516ac119a918ed input=201d0c58f69ae390]*/ -{ - if (_PyObject_IS_GC(obj) && _PyGC_FINALIZED(obj)) { - Py_RETURN_TRUE; - } - Py_RETURN_FALSE; -} - -/*[clinic input] -gc.freeze - -Freeze all current tracked objects and ignore them for future collections. - -This can be used before a POSIX fork() call to make the gc copy-on-write friendly. -Note: collection before a POSIX fork() call may free pages for future allocation -which can cause copy-on-write. -[clinic start generated code]*/ - -static PyObject * -gc_freeze_impl(PyObject *module) -/*[clinic end generated code: output=502159d9cdc4c139 input=b602b16ac5febbe5]*/ +void +_PyGC_Freeze(PyInterpreterState *interp) { - GCState *gcstate = get_gc_state(); + GCState *gcstate = &interp->gc; for (int i = 0; i < NUM_GENERATIONS; ++i) { gc_list_merge(GEN_HEAD(gcstate, i), &gcstate->permanent_generation.head); gcstate->generations[i].count = 0; } - Py_RETURN_NONE; } -/*[clinic input] -gc.unfreeze - -Unfreeze all objects in the permanent generation. - -Put all objects in the permanent generation back into oldest generation. -[clinic start generated code]*/ - -static PyObject * -gc_unfreeze_impl(PyObject *module) -/*[clinic end generated code: output=1c15f2043b25e169 input=2dd52b170f4cef6c]*/ +void +_PyGC_Unfreeze(PyInterpreterState *interp) { - GCState *gcstate = get_gc_state(); + GCState *gcstate = &interp->gc; gc_list_merge(&gcstate->permanent_generation.head, GEN_HEAD(gcstate, NUM_GENERATIONS-1)); - Py_RETURN_NONE; } -/*[clinic input] -gc.get_freeze_count -> Py_ssize_t - -Return the number of objects in the permanent generation. -[clinic start generated code]*/ - -static Py_ssize_t -gc_get_freeze_count_impl(PyObject *module) -/*[clinic end generated code: output=61cbd9f43aa032e1 input=45ffbc65cfe2a6ed]*/ +Py_ssize_t +_PyGC_GetFreezeCount(PyInterpreterState *interp) { - GCState *gcstate = get_gc_state(); + GCState *gcstate = &interp->gc; return gc_list_size(&gcstate->permanent_generation.head); } - -PyDoc_STRVAR(gc__doc__, -"This module provides access to the garbage collector for reference cycles.\n" -"\n" -"enable() -- Enable automatic garbage collection.\n" -"disable() -- Disable automatic garbage collection.\n" -"isenabled() -- Returns true if automatic collection is enabled.\n" -"collect() -- Do a full collection right now.\n" -"get_count() -- Return the current collection counts.\n" -"get_stats() -- Return list of dictionaries containing per-generation stats.\n" -"set_debug() -- Set debugging flags.\n" -"get_debug() -- Get debugging flags.\n" -"set_threshold() -- Set the collection thresholds.\n" -"get_threshold() -- Return the current the collection thresholds.\n" -"get_objects() -- Return a list of all objects tracked by the collector.\n" -"is_tracked() -- Returns true if a given object is tracked.\n" -"is_finalized() -- Returns true if a given object has been already finalized.\n" -"get_referrers() -- Return the list of objects that refer to an object.\n" -"get_referents() -- Return the list of objects that an object refers to.\n" -"freeze() -- Freeze all tracked objects and ignore them for future collections.\n" -"unfreeze() -- Unfreeze all objects in the permanent generation.\n" -"get_freeze_count() -- Return the number of objects in the permanent generation.\n"); - -static PyMethodDef GcMethods[] = { - GC_ENABLE_METHODDEF - GC_DISABLE_METHODDEF - GC_ISENABLED_METHODDEF - GC_SET_DEBUG_METHODDEF - GC_GET_DEBUG_METHODDEF - GC_GET_COUNT_METHODDEF - {"set_threshold", gc_set_threshold, METH_VARARGS, gc_set_thresh__doc__}, - GC_GET_THRESHOLD_METHODDEF - GC_COLLECT_METHODDEF - GC_GET_OBJECTS_METHODDEF - GC_GET_STATS_METHODDEF - GC_IS_TRACKED_METHODDEF - GC_IS_FINALIZED_METHODDEF - {"get_referrers", gc_get_referrers, METH_VARARGS, - gc_get_referrers__doc__}, - {"get_referents", gc_get_referents, METH_VARARGS, - gc_get_referents__doc__}, - GC_FREEZE_METHODDEF - GC_UNFREEZE_METHODDEF - GC_GET_FREEZE_COUNT_METHODDEF - {NULL, NULL} /* Sentinel */ -}; - -static int -gcmodule_exec(PyObject *module) -{ - GCState *gcstate = get_gc_state(); - - /* garbage and callbacks are initialized by _PyGC_Init() early in - * interpreter lifecycle. */ - assert(gcstate->garbage != NULL); - if (PyModule_AddObjectRef(module, "garbage", gcstate->garbage) < 0) { - return -1; - } - assert(gcstate->callbacks != NULL); - if (PyModule_AddObjectRef(module, "callbacks", gcstate->callbacks) < 0) { - return -1; - } - -#define ADD_INT(NAME) if (PyModule_AddIntConstant(module, #NAME, NAME) < 0) { return -1; } - ADD_INT(DEBUG_STATS); - ADD_INT(DEBUG_COLLECTABLE); - ADD_INT(DEBUG_UNCOLLECTABLE); - ADD_INT(DEBUG_SAVEALL); - ADD_INT(DEBUG_LEAK); -#undef ADD_INT - return 0; -} - -static PyModuleDef_Slot gcmodule_slots[] = { - {Py_mod_exec, gcmodule_exec}, - {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, - {0, NULL} -}; - -static struct PyModuleDef gcmodule = { - PyModuleDef_HEAD_INIT, - .m_name = "gc", - .m_doc = gc__doc__, - .m_size = 0, // per interpreter state, see: get_gc_state() - .m_methods = GcMethods, - .m_slots = gcmodule_slots -}; - -PyMODINIT_FUNC -PyInit_gc(void) -{ - return PyModuleDef_Init(&gcmodule); -} - /* C API for controlling the state of the garbage collector */ int PyGC_Enable(void) @@ -2139,6 +1648,12 @@ PyGC_Collect(void) return n; } +Py_ssize_t +_PyGC_Collect(PyThreadState *tstate, int generation, _PyGC_Reason reason) +{ + return gc_collect_main(tstate, generation, reason); +} + Py_ssize_t _PyGC_CollectNoFail(PyThreadState *tstate) { @@ -2155,10 +1670,10 @@ void _PyGC_DumpShutdownStats(PyInterpreterState *interp) { GCState *gcstate = &interp->gc; - if (!(gcstate->debug & DEBUG_SAVEALL) + if (!(gcstate->debug & _PyGC_DEBUG_SAVEALL) && gcstate->garbage != NULL && PyList_GET_SIZE(gcstate->garbage) > 0) { const char *message; - if (gcstate->debug & DEBUG_UNCOLLECTABLE) + if (gcstate->debug & _PyGC_DEBUG_UNCOLLECTABLE) message = "gc: %zd uncollectable objects at " \ "shutdown"; else @@ -2171,7 +1686,7 @@ _PyGC_DumpShutdownStats(PyInterpreterState *interp) "gc", NULL, message, PyList_GET_SIZE(gcstate->garbage))) PyErr_WriteUnraisable(NULL); - if (gcstate->debug & DEBUG_UNCOLLECTABLE) { + if (gcstate->debug & _PyGC_DEBUG_UNCOLLECTABLE) { PyObject *repr = NULL, *bytes = NULL; repr = PyObject_Repr(gcstate->garbage); if (!repr || !(bytes = PyUnicode_EncodeFSDefault(repr))) From 29e62e27df12ce9c8eb377b2a65a92b280257fbb Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Thu, 4 Jan 2024 21:53:20 +0000 Subject: [PATCH 3/5] Update comment at top of Python/gc.c --- Python/gc.c | 27 +++------------------------ 1 file changed, 3 insertions(+), 24 deletions(-) diff --git a/Python/gc.c b/Python/gc.c index 04a686e50ea331..015a4c261d4358 100644 --- a/Python/gc.c +++ b/Python/gc.c @@ -1,27 +1,6 @@ -/* - - Reference Cycle Garbage Collection - ================================== - - Neil Schemenauer - - Based on a post on the python-dev list. Ideas from Guido van Rossum, - Eric Tiedemann, and various others. - - http://www.arctrix.com/nas/python/gc/ - - The following mailing list threads provide a historical perspective on - the design of this module. Note that a fair amount of refinement has - occurred since those discussions. - - http://mail.python.org/pipermail/python-dev/2000-March/002385.html - http://mail.python.org/pipermail/python-dev/2000-March/002434.html - http://mail.python.org/pipermail/python-dev/2000-March/002497.html - - For a highlevel view of the collection process, read the collect - function. - -*/ +// This implements the reference cycle garbage collector. +// The Python module inteface to the collector is in gcmodule.c. +// See https://devguide.python.org/internals/garbage-collector/ #include "Python.h" #include "pycore_ceval.h" // _Py_set_eval_breaker_bit() From 1a7d8936c6c6c018830ec96a389a66e34d2a6ca8 Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Fri, 5 Jan 2024 11:23:05 -0500 Subject: [PATCH 4/5] Apply suggestions from code review Co-authored-by: Erlend E. Aasland --- Python/gc.c | 53 +++++++++++++++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/Python/gc.c b/Python/gc.c index 015a4c261d4358..421d64eaf2c94b 100644 --- a/Python/gc.c +++ b/Python/gc.c @@ -777,8 +777,9 @@ handle_weakrefs(PyGC_Head *unreachable, PyGC_Head *old) _PyWeakref_ClearRef((PyWeakReference *)op); } - if (! _PyType_SUPPORTS_WEAKREFS(Py_TYPE(op))) + if (! _PyType_SUPPORTS_WEAKREFS(Py_TYPE(op))) { continue; + } /* It supports weakrefs. Does it have any? * @@ -870,10 +871,12 @@ handle_weakrefs(PyGC_Head *unreachable, PyGC_Head *old) /* copy-paste of weakrefobject.c's handle_callback() */ temp = PyObject_CallOneArg(callback, (PyObject *)wr); - if (temp == NULL) + if (temp == NULL) { PyErr_WriteUnraisable(callback); - else + } + else { Py_DECREF(temp); + } /* Give up the reference we created in the first pass. When * op's refcount hits 0 (which it may or may not do right now), @@ -961,7 +964,8 @@ finalize_garbage(PyThreadState *tstate, PyGC_Head *collectable) PyObject *op = FROM_GC(gc); gc_list_move(gc, &seen); if (!_PyGCHead_FINALIZED(gc) && - (finalize = Py_TYPE(op)->tp_finalize) != NULL) { + (finalize = Py_TYPE(op)->tp_finalize) != NULL) + { _PyGCHead_SET_FINALIZED(gc); Py_INCREF(op); finalize(op); @@ -1264,7 +1268,9 @@ gc_select_generation(GCState *gcstate) */ if (i == NUM_GENERATIONS - 1 && gcstate->long_lived_pending < gcstate->long_lived_total / 4) + { continue; + } return i; } } @@ -1329,14 +1335,17 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason) t1 = _PyTime_GetPerfCounter(); } - if (PyDTrace_GC_START_ENABLED()) + if (PyDTrace_GC_START_ENABLED()) { PyDTrace_GC_START(generation); + } /* update collection and allocation counters */ - if (generation+1 < NUM_GENERATIONS) + if (generation+1 < NUM_GENERATIONS) { gcstate->generations[generation+1].count += 1; - for (i = 0; i <= generation; i++) + } + for (i = 0; i <= generation; i++) { gcstate->generations[i].count = 0; + } /* merge younger generations with one we are currently collecting */ for (i = 0; i < generation; i++) { @@ -1483,9 +1492,11 @@ referrersvisit(PyObject* obj, void *arg) { PyObject *objs = arg; Py_ssize_t i; - for (i = 0; i < PyTuple_GET_SIZE(objs); i++) - if (PyTuple_GET_ITEM(objs, i) == obj) + for (i = 0; i < PyTuple_GET_SIZE(objs); i++) { + if (PyTuple_GET_ITEM(objs, i) == obj) { return 1; + } + } return 0; } @@ -1498,11 +1509,13 @@ gc_referrers_for(PyObject *objs, PyGC_Head *list, PyObject *resultlist) for (gc = GC_NEXT(list); gc != list; gc = GC_NEXT(gc)) { obj = FROM_GC(gc); traverse = Py_TYPE(obj)->tp_traverse; - if (obj == objs || obj == resultlist) + if (obj == objs || obj == resultlist) { continue; + } if (traverse(obj, referrersvisit, objs)) { - if (PyList_Append(resultlist, obj) < 0) + if (PyList_Append(resultlist, obj) < 0) { return 0; /* error */ + } } } return 1; /* no error */ @@ -1652,24 +1665,28 @@ _PyGC_DumpShutdownStats(PyInterpreterState *interp) if (!(gcstate->debug & _PyGC_DEBUG_SAVEALL) && gcstate->garbage != NULL && PyList_GET_SIZE(gcstate->garbage) > 0) { const char *message; - if (gcstate->debug & _PyGC_DEBUG_UNCOLLECTABLE) - message = "gc: %zd uncollectable objects at " \ - "shutdown"; - else - message = "gc: %zd uncollectable objects at " \ - "shutdown; use gc.set_debug(gc.DEBUG_UNCOLLECTABLE) to list them"; + if (gcstate->debug & _PyGC_DEBUG_UNCOLLECTABLE) { + message = "gc: %zd uncollectable objects at shutdown"; + } + else { + message = "gc: %zd uncollectable objects at shutdown; " \ + "use gc.set_debug(gc.DEBUG_UNCOLLECTABLE) to list them"; + } /* PyErr_WarnFormat does too many things and we are at shutdown, the warnings module's dependencies (e.g. linecache) may be gone already. */ if (PyErr_WarnExplicitFormat(PyExc_ResourceWarning, "gc", 0, "gc", NULL, message, PyList_GET_SIZE(gcstate->garbage))) + { PyErr_WriteUnraisable(NULL); + } if (gcstate->debug & _PyGC_DEBUG_UNCOLLECTABLE) { PyObject *repr = NULL, *bytes = NULL; repr = PyObject_Repr(gcstate->garbage); - if (!repr || !(bytes = PyUnicode_EncodeFSDefault(repr))) + if (!repr || !(bytes = PyUnicode_EncodeFSDefault(repr))) { PyErr_WriteUnraisable(gcstate->garbage); + } else { PySys_WriteStderr( " %s\n", From 24535df5847c3cedb87691dc7fb5494fc1d5a55a Mon Sep 17 00:00:00 2001 From: Sam Gross Date: Fri, 5 Jan 2024 11:23:39 -0500 Subject: [PATCH 5/5] Apply suggestions from code review Co-authored-by: Erlend E. Aasland --- Python/gc.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Python/gc.c b/Python/gc.c index 421d64eaf2c94b..f47c74f87a9166 100644 --- a/Python/gc.c +++ b/Python/gc.c @@ -1354,10 +1354,12 @@ gc_collect_main(PyThreadState *tstate, int generation, _PyGC_Reason reason) /* handy references */ young = GEN_HEAD(gcstate, generation); - if (generation < NUM_GENERATIONS-1) + if (generation < NUM_GENERATIONS-1) { old = GEN_HEAD(gcstate, generation+1); - else + } + else { old = young; + } validate_list(old, collecting_clear_unreachable_clear); deduce_unreachable(young, &unreachable);