From 08be72d0aa0112118b79d271479598c218adfd23 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 24 Oct 2010 15:11:22 +0000 Subject: [PATCH] Add a new warning gategory, ResourceWarning, as discussed on python-dev. It is silent by default, except when configured --with-pydebug. Emit this warning from the GC shutdown procedure, rather than just printing to stderr. --- Doc/library/exceptions.rst | 10 ++++++++++ Doc/library/gc.rst | 20 +++++++++----------- Doc/library/warnings.rst | 3 +++ Include/pyerrors.h | 1 + Lib/test/exception_hierarchy.txt | 1 + Lib/test/test_gc.py | 8 +++++--- Lib/warnings.py | 7 +++++++ Misc/NEWS | 2 +- Modules/gcmodule.c | 20 ++++++++++---------- Objects/exceptions.c | 10 ++++++++++ Python/_warnings.c | 22 +++++++++++++++++++--- Python/errors.c | 6 ++++-- 12 files changed, 80 insertions(+), 30 deletions(-) diff --git a/Doc/library/exceptions.rst b/Doc/library/exceptions.rst index 49b5b9338ac..9d776983d4e 100644 --- a/Doc/library/exceptions.rst +++ b/Doc/library/exceptions.rst @@ -410,10 +410,20 @@ module for more information. Base class for warnings related to Unicode. + .. exception:: BytesWarning Base class for warnings related to :class:`bytes` and :class:`buffer`. + +.. exception:: ResourceWarning + + Base class for warnings related to resource usage. + + .. versionadded:: 3.2 + + + Exception hierarchy ------------------- diff --git a/Doc/library/gc.rst b/Doc/library/gc.rst index ae615ebe8d2..0281bb761f7 100644 --- a/Doc/library/gc.rst +++ b/Doc/library/gc.rst @@ -174,17 +174,15 @@ value but should not rebind it): with :meth:`__del__` methods, and *garbage* can be examined in that case to verify that no such cycles are being created. - If :const:`DEBUG_SAVEALL` is set, then all unreachable objects will be added to - this list rather than freed. + 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. + If this list is non-empty at interpreter shutdown, a + :exc:`ResourceWarning` is emitted, which is silent by default. If + :const:`DEBUG_UNCOLLECTABLE` is set, in addition all uncollectable objects + are 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`: @@ -203,12 +201,12 @@ The following constants are provided for use with :func:`set_debug`: .. data:: DEBUG_UNCOLLECTABLE Print information of uncollectable objects found (objects which are not - reachable but cannot be freed by the collector). These objects will be added to - the ``garbage`` list. + 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. + shutdown, if it isn't empty. .. data:: DEBUG_SAVEALL diff --git a/Doc/library/warnings.rst b/Doc/library/warnings.rst index 309fec5032e..64689adfb9f 100644 --- a/Doc/library/warnings.rst +++ b/Doc/library/warnings.rst @@ -82,6 +82,9 @@ following warnings category classes are currently defined: | :exc:`BytesWarning` | Base category for warnings related to | | | :class:`bytes` and :class:`buffer`. | +----------------------------------+-----------------------------------------------+ +| :exc:`ResourceWarning` | Base category for warnings related to | +| | resource usage. | ++----------------------------------+-----------------------------------------------+ While these are technically built-in exceptions, they are documented here, diff --git a/Include/pyerrors.h b/Include/pyerrors.h index 58a3df731ee..25843c359d5 100644 --- a/Include/pyerrors.h +++ b/Include/pyerrors.h @@ -170,6 +170,7 @@ PyAPI_DATA(PyObject *) PyExc_FutureWarning; PyAPI_DATA(PyObject *) PyExc_ImportWarning; PyAPI_DATA(PyObject *) PyExc_UnicodeWarning; PyAPI_DATA(PyObject *) PyExc_BytesWarning; +PyAPI_DATA(PyObject *) PyExc_ResourceWarning; /* Convenience functions */ diff --git a/Lib/test/exception_hierarchy.txt b/Lib/test/exception_hierarchy.txt index 73ccb6674b7..5037b335d97 100644 --- a/Lib/test/exception_hierarchy.txt +++ b/Lib/test/exception_hierarchy.txt @@ -47,3 +47,4 @@ BaseException +-- ImportWarning +-- UnicodeWarning +-- BytesWarning + +-- ResourceWarning diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py index 6c8907d4faf..9de7c29190f 100644 --- a/Lib/test/test_gc.py +++ b/Lib/test/test_gc.py @@ -485,7 +485,7 @@ class GCTests(unittest.TestCase): gc.set_debug(%s) """ def run_command(code): - p = subprocess.Popen([sys.executable, "-c", code], + p = subprocess.Popen([sys.executable, "-Wd", "-c", code], stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = p.communicate() @@ -494,11 +494,13 @@ class GCTests(unittest.TestCase): return strip_python_stderr(stderr) stderr = run_command(code % "0") - self.assertIn(b"gc: 2 uncollectable objects at shutdown", stderr) + self.assertIn(b"ResourceWarning: gc: 2 uncollectable objects at " + b"shutdown; use", stderr) self.assertNotIn(b"", stderr) # With DEBUG_UNCOLLECTABLE, the garbage list gets printed stderr = run_command(code % "gc.DEBUG_UNCOLLECTABLE") - self.assertIn(b"gc: 2 uncollectable objects at shutdown", stderr) + self.assertIn(b"ResourceWarning: gc: 2 uncollectable objects at " + b"shutdown", stderr) self.assertTrue( (b"[, ]" in stderr) or (b"[, ]" in stderr), stderr) diff --git a/Lib/warnings.py b/Lib/warnings.py index a81aab3fe56..5b5821b1c3d 100644 --- a/Lib/warnings.py +++ b/Lib/warnings.py @@ -383,4 +383,11 @@ if not _warnings_defaults: else: bytes_action = "ignore" simplefilter(bytes_action, category=BytesWarning, append=1) + # resource usage warnings are enabled by default in pydebug mode + if hasattr(sys, 'gettotalrefcount'): + resource_action = "always" + else: + resource_action = "ignore" + simplefilter(resource_action, category=ResourceWarning, append=1) + del _warnings_defaults diff --git a/Misc/NEWS b/Misc/NEWS index e1b818d36da..015ce1f1cc7 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -691,7 +691,7 @@ Extensions - Issue #8524: Add a detach() method to socket objects, so as to put the socket into the closed state without closing the underlying file descriptor. -- Issue #477863: Print a warning at shutdown if gc.garbage is not empty. +- Issue #477863: Emit a ResourceWarning at shutdown if gc.garbage is not empty. - Issue #6869: Fix a refcount problem in the _ctypes extension. diff --git a/Modules/gcmodule.c b/Modules/gcmodule.c index a95bec773a6..3f96c422574 100644 --- a/Modules/gcmodule.c +++ b/Modules/gcmodule.c @@ -1368,11 +1368,16 @@ _PyGC_Fini(void) { if (!(debug & DEBUG_SAVEALL) && garbage != NULL && PyList_GET_SIZE(garbage) > 0) { - PySys_WriteStderr( - "gc: " - "%" PY_FORMAT_SIZE_T "d uncollectable objects at shutdown:\n", - PyList_GET_SIZE(garbage) - ); + char *message; + if (debug & DEBUG_UNCOLLECTABLE) + message = "gc: %" PY_FORMAT_SIZE_T "d uncollectable objects at " \ + "shutdown"; + else + message = "gc: %" PY_FORMAT_SIZE_T "d uncollectable objects at " \ + "shutdown; use gc.set_debug(gc.DEBUG_UNCOLLECTABLE) to list them"; + if (PyErr_WarnFormat(PyExc_ResourceWarning, 0, message, + PyList_GET_SIZE(garbage)) < 0) + PyErr_WriteUnraisable(NULL); if (debug & DEBUG_UNCOLLECTABLE) { PyObject *repr = NULL, *bytes = NULL; repr = PyObject_Repr(garbage); @@ -1387,11 +1392,6 @@ _PyGC_Fini(void) Py_XDECREF(repr); Py_XDECREF(bytes); } - else { - PySys_WriteStderr( - " Use gc.set_debug(gc.DEBUG_UNCOLLECTABLE) to list them.\n" - ); - } } } diff --git a/Objects/exceptions.c b/Objects/exceptions.c index b82b6ba87f7..5715b26fa7d 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -1852,6 +1852,7 @@ SimpleExtendsException(PyExc_Warning, UnicodeWarning, "Base class for warnings about Unicode related problems, mostly\n" "related to conversion problems."); + /* * BytesWarning extends Warning */ @@ -1860,6 +1861,13 @@ SimpleExtendsException(PyExc_Warning, BytesWarning, "related to conversion from str or comparing to str."); +/* + * ResourceWarning extends Warning + */ +SimpleExtendsException(PyExc_Warning, ResourceWarning, + "Base class for warnings about resource usage."); + + /* Pre-computed MemoryError instance. Best to create this as early as * possible and not wait until a MemoryError is actually raised! @@ -1939,6 +1947,7 @@ _PyExc_Init(void) PRE_INIT(ImportWarning) PRE_INIT(UnicodeWarning) PRE_INIT(BytesWarning) + PRE_INIT(ResourceWarning) bltinmod = PyImport_ImportModule("builtins"); if (bltinmod == NULL) @@ -2001,6 +2010,7 @@ _PyExc_Init(void) POST_INIT(ImportWarning) POST_INIT(UnicodeWarning) POST_INIT(BytesWarning) + POST_INIT(ResourceWarning) PyExc_MemoryErrorInst = BaseException_new(&_PyExc_MemoryError, NULL, NULL); if (!PyExc_MemoryErrorInst) diff --git a/Python/_warnings.c b/Python/_warnings.c index a4e9d48e8ec..87755e1edbc 100644 --- a/Python/_warnings.c +++ b/Python/_warnings.c @@ -835,6 +835,7 @@ create_filter(PyObject *category, const char *action) static PyObject *ignore_str = NULL; static PyObject *error_str = NULL; static PyObject *default_str = NULL; + static PyObject *always_str = NULL; PyObject *action_obj = NULL; PyObject *lineno, *result; @@ -862,6 +863,14 @@ create_filter(PyObject *category, const char *action) } action_obj = default_str; } + else if (!strcmp(action, "always")) { + if (always_str == NULL) { + always_str = PyUnicode_InternFromString("always"); + if (always_str == NULL) + return NULL; + } + action_obj = always_str; + } else { Py_FatalError("unknown action"); } @@ -879,10 +888,10 @@ static PyObject * init_filters(void) { /* Don't silence DeprecationWarning if -3 was used. */ - PyObject *filters = PyList_New(4); + PyObject *filters = PyList_New(5); unsigned int pos = 0; /* Post-incremented in each use. */ unsigned int x; - const char *bytes_action; + const char *bytes_action, *resource_action; if (filters == NULL) return NULL; @@ -901,7 +910,14 @@ init_filters(void) bytes_action = "ignore"; PyList_SET_ITEM(filters, pos++, create_filter(PyExc_BytesWarning, bytes_action)); - + /* resource usage warnings are enabled by default in pydebug mode */ +#ifdef Py_DEBUG + resource_action = "always"; +#else + resource_action = "ignore"; +#endif + PyList_SET_ITEM(filters, pos++, create_filter(PyExc_ResourceWarning, + resource_action)); for (x = 0; x < pos; x += 1) { if (PyList_GET_ITEM(filters, x) == NULL) { Py_DECREF(filters); diff --git a/Python/errors.c b/Python/errors.c index e3486b95c51..04906141d52 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -767,8 +767,10 @@ PyErr_WriteUnraisable(PyObject *obj) } Py_XDECREF(moduleName); } - PyFile_WriteString(" in ", f); - PyFile_WriteObject(obj, f, 0); + if (obj) { + PyFile_WriteString(" in ", f); + PyFile_WriteObject(obj, f, 0); + } PyFile_WriteString(" ignored\n", f); PyErr_Clear(); /* Just in case */ }