]> granicus.if.org Git - python/commitdiff
Issue #477863: Print a warning at shutdown if gc.garbage is not empty.
authorAntoine Pitrou <solipsis@pitrou.net>
Sun, 8 Aug 2010 22:18:46 +0000 (22:18 +0000)
committerAntoine Pitrou <solipsis@pitrou.net>
Sun, 8 Aug 2010 22:18:46 +0000 (22:18 +0000)
Doc/library/gc.rst
Doc/whatsnew/3.2.rst
Include/pythonrun.h
Lib/test/test_gc.py
Misc/NEWS
Modules/gcmodule.c
Python/pythonrun.c

index a5c9e7bbcfc4c3ea01b75f01dd62103bd6ef6795..29afc96edfdaedbab4b8b47f236f18b4dc53f3ea 100644 (file)
@@ -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
 
index 91fd0c2f6d203996be0a26152a72c68764105be7..f4802a0bf7bbb048b308482c3f449fde80efaf71 100644 (file)
@@ -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
index faf930fc6f12fba9fc0ccf3c3b11707b8325467c..b9da550469f8a92dfbaf5a43dd90d3f830d988f3 100644 (file)
@@ -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 *);
index 3b7df992d88385efaf101feaf30f3e368e97876c..fba958314471360f2a52115c5161ccda3f1f3f8d 100644 (file)
@@ -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 "<X %%r>" %% 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"[<X 'first'>, <X 'second'>]", 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"[<X 'first'>, <X 'second'>]", stderr)
+
 class GCTogglingTests(unittest.TestCase):
     def setUp(self):
         gc.enable()
index 7aba37d83eab0f45507fbbc40cae076dec336755..c5fe3d81dee6619a72248943da4393632bf46690 100644 (file)
--- 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
index 3717a27675857d32e51d6f436b099e6058cc6abe..73843272e13049b9c14670e3f74474adfb643558 100644 (file)
@@ -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)
index 233fc16ea136fac83d4152525da72e9ed11a3bcf..a7a54ba71018155062d7e64db5a338bee90e2f9e 100644 (file)
@@ -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();