Commit e0a001d7 authored by Neil Schemenauer's avatar Neil Schemenauer

- Add DEBUG_SAVEALL option. When enabled all garbage objects found by the

  collector will be saved in gc.garbage.  This is useful for debugging a
  program that creates reference cycles.

- Fix else statements in gcmodule.c to conform to Python coding standards.
parent da6c3ee5
...@@ -85,7 +85,9 @@ The following variable is provided for read-only access: ...@@ -85,7 +85,9 @@ The following variable is provided for read-only access:
A list of objects which the collector found to be unreachable A list of objects which the collector found to be unreachable
but could not be freed (uncollectable objects). Objects that have but could not be freed (uncollectable objects). Objects that have
\method{__del__()} methods and create part of a reference cycle cause \method{__del__()} methods and create part of a reference cycle cause
the entire reference cycle to be uncollectable. the entire reference cycle to be uncollectable. If
\constant{DEBUG_SAVEALL} is set, then all unreachable objects will
be added to this list rather than freed.
\end{datadesc} \end{datadesc}
...@@ -117,8 +119,14 @@ When \constant{DEBUG_COLLECTABLE} or \constant{DEBUG_UNCOLLECTABLE} is ...@@ -117,8 +119,14 @@ When \constant{DEBUG_COLLECTABLE} or \constant{DEBUG_UNCOLLECTABLE} is
set, print information about objects other than instance objects found. set, print information about objects other than instance objects found.
\end{datadesc} \end{datadesc}
\begin{datadesc}{DEBUG_SAVEALL}
When set, all unreachable objects found will be appended to
\var{garbage} rather than being freed. This can be useful for debugging
a leaking program.
\end{datadesc}
\begin{datadesc}{DEBUG_LEAK} \begin{datadesc}{DEBUG_LEAK}
The debugging flags necessary for the collector to print The debugging flags necessary for the collector to print
information about a leaking program (equal to \code{DEBUG_COLLECTABLE | information about a leaking program (equal to \code{DEBUG_COLLECTABLE |
DEBUG_UNCOLLECTABLE | DEBUG_INSTANCES | DEBUG_OBJECTS}). DEBUG_UNCOLLECTABLE | DEBUG_INSTANCES | DEBUG_OBJECTS | DEBUG_SAVEALL}).
\end{datadesc} \end{datadesc}
...@@ -53,10 +53,12 @@ static int allocated; ...@@ -53,10 +53,12 @@ static int allocated;
#define DEBUG_UNCOLLECTABLE (1<<2) /* print uncollectable objects */ #define DEBUG_UNCOLLECTABLE (1<<2) /* print uncollectable objects */
#define DEBUG_INSTANCES (1<<3) /* print instances */ #define DEBUG_INSTANCES (1<<3) /* print instances */
#define DEBUG_OBJECTS (1<<4) /* print other objects */ #define DEBUG_OBJECTS (1<<4) /* print other objects */
#define DEBUG_SAVEALL (1<<5) /* save all garbage in gc.garbage */
#define DEBUG_LEAK DEBUG_COLLECTABLE | \ #define DEBUG_LEAK DEBUG_COLLECTABLE | \
DEBUG_UNCOLLECTABLE | \ DEBUG_UNCOLLECTABLE | \
DEBUG_INSTANCES | \ DEBUG_INSTANCES | \
DEBUG_OBJECTS DEBUG_OBJECTS | \
DEBUG_SAVEALL
static int debug; static int debug;
/* list of uncollectable objects */ /* list of uncollectable objects */
...@@ -100,7 +102,8 @@ gc_list_move(PyGC_Head *from, PyGC_Head *to) ...@@ -100,7 +102,8 @@ gc_list_move(PyGC_Head *from, PyGC_Head *to)
if (from->gc_next == from) { if (from->gc_next == from) {
/* empty from list */ /* empty from list */
gc_list_init(to); gc_list_init(to);
} else { }
else {
to->gc_next = from->gc_next; to->gc_next = from->gc_next;
to->gc_next->gc_prev = to; to->gc_next->gc_prev = to;
to->gc_prev = from->gc_prev; to->gc_prev = from->gc_prev;
...@@ -290,7 +293,8 @@ debug_cycle(char *msg, PyObject *op) ...@@ -290,7 +293,8 @@ debug_cycle(char *msg, PyObject *op)
{ {
if ((debug & DEBUG_INSTANCES) && PyInstance_Check(op)) { if ((debug & DEBUG_INSTANCES) && PyInstance_Check(op)) {
debug_instance(msg, (PyInstanceObject *)op); debug_instance(msg, (PyInstanceObject *)op);
} else if (debug & DEBUG_OBJECTS) { }
else if (debug & DEBUG_OBJECTS) {
PySys_WriteStderr("gc: %.100s <%.100s %p>\n", PySys_WriteStderr("gc: %.100s <%.100s %p>\n",
msg, op->ob_type->tp_name, op); msg, op->ob_type->tp_name, op);
} }
...@@ -307,19 +311,20 @@ handle_finalizers(PyGC_Head *finalizers, PyGC_Head *old) ...@@ -307,19 +311,20 @@ handle_finalizers(PyGC_Head *finalizers, PyGC_Head *old)
for (gc = finalizers->gc_next; gc != finalizers; for (gc = finalizers->gc_next; gc != finalizers;
gc = finalizers->gc_next) { gc = finalizers->gc_next) {
PyObject *op = PyObject_FROM_GC(gc); PyObject *op = PyObject_FROM_GC(gc);
/* Add all instances to a Python accessible list of garbage */ if ((debug & DEBUG_SAVEALL) || PyInstance_Check(op)) {
if (PyInstance_Check(op)) { /* If SAVEALL is not set then just append
* instances to the list of garbage. We assume
* that all objects in the finalizers list are
* reachable from instances. */
PyList_Append(garbage, op); PyList_Append(garbage, op);
} }
/* We assume that all objects in finalizers are reachable from /* object is now reachable again */
* instances. Once we add the instances to the garbage list
* everything is reachable from Python again. */
gc_list_remove(gc); gc_list_remove(gc);
gc_list_append(gc, old); gc_list_append(gc, old);
} }
} }
/* Break reference cycles by clearing the containers involved. This is /* Break reference cycles by clearing the containers involved. This is
* tricky business as the lists can be changing and we don't know which * tricky business as the lists can be changing and we don't know which
* objects may be freed. It is possible I screwed something up here. */ * objects may be freed. It is possible I screwed something up here. */
static void static void
...@@ -330,17 +335,18 @@ delete_garbage(PyGC_Head *unreachable, PyGC_Head *old) ...@@ -330,17 +335,18 @@ delete_garbage(PyGC_Head *unreachable, PyGC_Head *old)
while (unreachable->gc_next != unreachable) { while (unreachable->gc_next != unreachable) {
PyGC_Head *gc = unreachable->gc_next; PyGC_Head *gc = unreachable->gc_next;
PyObject *op = PyObject_FROM_GC(gc); PyObject *op = PyObject_FROM_GC(gc);
/* if (debug & DEBUG_SAVEALL) {
PyList_Append(garbage, op); PyList_Append(garbage, op);
*/ }
if ((clear = op->ob_type->tp_clear) != NULL) { else {
Py_INCREF(op); if ((clear = op->ob_type->tp_clear) != NULL) {
clear((PyObject *)op); Py_INCREF(op);
Py_DECREF(op); clear((PyObject *)op);
Py_DECREF(op);
}
} }
/* only try to call tp_clear once for each object */
if (unreachable->gc_next == gc) { if (unreachable->gc_next == gc) {
/* still alive, move it, it may die later */ /* object is still alive, move it, it may die later */
gc_list_remove(gc); gc_list_remove(gc);
gc_list_append(gc, old); gc_list_append(gc, old);
} }
...@@ -425,7 +431,8 @@ collect(PyGC_Head *young, PyGC_Head *old) ...@@ -425,7 +431,8 @@ collect(PyGC_Head *young, PyGC_Head *old)
if (debug & DEBUG_STATS) { if (debug & DEBUG_STATS) {
if (m == 0 && n == 0) { if (m == 0 && n == 0) {
PySys_WriteStderr("gc: done.\n"); PySys_WriteStderr("gc: done.\n");
} else { }
else {
PySys_WriteStderr( PySys_WriteStderr(
"gc: done, %ld unreachable, %ld uncollectable.\n", "gc: done, %ld unreachable, %ld uncollectable.\n",
n+m, n); n+m, n);
...@@ -438,6 +445,9 @@ collect(PyGC_Head *young, PyGC_Head *old) ...@@ -438,6 +445,9 @@ collect(PyGC_Head *young, PyGC_Head *old)
handle_finalizers(&finalizers, old); handle_finalizers(&finalizers, old);
if (PyErr_Occurred()) { if (PyErr_Occurred()) {
if (gc_str == NULL) {
gc_str = PyString_FromString("garbage collection");
}
PyErr_WriteUnraisable(gc_str); PyErr_WriteUnraisable(gc_str);
Py_FatalError("unexpected exception during garbage collection"); Py_FatalError("unexpected exception during garbage collection");
} }
...@@ -461,7 +471,8 @@ collect_generations(void) ...@@ -461,7 +471,8 @@ collect_generations(void)
n = collect(&generation2, &generation2); n = collect(&generation2, &generation2);
} }
collections1 = 0; collections1 = 0;
} else if (collections0 > threshold1) { }
else if (collections0 > threshold1) {
generation = 1; generation = 1;
collections1++; collections1++;
gc_list_merge(&generation0, &generation1); gc_list_merge(&generation0, &generation1);
...@@ -469,7 +480,8 @@ collect_generations(void) ...@@ -469,7 +480,8 @@ collect_generations(void)
n = collect(&generation1, &generation2); n = collect(&generation1, &generation2);
} }
collections0 = 0; collections0 = 0;
} else { }
else {
generation = 0; generation = 0;
collections0++; collections0++;
if (generation0.gc_next != &generation0) { if (generation0.gc_next != &generation0) {
...@@ -603,6 +615,7 @@ static char gc_set_debug__doc__[] = ...@@ -603,6 +615,7 @@ static char gc_set_debug__doc__[] =
" DEBUG_UNCOLLECTABLE - Print unreachable but uncollectable objects found.\n" " DEBUG_UNCOLLECTABLE - Print unreachable but uncollectable objects found.\n"
" DEBUG_INSTANCES - Print instance objects.\n" " DEBUG_INSTANCES - Print instance objects.\n"
" DEBUG_OBJECTS - Print objects other than instances.\n" " DEBUG_OBJECTS - Print objects other than instances.\n"
" DEBUG_SAVEALL - Save objects to gc.garbage rather than freeing them.\n"
" DEBUG_LEAK - Debug leaking programs (everything but STATS).\n" " DEBUG_LEAK - Debug leaking programs (everything but STATS).\n"
; ;
...@@ -679,14 +692,14 @@ static char gc__doc__ [] = ...@@ -679,14 +692,14 @@ static char gc__doc__ [] =
; ;
static PyMethodDef GcMethods[] = { static PyMethodDef GcMethods[] = {
{"enable", gc_enable, METH_VARARGS, gc_enable__doc__}, {"enable", gc_enable, METH_VARARGS, gc_enable__doc__},
{"disable", gc_disable, METH_VARARGS, gc_disable__doc__}, {"disable", gc_disable, METH_VARARGS, gc_disable__doc__},
{"isenabled", gc_isenabled, METH_VARARGS, gc_isenabled__doc__}, {"isenabled", gc_isenabled, METH_VARARGS, gc_isenabled__doc__},
{"set_debug", gc_set_debug, METH_VARARGS, gc_set_debug__doc__}, {"set_debug", gc_set_debug, METH_VARARGS, gc_set_debug__doc__},
{"get_debug", gc_get_debug, METH_VARARGS, gc_get_debug__doc__}, {"get_debug", gc_get_debug, METH_VARARGS, gc_get_debug__doc__},
{"set_threshold", gc_set_thresh, METH_VARARGS, gc_set_thresh__doc__}, {"set_threshold", gc_set_thresh, METH_VARARGS, gc_set_thresh__doc__},
{"get_threshold", gc_get_thresh, METH_VARARGS, gc_get_thresh__doc__}, {"get_threshold", gc_get_thresh, METH_VARARGS, gc_get_thresh__doc__},
{"collect", gc_collect, METH_VARARGS, gc_collect__doc__}, {"collect", gc_collect, METH_VARARGS, gc_collect__doc__},
{NULL, NULL} /* Sentinel */ {NULL, NULL} /* Sentinel */
}; };
...@@ -705,9 +718,6 @@ initgc(void) ...@@ -705,9 +718,6 @@ initgc(void)
if (garbage == NULL) { if (garbage == NULL) {
garbage = PyList_New(0); garbage = PyList_New(0);
} }
if (gc_str == NULL) {
gc_str = PyString_FromString("garbage collection");
}
PyDict_SetItemString(d, "garbage", garbage); PyDict_SetItemString(d, "garbage", garbage);
PyDict_SetItemString(d, "DEBUG_STATS", PyDict_SetItemString(d, "DEBUG_STATS",
PyInt_FromLong(DEBUG_STATS)); PyInt_FromLong(DEBUG_STATS));
...@@ -719,6 +729,8 @@ initgc(void) ...@@ -719,6 +729,8 @@ initgc(void)
PyInt_FromLong(DEBUG_INSTANCES)); PyInt_FromLong(DEBUG_INSTANCES));
PyDict_SetItemString(d, "DEBUG_OBJECTS", PyDict_SetItemString(d, "DEBUG_OBJECTS",
PyInt_FromLong(DEBUG_OBJECTS)); PyInt_FromLong(DEBUG_OBJECTS));
PyDict_SetItemString(d, "DEBUG_SAVEALL",
PyInt_FromLong(DEBUG_SAVEALL));
PyDict_SetItemString(d, "DEBUG_LEAK", PyDict_SetItemString(d, "DEBUG_LEAK",
PyInt_FromLong(DEBUG_LEAK)); PyInt_FromLong(DEBUG_LEAK));
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment