Commit 08be72d0 authored by Georg Brandl's avatar Georg Brandl

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.
parent 872a702b
...@@ -410,10 +410,20 @@ module for more information. ...@@ -410,10 +410,20 @@ module for more information.
Base class for warnings related to Unicode. Base class for warnings related to Unicode.
.. exception:: BytesWarning .. exception:: BytesWarning
Base class for warnings related to :class:`bytes` and :class:`buffer`. 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 Exception hierarchy
------------------- -------------------
......
...@@ -174,17 +174,15 @@ value but should not rebind it): ...@@ -174,17 +174,15 @@ value but should not rebind it):
with :meth:`__del__` methods, and *garbage* can be examined in that case to with :meth:`__del__` methods, and *garbage* can be examined in that case to
verify that no such cycles are being created. verify that no such cycles are being created.
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
this list rather than freed. to this list rather than freed.
.. versionchanged:: 3.2 .. versionchanged:: 3.2
If this list is non-empty at interpreter shutdown, a warning message If this list is non-empty at interpreter shutdown, a
gets printed. :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`: 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`: ...@@ -203,12 +201,12 @@ The following constants are provided for use with :func:`set_debug`:
.. data:: DEBUG_UNCOLLECTABLE .. data:: DEBUG_UNCOLLECTABLE
Print information of uncollectable objects found (objects which are not Print information of uncollectable objects found (objects which are not
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
the ``garbage`` list. to the ``garbage`` list.
.. versionchanged:: 3.2 .. versionchanged:: 3.2
Also print the contents of the :data:`garbage` list at interpreter 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 .. data:: DEBUG_SAVEALL
......
...@@ -82,6 +82,9 @@ following warnings category classes are currently defined: ...@@ -82,6 +82,9 @@ following warnings category classes are currently defined:
| :exc:`BytesWarning` | Base category for warnings related to | | :exc:`BytesWarning` | Base category for warnings related to |
| | :class:`bytes` and :class:`buffer`. | | | :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, While these are technically built-in exceptions, they are documented here,
......
...@@ -170,6 +170,7 @@ PyAPI_DATA(PyObject *) PyExc_FutureWarning; ...@@ -170,6 +170,7 @@ PyAPI_DATA(PyObject *) PyExc_FutureWarning;
PyAPI_DATA(PyObject *) PyExc_ImportWarning; PyAPI_DATA(PyObject *) PyExc_ImportWarning;
PyAPI_DATA(PyObject *) PyExc_UnicodeWarning; PyAPI_DATA(PyObject *) PyExc_UnicodeWarning;
PyAPI_DATA(PyObject *) PyExc_BytesWarning; PyAPI_DATA(PyObject *) PyExc_BytesWarning;
PyAPI_DATA(PyObject *) PyExc_ResourceWarning;
/* Convenience functions */ /* Convenience functions */
......
...@@ -47,3 +47,4 @@ BaseException ...@@ -47,3 +47,4 @@ BaseException
+-- ImportWarning +-- ImportWarning
+-- UnicodeWarning +-- UnicodeWarning
+-- BytesWarning +-- BytesWarning
+-- ResourceWarning
...@@ -485,7 +485,7 @@ class GCTests(unittest.TestCase): ...@@ -485,7 +485,7 @@ class GCTests(unittest.TestCase):
gc.set_debug(%s) gc.set_debug(%s)
""" """
def run_command(code): def run_command(code):
p = subprocess.Popen([sys.executable, "-c", code], p = subprocess.Popen([sys.executable, "-Wd", "-c", code],
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE) stderr=subprocess.PIPE)
stdout, stderr = p.communicate() stdout, stderr = p.communicate()
...@@ -494,11 +494,13 @@ class GCTests(unittest.TestCase): ...@@ -494,11 +494,13 @@ class GCTests(unittest.TestCase):
return strip_python_stderr(stderr) return strip_python_stderr(stderr)
stderr = run_command(code % "0") 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"<X 'first'>", stderr) self.assertNotIn(b"<X 'first'>", stderr)
# With DEBUG_UNCOLLECTABLE, the garbage list gets printed # With DEBUG_UNCOLLECTABLE, the garbage list gets printed
stderr = run_command(code % "gc.DEBUG_UNCOLLECTABLE") 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( self.assertTrue(
(b"[<X 'first'>, <X 'second'>]" in stderr) or (b"[<X 'first'>, <X 'second'>]" in stderr) or
(b"[<X 'second'>, <X 'first'>]" in stderr), stderr) (b"[<X 'second'>, <X 'first'>]" in stderr), stderr)
......
...@@ -383,4 +383,11 @@ if not _warnings_defaults: ...@@ -383,4 +383,11 @@ if not _warnings_defaults:
else: else:
bytes_action = "ignore" bytes_action = "ignore"
simplefilter(bytes_action, category=BytesWarning, append=1) 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 del _warnings_defaults
...@@ -691,7 +691,7 @@ Extensions ...@@ -691,7 +691,7 @@ Extensions
- Issue #8524: Add a detach() method to socket objects, so as to put the socket - 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. 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. - Issue #6869: Fix a refcount problem in the _ctypes extension.
......
...@@ -1368,11 +1368,16 @@ _PyGC_Fini(void) ...@@ -1368,11 +1368,16 @@ _PyGC_Fini(void)
{ {
if (!(debug & DEBUG_SAVEALL) if (!(debug & DEBUG_SAVEALL)
&& garbage != NULL && PyList_GET_SIZE(garbage) > 0) { && garbage != NULL && PyList_GET_SIZE(garbage) > 0) {
PySys_WriteStderr( char *message;
"gc: " if (debug & DEBUG_UNCOLLECTABLE)
"%" PY_FORMAT_SIZE_T "d uncollectable objects at shutdown:\n", message = "gc: %" PY_FORMAT_SIZE_T "d uncollectable objects at " \
PyList_GET_SIZE(garbage) "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) { if (debug & DEBUG_UNCOLLECTABLE) {
PyObject *repr = NULL, *bytes = NULL; PyObject *repr = NULL, *bytes = NULL;
repr = PyObject_Repr(garbage); repr = PyObject_Repr(garbage);
...@@ -1387,11 +1392,6 @@ _PyGC_Fini(void) ...@@ -1387,11 +1392,6 @@ _PyGC_Fini(void)
Py_XDECREF(repr); Py_XDECREF(repr);
Py_XDECREF(bytes); Py_XDECREF(bytes);
} }
else {
PySys_WriteStderr(
" Use gc.set_debug(gc.DEBUG_UNCOLLECTABLE) to list them.\n"
);
}
} }
} }
......
...@@ -1852,6 +1852,7 @@ SimpleExtendsException(PyExc_Warning, UnicodeWarning, ...@@ -1852,6 +1852,7 @@ SimpleExtendsException(PyExc_Warning, UnicodeWarning,
"Base class for warnings about Unicode related problems, mostly\n" "Base class for warnings about Unicode related problems, mostly\n"
"related to conversion problems."); "related to conversion problems.");
/* /*
* BytesWarning extends Warning * BytesWarning extends Warning
*/ */
...@@ -1860,6 +1861,13 @@ SimpleExtendsException(PyExc_Warning, BytesWarning, ...@@ -1860,6 +1861,13 @@ SimpleExtendsException(PyExc_Warning, BytesWarning,
"related to conversion from str or comparing to str."); "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 /* Pre-computed MemoryError instance. Best to create this as early as
* possible and not wait until a MemoryError is actually raised! * possible and not wait until a MemoryError is actually raised!
...@@ -1939,6 +1947,7 @@ _PyExc_Init(void) ...@@ -1939,6 +1947,7 @@ _PyExc_Init(void)
PRE_INIT(ImportWarning) PRE_INIT(ImportWarning)
PRE_INIT(UnicodeWarning) PRE_INIT(UnicodeWarning)
PRE_INIT(BytesWarning) PRE_INIT(BytesWarning)
PRE_INIT(ResourceWarning)
bltinmod = PyImport_ImportModule("builtins"); bltinmod = PyImport_ImportModule("builtins");
if (bltinmod == NULL) if (bltinmod == NULL)
...@@ -2001,6 +2010,7 @@ _PyExc_Init(void) ...@@ -2001,6 +2010,7 @@ _PyExc_Init(void)
POST_INIT(ImportWarning) POST_INIT(ImportWarning)
POST_INIT(UnicodeWarning) POST_INIT(UnicodeWarning)
POST_INIT(BytesWarning) POST_INIT(BytesWarning)
POST_INIT(ResourceWarning)
PyExc_MemoryErrorInst = BaseException_new(&_PyExc_MemoryError, NULL, NULL); PyExc_MemoryErrorInst = BaseException_new(&_PyExc_MemoryError, NULL, NULL);
if (!PyExc_MemoryErrorInst) if (!PyExc_MemoryErrorInst)
......
...@@ -835,6 +835,7 @@ create_filter(PyObject *category, const char *action) ...@@ -835,6 +835,7 @@ create_filter(PyObject *category, const char *action)
static PyObject *ignore_str = NULL; static PyObject *ignore_str = NULL;
static PyObject *error_str = NULL; static PyObject *error_str = NULL;
static PyObject *default_str = NULL; static PyObject *default_str = NULL;
static PyObject *always_str = NULL;
PyObject *action_obj = NULL; PyObject *action_obj = NULL;
PyObject *lineno, *result; PyObject *lineno, *result;
...@@ -862,6 +863,14 @@ create_filter(PyObject *category, const char *action) ...@@ -862,6 +863,14 @@ create_filter(PyObject *category, const char *action)
} }
action_obj = default_str; 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 { else {
Py_FatalError("unknown action"); Py_FatalError("unknown action");
} }
...@@ -879,10 +888,10 @@ static PyObject * ...@@ -879,10 +888,10 @@ static PyObject *
init_filters(void) init_filters(void)
{ {
/* Don't silence DeprecationWarning if -3 was used. */ /* 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 pos = 0; /* Post-incremented in each use. */
unsigned int x; unsigned int x;
const char *bytes_action; const char *bytes_action, *resource_action;
if (filters == NULL) if (filters == NULL)
return NULL; return NULL;
...@@ -901,7 +910,14 @@ init_filters(void) ...@@ -901,7 +910,14 @@ init_filters(void)
bytes_action = "ignore"; bytes_action = "ignore";
PyList_SET_ITEM(filters, pos++, create_filter(PyExc_BytesWarning, PyList_SET_ITEM(filters, pos++, create_filter(PyExc_BytesWarning,
bytes_action)); 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) { for (x = 0; x < pos; x += 1) {
if (PyList_GET_ITEM(filters, x) == NULL) { if (PyList_GET_ITEM(filters, x) == NULL) {
Py_DECREF(filters); Py_DECREF(filters);
......
...@@ -767,8 +767,10 @@ PyErr_WriteUnraisable(PyObject *obj) ...@@ -767,8 +767,10 @@ PyErr_WriteUnraisable(PyObject *obj)
} }
Py_XDECREF(moduleName); Py_XDECREF(moduleName);
} }
if (obj) {
PyFile_WriteString(" in ", f); PyFile_WriteString(" in ", f);
PyFile_WriteObject(obj, f, 0); PyFile_WriteObject(obj, f, 0);
}
PyFile_WriteString(" ignored\n", f); PyFile_WriteString(" ignored\n", f);
PyErr_Clear(); /* Just in case */ PyErr_Clear(); /* Just in case */
} }
......
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