summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Doc/library/gc.rst12
-rw-r--r--Doc/whatsnew/3.2.rst5
-rw-r--r--Include/pythonrun.h1
-rw-r--r--Lib/test/test_gc.py38
-rw-r--r--Misc/NEWS2
-rw-r--r--Modules/gcmodule.c48
-rw-r--r--Python/pythonrun.c3
7 files changed, 99 insertions, 10 deletions
diff --git a/Doc/library/gc.rst b/Doc/library/gc.rst
index a5c9e7bbcf..29afc96edf 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 91fd0c2f6d..f4802a0bf7 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 faf930fc6f..b9da550469 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 3b7df992d8..fba9583144 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 "<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()
diff --git a/Misc/NEWS b/Misc/NEWS
index 7aba37d83e..c5fe3d81de 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 3717a27675..73843272e1 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 233fc16ea1..a7a54ba710 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();