Commit cbed4274 authored by Kevin Modzelewski's avatar Kevin Modzelewski

Smart leak checker

Previously we would print out all leaked objects at program exit.
But this is problematic since it includes both directly leaked objects
(the ones we are interested in), and indirectly leaked objects
(which only got leaked by being referenced from another leaked object).

This uses the cycle collector infrastructure to figure out what objects
have non-heap, ie leaked, references remaining.  Hopefully this should
help us debug cases where there are hundreds of objects remaining.
parent 03a8895c
......@@ -145,6 +145,16 @@ PyAPI_FUNC(PyObject*) _PyGC_GetGarbage(void) PYSTON_NOEXCEPT;
PyAPI_FUNC(void) PyGC_Enable(void) PYSTON_NOEXCEPT;
PyAPI_FUNC(void) PyGC_Disable(void) PYSTON_NOEXCEPT;
#ifdef Py_TRACE_REFS
// This function is a semi-smart leak finder. Using the cycle-collector
// infrastructure, it will find all non-heap references remaining. This is
// an improvement over calling _Py_PrintReferenceAddresses, since this will
// automatically filter out any objects that are only indirectly leaked.
//
// This will destroy the heap, so it has to be the last thing called.
PyAPI_FUNC(void) _PyGC_FindLeaks(void) PYSTON_NOEXCEPT;
#endif
#ifdef __cplusplus
}
#endif
......
......@@ -876,6 +876,54 @@ get_time(void)
return result;
}
#ifdef Py_TRACE_REFS
// Similar to visit_decref, but changed to operate on all objects, not just
// gc-tracked ones.
static int
visit_findleaks(PyObject *op, void *data) {
assert(op != NULL);
op->ob_refcnt--;
return 0;
}
extern PyObject refchain;
// Pyston addition. Mostly copied from collect() but stripped down a lot.
void
_PyGC_FindLeaks(void)
{
int i;
PyGC_Head *young; /* the generation we are examining */
int generation = NUM_GENERATIONS - 1;
/* merge younger generations with one we are currently collecting */
for (i = 0; i < generation; i++) {
gc_list_merge(GEN_HEAD(i), GEN_HEAD(generation));
}
/* handy references */
young = GEN_HEAD(generation);
traverseproc traverse;
PyGC_Head *gc = young->gc.gc_next;
for (; gc != young; gc=gc->gc.gc_next) {
traverse = Py_TYPE(FROM_GC(gc))->tp_traverse;
(void) traverse(FROM_GC(gc),
(visitproc)visit_findleaks,
NULL);
}
PyObject* op;
fprintf(stderr, "Leaked references:\n");
for (op = refchain._ob_next; op != &refchain; op = op->_ob_next) {
if (op->ob_refcnt == 0)
continue;
fprintf(stderr, "%p [%" PY_FORMAT_SIZE_T "d] %s \033[40mwatch -l ((PyObject*)%p)->ob_refcnt\033[0m\n", op,
op->ob_refcnt, Py_TYPE(op)->tp_name, op);
}
}
#endif
/* This is the main function. Read this to understand how the
* collection process works. */
static Py_ssize_t
......
......@@ -1195,7 +1195,7 @@ extern "C" void _PyTrash_thread_destroy_chain() noexcept {
*/
extern "C" {
// static PyObject refchain = { &refchain, &refchain };
static PyObject refchain(Box::createRefchain());
PyObject refchain(Box::createRefchain());
}
/* Insert op at the front of the list of all objects. If force is true,
......
......@@ -4741,7 +4741,7 @@ extern "C" void Py_Finalize() noexcept {
if (assert_refs) {
#ifdef Py_TRACE_REFS
if (_Py_RefTotal != 0)
_Py_PrintReferenceAddressesCapped(stderr, 10);
_PyGC_FindLeaks();
#endif
RELEASE_ASSERT(_Py_RefTotal == 0, "%ld refs remaining!", _Py_RefTotal);
......
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