Kaydet (Commit) 696e0355 authored tarafından Antoine Pitrou's avatar Antoine Pitrou

Issue #477863: Print a warning at shutdown if gc.garbage is not empty.

üst 2e5f1178
...@@ -177,6 +177,15 @@ value but should not rebind it): ...@@ -177,6 +177,15 @@ value but should not rebind it):
If :const:`DEBUG_SAVEALL` is set, then all unreachable objects will be added to If :const:`DEBUG_SAVEALL` is set, then all unreachable objects will be added to
this list rather than freed. 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`: 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`: ...@@ -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 reachable but cannot be freed by the collector). These objects will be added to
the ``garbage`` list. 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 .. data:: DEBUG_SAVEALL
......
...@@ -119,6 +119,11 @@ New, Improved, and Deprecated Modules ...@@ -119,6 +119,11 @@ New, Improved, and Deprecated Modules
* The :class:`ftplib.FTP` class now supports the context manager protocol * The :class:`ftplib.FTP` class now supports the context manager protocol
(Contributed by Tarek Ziadé and Giampaolo Rodolà; :issue:`4972`.) (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: * The :func:`shutil.copytree` function has two new options:
* *ignore_dangling_symlinks*: when ``symlinks=False`` (meaning that the * *ignore_dangling_symlinks*: when ``symlinks=False`` (meaning that the
......
...@@ -148,6 +148,7 @@ PyAPI_FUNC(void) PyBytes_Fini(void); ...@@ -148,6 +148,7 @@ PyAPI_FUNC(void) PyBytes_Fini(void);
PyAPI_FUNC(void) PyByteArray_Fini(void); PyAPI_FUNC(void) PyByteArray_Fini(void);
PyAPI_FUNC(void) PyFloat_Fini(void); PyAPI_FUNC(void) PyFloat_Fini(void);
PyAPI_FUNC(void) PyOS_FiniInterrupts(void); PyAPI_FUNC(void) PyOS_FiniInterrupts(void);
PyAPI_FUNC(void) _PyGC_Fini(void);
/* Stuff with no proper home (yet) */ /* Stuff with no proper home (yet) */
PyAPI_FUNC(char *) PyOS_Readline(FILE *, FILE *, char *); PyAPI_FUNC(char *) PyOS_Readline(FILE *, FILE *, char *);
......
import unittest import unittest
from test.support import verbose, run_unittest from test.support import verbose, run_unittest, strip_python_stderr
import sys import sys
import gc import gc
import weakref import weakref
...@@ -466,6 +466,42 @@ class GCTests(unittest.TestCase): ...@@ -466,6 +466,42 @@ class GCTests(unittest.TestCase):
# would be damaged, with an empty __dict__. # would be damaged, with an empty __dict__.
self.assertEqual(x, None) 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): class GCTogglingTests(unittest.TestCase):
def setUp(self): def setUp(self):
gc.enable() gc.enable()
......
...@@ -30,6 +30,8 @@ Core and Builtins ...@@ -30,6 +30,8 @@ Core and Builtins
Extensions 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 #6869: Fix a refcount problem in the _ctypes extension.
- Issue #5504: ctypes should now work with systems where mmap can't - Issue #5504: ctypes should now work with systems where mmap can't
......
...@@ -1295,17 +1295,16 @@ static PyMethodDef GcMethods[] = { ...@@ -1295,17 +1295,16 @@ static PyMethodDef GcMethods[] = {
static struct PyModuleDef gcmodule = { static struct PyModuleDef gcmodule = {
PyModuleDef_HEAD_INIT, PyModuleDef_HEAD_INIT,
"gc", "gc", /* m_name */
gc__doc__, gc__doc__, /* m_doc */
-1, -1, /* m_size */
GcMethods, GcMethods, /* m_methods */
NULL, NULL, /* m_reload */
NULL, NULL, /* m_traverse */
NULL, NULL, /* m_clear */
NULL NULL /* m_free */
}; };
PyMODINIT_FUNC PyMODINIT_FUNC
PyInit_gc(void) PyInit_gc(void)
{ {
...@@ -1364,6 +1363,37 @@ PyGC_Collect(void) ...@@ -1364,6 +1363,37 @@ PyGC_Collect(void)
return n; 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 */ /* for debugging */
void void
_PyGC_Dump(PyGC_Head *g) _PyGC_Dump(PyGC_Head *g)
......
...@@ -404,6 +404,9 @@ Py_Finalize(void) ...@@ -404,6 +404,9 @@ Py_Finalize(void)
while (PyGC_Collect() > 0) while (PyGC_Collect() > 0)
/* nothing */; /* nothing */;
#endif #endif
/* We run this while most interpreter state is still alive, so that
debug information can be printed out */
_PyGC_Fini();
/* Destroy all modules */ /* Destroy all modules */
PyImport_Cleanup(); PyImport_Cleanup();
......
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