aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/Modules/gcmodule.c
diff options
context:
space:
mode:
Diffstat (limited to 'Modules/gcmodule.c')
-rw-r--r--Modules/gcmodule.c113
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);
}