Skip to content

Commit 696e035

Browse files
committed
Issue #477863: Print a warning at shutdown if gc.garbage is not empty.
1 parent 2e5f117 commit 696e035

File tree

7 files changed

+99
-10
lines changed

7 files changed

+99
-10
lines changed

Doc/library/gc.rst

+12
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,15 @@ value but should not rebind it):
177177
If :const:`DEBUG_SAVEALL` is set, then all unreachable objects will be added to
178178
this list rather than freed.
179179

180+
.. versionchanged:: 3.2
181+
If this list is non-empty at interpreter shutdown, a warning message
182+
gets printed:
183+
184+
::
185+
186+
gc: 2 uncollectable objects at shutdown:
187+
Use gc.set_debug(gc.DEBUG_UNCOLLECTABLE) to list them.
188+
180189
The following constants are provided for use with :func:`set_debug`:
181190

182191

@@ -197,6 +206,9 @@ The following constants are provided for use with :func:`set_debug`:
197206
reachable but cannot be freed by the collector). These objects will be added to
198207
the ``garbage`` list.
199208

209+
.. versionchanged:: 3.2
210+
Also print the contents of the :data:`garbage` list at interpreter
211+
shutdown (rather than just its length), if it isn't empty.
200212

201213
.. data:: DEBUG_SAVEALL
202214

Doc/whatsnew/3.2.rst

+5
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,11 @@ New, Improved, and Deprecated Modules
119119
* The :class:`ftplib.FTP` class now supports the context manager protocol
120120
(Contributed by Tarek Ziadé and Giampaolo Rodolà; :issue:`4972`.)
121121

122+
* A warning message will now get printed at interpreter shutdown if
123+
the :data:`gc.garbage` list isn't empty. This is meant to make the
124+
programmer aware that his code contains object finalization issues.
125+
(Added by Antoine Pitrou; :issue:`477863`.)
126+
122127
* The :func:`shutil.copytree` function has two new options:
123128

124129
* *ignore_dangling_symlinks*: when ``symlinks=False`` (meaning that the

Include/pythonrun.h

+1
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ PyAPI_FUNC(void) PyBytes_Fini(void);
148148
PyAPI_FUNC(void) PyByteArray_Fini(void);
149149
PyAPI_FUNC(void) PyFloat_Fini(void);
150150
PyAPI_FUNC(void) PyOS_FiniInterrupts(void);
151+
PyAPI_FUNC(void) _PyGC_Fini(void);
151152

152153
/* Stuff with no proper home (yet) */
153154
PyAPI_FUNC(char *) PyOS_Readline(FILE *, FILE *, char *);

Lib/test/test_gc.py

+37-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import unittest
2-
from test.support import verbose, run_unittest
2+
from test.support import verbose, run_unittest, strip_python_stderr
33
import sys
44
import gc
55
import weakref
@@ -466,6 +466,42 @@ def callback(ignored):
466466
# would be damaged, with an empty __dict__.
467467
self.assertEqual(x, None)
468468

469+
def test_garbage_at_shutdown(self):
470+
import subprocess
471+
code = """if 1:
472+
import gc
473+
class X:
474+
def __init__(self, name):
475+
self.name = name
476+
def __repr__(self):
477+
return "<X %%r>" %% self.name
478+
def __del__(self):
479+
pass
480+
481+
x = X('first')
482+
x.x = x
483+
x.y = X('second')
484+
del x
485+
if %d:
486+
gc.set_debug(gc.DEBUG_UNCOLLECTABLE)
487+
"""
488+
def run_command(code):
489+
p = subprocess.Popen([sys.executable, "-c", code],
490+
stdout=subprocess.PIPE,
491+
stderr=subprocess.PIPE)
492+
stdout, stderr = p.communicate()
493+
self.assertEqual(p.returncode, 0)
494+
self.assertEqual(stdout.strip(), b"")
495+
return strip_python_stderr(stderr)
496+
497+
stderr = run_command(code % 0)
498+
self.assertIn(b"gc: 2 uncollectable objects at shutdown", stderr)
499+
self.assertNotIn(b"[<X 'first'>, <X 'second'>]", stderr)
500+
# With DEBUG_UNCOLLECTABLE, the garbage list gets printed
501+
stderr = run_command(code % 1)
502+
self.assertIn(b"gc: 2 uncollectable objects at shutdown", stderr)
503+
self.assertIn(b"[<X 'first'>, <X 'second'>]", stderr)
504+
469505
class GCTogglingTests(unittest.TestCase):
470506
def setUp(self):
471507
gc.enable()

Misc/NEWS

+2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ Core and Builtins
3030
Extensions
3131
----------
3232

33+
- Issue #477863: Print a warning at shutdown if gc.garbage is not empty.
34+
3335
- Issue #6869: Fix a refcount problem in the _ctypes extension.
3436

3537
- Issue #5504: ctypes should now work with systems where mmap can't

Modules/gcmodule.c

+39-9
Original file line numberDiff line numberDiff line change
@@ -1295,17 +1295,16 @@ static PyMethodDef GcMethods[] = {
12951295

12961296
static struct PyModuleDef gcmodule = {
12971297
PyModuleDef_HEAD_INIT,
1298-
"gc",
1299-
gc__doc__,
1300-
-1,
1301-
GcMethods,
1302-
NULL,
1303-
NULL,
1304-
NULL,
1305-
NULL
1298+
"gc", /* m_name */
1299+
gc__doc__, /* m_doc */
1300+
-1, /* m_size */
1301+
GcMethods, /* m_methods */
1302+
NULL, /* m_reload */
1303+
NULL, /* m_traverse */
1304+
NULL, /* m_clear */
1305+
NULL /* m_free */
13061306
};
13071307

1308-
13091308
PyMODINIT_FUNC
13101309
PyInit_gc(void)
13111310
{
@@ -1364,6 +1363,37 @@ PyGC_Collect(void)
13641363
return n;
13651364
}
13661365

1366+
void
1367+
_PyGC_Fini(void)
1368+
{
1369+
if (garbage != NULL && PyList_GET_SIZE(garbage) > 0) {
1370+
PySys_WriteStderr(
1371+
"gc: "
1372+
"%" PY_FORMAT_SIZE_T "d uncollectable objects at shutdown:\n",
1373+
PyList_GET_SIZE(garbage)
1374+
);
1375+
if (debug & DEBUG_UNCOLLECTABLE) {
1376+
PyObject *repr = NULL, *bytes = NULL;
1377+
repr = PyObject_Repr(garbage);
1378+
if (!repr || !(bytes = PyUnicode_EncodeFSDefault(repr)))
1379+
PyErr_WriteUnraisable(garbage);
1380+
else {
1381+
PySys_WriteStderr(
1382+
" %s\n",
1383+
PyBytes_AS_STRING(bytes)
1384+
);
1385+
}
1386+
Py_XDECREF(repr);
1387+
Py_XDECREF(bytes);
1388+
}
1389+
else {
1390+
PySys_WriteStderr(
1391+
" Use gc.set_debug(gc.DEBUG_UNCOLLECTABLE) to list them.\n"
1392+
);
1393+
}
1394+
}
1395+
}
1396+
13671397
/* for debugging */
13681398
void
13691399
_PyGC_Dump(PyGC_Head *g)

Python/pythonrun.c

+3
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,9 @@ Py_Finalize(void)
404404
while (PyGC_Collect() > 0)
405405
/* nothing */;
406406
#endif
407+
/* We run this while most interpreter state is still alive, so that
408+
debug information can be printed out */
409+
_PyGC_Fini();
407410

408411
/* Destroy all modules */
409412
PyImport_Cleanup();

0 commit comments

Comments
 (0)