From 696e03553b9e455bd6729cee5a8be43a2924f537 Mon Sep 17 00:00:00 2001 From: Antoine Pitrou Date: Sun, 8 Aug 2010 22:18:46 +0000 Subject: Issue #477863: Print a warning at shutdown if gc.garbage is not empty. --- Doc/library/gc.rst | 12 ++++++++++++ Doc/whatsnew/3.2.rst | 5 +++++ Include/pythonrun.h | 1 + Lib/test/test_gc.py | 38 +++++++++++++++++++++++++++++++++++++- Misc/NEWS | 2 ++ Modules/gcmodule.c | 48 +++++++++++++++++++++++++++++++++++++++--------- Python/pythonrun.c | 3 +++ 7 files changed, 99 insertions(+), 10 deletions(-) diff --git a/Doc/library/gc.rst b/Doc/library/gc.rst index a5c9e7b..29afc96 100644 --- a/Doc/library/gc.rst +++ b/Doc/library/gc.rst @@ -177,6 +177,15 @@ value but should not rebind it): If :const:`DEBUG_SAVEALL` is set, then all unreachable objects will be added to this list rather than freed. + .. versionchanged:: 3.2 + If this list is non-empty at interpreter shutdown, a warning message + gets printed: + + :: + + gc: 2 uncollectable objects at shutdown: + Use gc.set_debug(gc.DEBUG_UNCOLLECTABLE) to list them. + The following constants are provided for use with :func:`set_debug`: @@ -197,6 +206,9 @@ The following constants are provided for use with :func:`set_debug`: reachable but cannot be freed by the collector). These objects will be added to the ``garbage`` list. + .. versionchanged:: 3.2 + Also print the contents of the :data:`garbage` list at interpreter + shutdown (rather than just its length), if it isn't empty. .. data:: DEBUG_SAVEALL diff --git a/Doc/whatsnew/3.2.rst b/Doc/whatsnew/3.2.rst index 91fd0c2..f4802a0 100644 --- a/Doc/whatsnew/3.2.rst +++ b/Doc/whatsnew/3.2.rst @@ -119,6 +119,11 @@ New, Improved, and Deprecated Modules * The :class:`ftplib.FTP` class now supports the context manager protocol (Contributed by Tarek Ziadé and Giampaolo Rodolà; :issue:`4972`.) +* A warning message will now get printed at interpreter shutdown if + the :data:`gc.garbage` list isn't empty. This is meant to make the + programmer aware that his code contains object finalization issues. + (Added by Antoine Pitrou; :issue:`477863`.) + * The :func:`shutil.copytree` function has two new options: * *ignore_dangling_symlinks*: when ``symlinks=False`` (meaning that the diff --git a/Include/pythonrun.h b/Include/pythonrun.h index faf930f..b9da550 100644 --- a/Include/pythonrun.h +++ b/Include/pythonrun.h @@ -148,6 +148,7 @@ PyAPI_FUNC(void) PyBytes_Fini(void); PyAPI_FUNC(void) PyByteArray_Fini(void); PyAPI_FUNC(void) PyFloat_Fini(void); PyAPI_FUNC(void) PyOS_FiniInterrupts(void); +PyAPI_FUNC(void) _PyGC_Fini(void); /* Stuff with no proper home (yet) */ PyAPI_FUNC(char *) PyOS_Readline(FILE *, FILE *, char *); diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py index 3b7df99..fba9583 100644 --- a/Lib/test/test_gc.py +++ b/Lib/test/test_gc.py @@ -1,5 +1,5 @@ import unittest -from test.support import verbose, run_unittest +from test.support import verbose, run_unittest, strip_python_stderr import sys import gc import weakref @@ -466,6 +466,42 @@ class GCTests(unittest.TestCase): # would be damaged, with an empty __dict__. self.assertEqual(x, None) + def test_garbage_at_shutdown(self): + import subprocess + code = """if 1: + import gc + class X: + def __init__(self, name): + self.name = name + def __repr__(self): + return "" %% self.name + def __del__(self): + pass + + x = X('first') + x.x = x + x.y = X('second') + del x + if %d: + gc.set_debug(gc.DEBUG_UNCOLLECTABLE) + """ + def run_command(code): + p = subprocess.Popen([sys.executable, "-c", code], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout, stderr = p.communicate() + self.assertEqual(p.returncode, 0) + self.assertEqual(stdout.strip(), b"") + return strip_python_stderr(stderr) + + stderr = run_command(code % 0) + self.assertIn(b"gc: 2 uncollectable objects at shutdown", stderr) + self.assertNotIn(b"[, ]", stderr) + # With DEBUG_UNCOLLECTABLE, the garbage list gets printed + stderr = run_command(code % 1) + self.assertIn(b"gc: 2 uncollectable objects at shutdown", stderr) + self.assertIn(b"[, ]", stderr) + class GCTogglingTests(unittest.TestCase): def setUp(self): gc.enable() diff --git a/Misc/NEWS b/Misc/NEWS index 7aba37d..c5fe3d8 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -30,6 +30,8 @@ Core and Builtins Extensions ---------- +- Issue #477863: Print a warning at shutdown if gc.garbage is not empty. + - Issue #6869: Fix a refcount problem in the _ctypes extension. - Issue #5504: ctypes should now work with systems where mmap can't diff --git a/Modules/gcmodule.c b/Modules/gcmodule.c index 3717a27..7384327 100644 --- a/Modules/gcmodule.c +++ b/Modules/gcmodule.c @@ -1295,17 +1295,16 @@ static PyMethodDef GcMethods[] = { static struct PyModuleDef gcmodule = { PyModuleDef_HEAD_INIT, - "gc", - gc__doc__, - -1, - GcMethods, - NULL, - NULL, - NULL, - NULL + "gc", /* m_name */ + gc__doc__, /* m_doc */ + -1, /* m_size */ + GcMethods, /* m_methods */ + NULL, /* m_reload */ + NULL, /* m_traverse */ + NULL, /* m_clear */ + NULL /* m_free */ }; - PyMODINIT_FUNC PyInit_gc(void) { @@ -1364,6 +1363,37 @@ PyGC_Collect(void) return n; } +void +_PyGC_Fini(void) +{ + if (garbage != NULL && PyList_GET_SIZE(garbage) > 0) { + PySys_WriteStderr( + "gc: " + "%" PY_FORMAT_SIZE_T "d uncollectable objects at shutdown:\n", + PyList_GET_SIZE(garbage) + ); + if (debug & DEBUG_UNCOLLECTABLE) { + PyObject *repr = NULL, *bytes = NULL; + repr = PyObject_Repr(garbage); + if (!repr || !(bytes = PyUnicode_EncodeFSDefault(repr))) + PyErr_WriteUnraisable(garbage); + else { + PySys_WriteStderr( + " %s\n", + PyBytes_AS_STRING(bytes) + ); + } + Py_XDECREF(repr); + Py_XDECREF(bytes); + } + else { + PySys_WriteStderr( + " Use gc.set_debug(gc.DEBUG_UNCOLLECTABLE) to list them.\n" + ); + } + } +} + /* for debugging */ void _PyGC_Dump(PyGC_Head *g) diff --git a/Python/pythonrun.c b/Python/pythonrun.c index 233fc16..a7a54ba 100644 --- a/Python/pythonrun.c +++ b/Python/pythonrun.c @@ -404,6 +404,9 @@ Py_Finalize(void) while (PyGC_Collect() > 0) /* nothing */; #endif + /* We run this while most interpreter state is still alive, so that + debug information can be printed out */ + _PyGC_Fini(); /* Destroy all modules */ PyImport_Cleanup(); -- cgit v0.12