diff options
Diffstat (limited to 'Modules/gcmodule.c')
-rw-r--r-- | Modules/gcmodule.c | 113 |
1 files changed, 103 insertions, 10 deletions
diff --git a/Modules/gcmodule.c b/Modules/gcmodule.c index f782dd0923b..28417c28fe5 100644 --- a/Modules/gcmodule.c +++ b/Modules/gcmodule.c @@ -168,6 +168,18 @@ static Py_ssize_t long_lived_pending = 0; static int debug; static PyObject *tmod = NULL; +/* Running stats per generation */ +struct gc_generation_stats { + /* total number of collections */ + Py_ssize_t collections; + /* total number of collected objects */ + Py_ssize_t collected; + /* total number of uncollectable objects (put into gc.garbage) */ + Py_ssize_t uncollectable; +}; + +static struct gc_generation_stats generation_stats[NUM_GENERATIONS]; + /*-------------------------------------------------------------------------- gc_refs values. @@ -841,7 +853,8 @@ get_time(void) /* This is the main function. Read this to understand how the * collection process works. */ static Py_ssize_t -collect(int generation, Py_ssize_t *n_collected, Py_ssize_t *n_uncollectable) +collect(int generation, Py_ssize_t *n_collected, Py_ssize_t *n_uncollectable, + int nofail) { int i; Py_ssize_t m = 0; /* # objects collected */ @@ -852,6 +865,7 @@ collect(int generation, Py_ssize_t *n_collected, Py_ssize_t *n_uncollectable) PyGC_Head finalizers; /* objects with, & reachable from, __del__ */ PyGC_Head *gc; double t1 = 0.0; + struct gc_generation_stats *stats = &generation_stats[generation]; if (debug & DEBUG_STATS) { PySys_WriteStderr("gc: collecting generation %d...\n", @@ -987,16 +1001,25 @@ collect(int generation, Py_ssize_t *n_collected, Py_ssize_t *n_uncollectable) } if (PyErr_Occurred()) { - if (gc_str == NULL) - gc_str = PyUnicode_FromString("garbage collection"); - PyErr_WriteUnraisable(gc_str); - Py_FatalError("unexpected exception during garbage collection"); + if (nofail) { + PyErr_Clear(); + } + else { + if (gc_str == NULL) + gc_str = PyUnicode_FromString("garbage collection"); + PyErr_WriteUnraisable(gc_str); + Py_FatalError("unexpected exception during garbage collection"); + } } + /* Update stats */ if (n_collected) *n_collected = m; if (n_uncollectable) *n_uncollectable = n; + stats->collections++; + stats->collected += m; + stats->uncollectable += n; return n+m; } @@ -1045,7 +1068,7 @@ collect_with_callback(int generation) { Py_ssize_t result, collected, uncollectable; invoke_gc_callback("start", generation, 0, 0); - result = collect(generation, &collected, &uncollectable); + result = collect(generation, &collected, &uncollectable, 0); invoke_gc_callback("stop", generation, collected, uncollectable); return result; } @@ -1343,6 +1366,52 @@ gc_get_objects(PyObject *self, PyObject *noargs) return result; } +PyDoc_STRVAR(gc_get_stats__doc__, +"get_stats() -> [...]\n" +"\n" +"Return a list of dictionaries containing per-generation statistics.\n"); + +static PyObject * +gc_get_stats(PyObject *self, PyObject *noargs) +{ + int i; + PyObject *result; + 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. */ + for (i = 0; i < NUM_GENERATIONS; i++) { + stats[i] = generation_stats[i]; + } + + 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; +} + + PyDoc_STRVAR(gc_is_tracked__doc__, "is_tracked(obj) -> bool\n" "\n" @@ -1393,6 +1462,7 @@ static PyMethodDef GcMethods[] = { {"collect", (PyCFunction)gc_collect, METH_VARARGS | METH_KEYWORDS, gc_collect__doc__}, {"get_objects", gc_get_objects,METH_NOARGS, gc_get_objects__doc__}, + {"get_stats", gc_get_stats, METH_NOARGS, gc_get_stats__doc__}, {"is_tracked", gc_is_tracked, METH_O, gc_is_tracked__doc__}, {"get_referrers", gc_get_referrers, METH_VARARGS, gc_get_referrers__doc__}, @@ -1480,8 +1550,22 @@ PyGC_Collect(void) return n; } +Py_ssize_t +_PyGC_CollectNoFail(void) +{ + Py_ssize_t n; + + /* This function should only be called on interpreter shutdown, and + therefore not recursively. */ + assert(!collecting); + collecting = 1; + n = collect(NUM_GENERATIONS - 1, NULL, NULL, 1); + collecting = 0; + return n; +} + void -_PyGC_Fini(void) +_PyGC_DumpShutdownStats(void) { if (!(debug & DEBUG_SAVEALL) && garbage != NULL && PyList_GET_SIZE(garbage) > 0) { @@ -1492,8 +1576,12 @@ _PyGC_Fini(void) else message = "gc: %zd uncollectable objects at " \ "shutdown; use gc.set_debug(gc.DEBUG_UNCOLLECTABLE) to list them"; - if (PyErr_WarnFormat(PyExc_ResourceWarning, 0, message, - PyList_GET_SIZE(garbage)) < 0) + /* 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(garbage))) PyErr_WriteUnraisable(NULL); if (debug & DEBUG_UNCOLLECTABLE) { PyObject *repr = NULL, *bytes = NULL; @@ -1502,7 +1590,7 @@ _PyGC_Fini(void) PyErr_WriteUnraisable(garbage); else { PySys_WriteStderr( - " %s\n", + " %s\n", PyBytes_AS_STRING(bytes) ); } @@ -1510,6 +1598,11 @@ _PyGC_Fini(void) Py_XDECREF(bytes); } } +} + +void +_PyGC_Fini(void) +{ Py_CLEAR(callbacks); } |