On ResourceWarning, log traceback where the object was allocated

Issue #26567:

* Add a new function PyErr_ResourceWarning() function to pass the destroyed
  object
* Add a source attribute to warnings.WarningMessage
* Add warnings._showwarnmsg() which uses tracemalloc to get the traceback where
  source object was allocated.
This commit is contained in:
Victor Stinner 2016-03-19 01:03:51 +01:00
parent 1231a4615f
commit 914cde89d4
11 changed files with 175 additions and 40 deletions

View File

@ -334,6 +334,14 @@ an error value).
.. versionadded:: 3.2 .. versionadded:: 3.2
.. c:function:: int PyErr_ResourceWarning(PyObject *source, Py_ssize_t stack_level, const char *format, ...)
Function similar to :c:func:`PyErr_WarnFormat`, but *category* is
:exc:`ResourceWarning` and pass *source* to :func:`warnings.WarningMessage`.
.. versionadded:: 3.6
Querying the error indicator Querying the error indicator
============================ ============================

View File

@ -319,7 +319,7 @@ Available Functions
of the warning message). of the warning message).
.. function:: warn_explicit(message, category, filename, lineno, module=None, registry=None, module_globals=None) .. function:: warn_explicit(message, category, filename, lineno, module=None, registry=None, module_globals=None, source=None)
This is a low-level interface to the functionality of :func:`warn`, passing in This is a low-level interface to the functionality of :func:`warn`, passing in
explicitly the message, category, filename and line number, and optionally the explicitly the message, category, filename and line number, and optionally the
@ -335,6 +335,12 @@ Available Functions
source for modules found in zipfiles or other non-filesystem import source for modules found in zipfiles or other non-filesystem import
sources). sources).
*source*, if supplied, is the destroyed object which emitted a
:exc:`ResourceWarning`.
.. versionchanged:: 3.6
Add the *source* parameter.
.. function:: showwarning(message, category, filename, lineno, file=None, line=None) .. function:: showwarning(message, category, filename, lineno, file=None, line=None)

View File

@ -258,6 +258,40 @@ urllib.robotparser
(Contributed by Nikolay Bogoychev in :issue:`16099`.) (Contributed by Nikolay Bogoychev in :issue:`16099`.)
warnings
--------
A new optional *source* parameter has been added to the
:func:`warnings.warn_explicit` function: the destroyed object which emitted a
:exc:`ResourceWarning`. A *source* attribute has also been added to
:class:`warnings.WarningMessage` (contributed by Victor Stinner in
:issue:`26568` and :issue:`26567`).
When a :exc:`ResourceWarning` warning is logged, the :mod:`tracemalloc` is now
used to try to retrieve the traceback where the detroyed object was allocated.
Example with the script ``example.py``::
def func():
f = open(__file__)
f = None
func()
Output of the command ``python3.6 -Wd -X tracemalloc=5 example.py``::
example.py:3: ResourceWarning: unclosed file <...>
f = None
Object allocated at (most recent call first):
File "example.py", lineno 2
f = open(__file__)
File "example.py", lineno 5
func()
The "Object allocated at" traceback is new and only displayed if
:mod:`tracemalloc` is tracing Python memory allocations.
zipfile zipfile
------- -------

View File

@ -17,6 +17,13 @@ PyAPI_FUNC(int) PyErr_WarnFormat(
Py_ssize_t stack_level, Py_ssize_t stack_level,
const char *format, /* ASCII-encoded string */ const char *format, /* ASCII-encoded string */
...); ...);
/* Emit a ResourceWarning warning */
PyAPI_FUNC(int) PyErr_ResourceWarning(
PyObject *source,
Py_ssize_t stack_level,
const char *format, /* ASCII-encoded string */
...);
#ifndef Py_LIMITED_API #ifndef Py_LIMITED_API
PyAPI_FUNC(int) PyErr_WarnExplicitObject( PyAPI_FUNC(int) PyErr_WarnExplicitObject(
PyObject *category, PyObject *category,

View File

@ -2,7 +2,10 @@ from contextlib import contextmanager
import linecache import linecache
import os import os
from io import StringIO from io import StringIO
import re
import sys import sys
import tempfile
import textwrap
import unittest import unittest
from test import support from test import support
from test.support.script_helper import assert_python_ok, assert_python_failure from test.support.script_helper import assert_python_ok, assert_python_failure
@ -763,12 +766,39 @@ class WarningsDisplayTests(BaseTest):
file_object, expected_file_line) file_object, expected_file_line)
self.assertEqual(expect, file_object.getvalue()) self.assertEqual(expect, file_object.getvalue())
class CWarningsDisplayTests(WarningsDisplayTests, unittest.TestCase): class CWarningsDisplayTests(WarningsDisplayTests, unittest.TestCase):
module = c_warnings module = c_warnings
class PyWarningsDisplayTests(WarningsDisplayTests, unittest.TestCase): class PyWarningsDisplayTests(WarningsDisplayTests, unittest.TestCase):
module = py_warnings module = py_warnings
def test_tracemalloc(self):
with tempfile.NamedTemporaryFile("w", suffix=".py") as tmpfile:
tmpfile.write(textwrap.dedent("""
def func():
f = open(__file__)
# Emit ResourceWarning
f = None
func()
"""))
tmpfile.flush()
fname = tmpfile.name
res = assert_python_ok('-Wd', '-X', 'tracemalloc=2', fname)
stderr = res.err.decode('ascii', 'replace')
stderr = re.sub('<.*>', '<...>', stderr)
expected = textwrap.dedent(f'''
{fname}:5: ResourceWarning: unclosed file <...>
f = None
Object allocated at (most recent call first):
File "{fname}", lineno 3
f = open(__file__)
File "{fname}", lineno 7
func()
''').strip()
self.assertEqual(stderr, expected)
class CatchWarningTests(BaseTest): class CatchWarningTests(BaseTest):

View File

@ -2,6 +2,7 @@
import sys import sys
__all__ = ["warn", "warn_explicit", "showwarning", __all__ = ["warn", "warn_explicit", "showwarning",
"formatwarning", "filterwarnings", "simplefilter", "formatwarning", "filterwarnings", "simplefilter",
"resetwarnings", "catch_warnings"] "resetwarnings", "catch_warnings"]
@ -66,6 +67,18 @@ def _formatwarnmsg(msg):
if line: if line:
line = line.strip() line = line.strip()
s += " %s\n" % line s += " %s\n" % line
if msg.source is not None:
import tracemalloc
tb = tracemalloc.get_object_traceback(msg.source)
if tb is not None:
s += 'Object allocated at (most recent call first):\n'
for frame in tb:
s += (' File "%s", lineno %s\n'
% (frame.filename, frame.lineno))
line = linecache.getline(frame.filename, frame.lineno)
if line:
line = line.strip()
s += ' %s\n' % line
return s return s
def filterwarnings(action, message="", category=Warning, module="", lineno=0, def filterwarnings(action, message="", category=Warning, module="", lineno=0,
@ -267,7 +280,8 @@ def warn(message, category=None, stacklevel=1):
globals) globals)
def warn_explicit(message, category, filename, lineno, def warn_explicit(message, category, filename, lineno,
module=None, registry=None, module_globals=None): module=None, registry=None, module_globals=None,
source=None):
lineno = int(lineno) lineno = int(lineno)
if module is None: if module is None:
module = filename or "<unknown>" module = filename or "<unknown>"
@ -333,17 +347,17 @@ def warn_explicit(message, category, filename, lineno,
"Unrecognized action (%r) in warnings.filters:\n %s" % "Unrecognized action (%r) in warnings.filters:\n %s" %
(action, item)) (action, item))
# Print message and context # Print message and context
msg = WarningMessage(message, category, filename, lineno) msg = WarningMessage(message, category, filename, lineno, source)
_showwarnmsg(msg) _showwarnmsg(msg)
class WarningMessage(object): class WarningMessage(object):
_WARNING_DETAILS = ("message", "category", "filename", "lineno", "file", _WARNING_DETAILS = ("message", "category", "filename", "lineno", "file",
"line") "line", "source")
def __init__(self, message, category, filename, lineno, file=None, def __init__(self, message, category, filename, lineno, file=None,
line=None): line=None, source=None):
local_values = locals() local_values = locals()
for attr in self._WARNING_DETAILS: for attr in self._WARNING_DETAILS:
setattr(self, attr, local_values[attr]) setattr(self, attr, local_values[attr])

View File

@ -226,6 +226,11 @@ Core and Builtins
Library Library
------- -------
- Issue #26567: Add a new function :c:func:`PyErr_ResourceWarning` function to
pass the destroyed object. Add a *source* attribute to
:class:`warnings.WarningMessage`. Add warnings._showwarnmsg() which uses
tracemalloc to get the traceback where source object was allocated.
- Issue #26313: ssl.py _load_windows_store_certs fails if windows cert store - Issue #26313: ssl.py _load_windows_store_certs fails if windows cert store
is empty. Patch by Baji. is empty. Patch by Baji.

View File

@ -92,8 +92,7 @@ fileio_dealloc_warn(fileio *self, PyObject *source)
if (self->fd >= 0 && self->closefd) { if (self->fd >= 0 && self->closefd) {
PyObject *exc, *val, *tb; PyObject *exc, *val, *tb;
PyErr_Fetch(&exc, &val, &tb); PyErr_Fetch(&exc, &val, &tb);
if (PyErr_WarnFormat(PyExc_ResourceWarning, 1, if (PyErr_ResourceWarning(source, 1, "unclosed file %R", source)) {
"unclosed file %R", source)) {
/* Spurious errors can appear at shutdown */ /* Spurious errors can appear at shutdown */
if (PyErr_ExceptionMatches(PyExc_Warning)) if (PyErr_ExceptionMatches(PyExc_Warning))
PyErr_WriteUnraisable((PyObject *) self); PyErr_WriteUnraisable((PyObject *) self);

View File

@ -12111,8 +12111,8 @@ ScandirIterator_dealloc(ScandirIterator *iterator)
*/ */
++Py_REFCNT(iterator); ++Py_REFCNT(iterator);
PyErr_Fetch(&exc, &val, &tb); PyErr_Fetch(&exc, &val, &tb);
if (PyErr_WarnFormat(PyExc_ResourceWarning, 1, if (PyErr_ResourceWarning((PyObject *)iterator, 1,
"unclosed scandir iterator %R", iterator)) { "unclosed scandir iterator %R", iterator)) {
/* Spurious errors can appear at shutdown */ /* Spurious errors can appear at shutdown */
if (PyErr_ExceptionMatches(PyExc_Warning)) if (PyErr_ExceptionMatches(PyExc_Warning))
PyErr_WriteUnraisable((PyObject *) iterator); PyErr_WriteUnraisable((PyObject *) iterator);

View File

@ -4170,8 +4170,7 @@ sock_dealloc(PySocketSockObject *s)
Py_ssize_t old_refcount = Py_REFCNT(s); Py_ssize_t old_refcount = Py_REFCNT(s);
++Py_REFCNT(s); ++Py_REFCNT(s);
PyErr_Fetch(&exc, &val, &tb); PyErr_Fetch(&exc, &val, &tb);
if (PyErr_WarnFormat(PyExc_ResourceWarning, 1, if (PyErr_ResourceWarning(s, 1, "unclosed %R", s))
"unclosed %R", s))
/* Spurious errors can appear at shutdown */ /* Spurious errors can appear at shutdown */
if (PyErr_ExceptionMatches(PyExc_Warning)) if (PyErr_ExceptionMatches(PyExc_Warning))
PyErr_WriteUnraisable((PyObject *) s); PyErr_WriteUnraisable((PyObject *) s);

View File

@ -287,8 +287,8 @@ update_registry(PyObject *registry, PyObject *text, PyObject *category,
} }
static void static void
show_warning(PyObject *filename, int lineno, PyObject *text, PyObject show_warning(PyObject *filename, int lineno, PyObject *text,
*category, PyObject *sourceline) PyObject *category, PyObject *sourceline)
{ {
PyObject *f_stderr; PyObject *f_stderr;
PyObject *name; PyObject *name;
@ -362,7 +362,7 @@ error:
static int static int
call_show_warning(PyObject *category, PyObject *text, PyObject *message, call_show_warning(PyObject *category, PyObject *text, PyObject *message,
PyObject *filename, int lineno, PyObject *lineno_obj, PyObject *filename, int lineno, PyObject *lineno_obj,
PyObject *sourceline) PyObject *sourceline, PyObject *source)
{ {
PyObject *show_fn, *msg, *res, *warnmsg_cls = NULL; PyObject *show_fn, *msg, *res, *warnmsg_cls = NULL;
@ -388,7 +388,7 @@ call_show_warning(PyObject *category, PyObject *text, PyObject *message,
} }
msg = PyObject_CallFunctionObjArgs(warnmsg_cls, message, category, msg = PyObject_CallFunctionObjArgs(warnmsg_cls, message, category,
filename, lineno_obj, filename, lineno_obj, Py_None, Py_None, source,
NULL); NULL);
Py_DECREF(warnmsg_cls); Py_DECREF(warnmsg_cls);
if (msg == NULL) if (msg == NULL)
@ -412,7 +412,8 @@ error:
static PyObject * static PyObject *
warn_explicit(PyObject *category, PyObject *message, warn_explicit(PyObject *category, PyObject *message,
PyObject *filename, int lineno, PyObject *filename, int lineno,
PyObject *module, PyObject *registry, PyObject *sourceline) PyObject *module, PyObject *registry, PyObject *sourceline,
PyObject *source)
{ {
PyObject *key = NULL, *text = NULL, *result = NULL, *lineno_obj = NULL; PyObject *key = NULL, *text = NULL, *result = NULL, *lineno_obj = NULL;
PyObject *item = NULL; PyObject *item = NULL;
@ -521,7 +522,7 @@ warn_explicit(PyObject *category, PyObject *message,
goto return_none; goto return_none;
if (rc == 0) { if (rc == 0) {
if (call_show_warning(category, text, message, filename, lineno, if (call_show_warning(category, text, message, filename, lineno,
lineno_obj, sourceline) < 0) lineno_obj, sourceline, source) < 0)
goto cleanup; goto cleanup;
} }
else /* if (rc == -1) */ else /* if (rc == -1) */
@ -766,7 +767,8 @@ get_category(PyObject *message, PyObject *category)
} }
static PyObject * static PyObject *
do_warn(PyObject *message, PyObject *category, Py_ssize_t stack_level) do_warn(PyObject *message, PyObject *category, Py_ssize_t stack_level,
PyObject *source)
{ {
PyObject *filename, *module, *registry, *res; PyObject *filename, *module, *registry, *res;
int lineno; int lineno;
@ -775,7 +777,7 @@ do_warn(PyObject *message, PyObject *category, Py_ssize_t stack_level)
return NULL; return NULL;
res = warn_explicit(category, message, filename, lineno, module, registry, res = warn_explicit(category, message, filename, lineno, module, registry,
NULL); NULL, source);
Py_DECREF(filename); Py_DECREF(filename);
Py_DECREF(registry); Py_DECREF(registry);
Py_DECREF(module); Py_DECREF(module);
@ -796,14 +798,15 @@ warnings_warn(PyObject *self, PyObject *args, PyObject *kwds)
category = get_category(message, category); category = get_category(message, category);
if (category == NULL) if (category == NULL)
return NULL; return NULL;
return do_warn(message, category, stack_level); return do_warn(message, category, stack_level, NULL);
} }
static PyObject * static PyObject *
warnings_warn_explicit(PyObject *self, PyObject *args, PyObject *kwds) warnings_warn_explicit(PyObject *self, PyObject *args, PyObject *kwds)
{ {
static char *kwd_list[] = {"message", "category", "filename", "lineno", static char *kwd_list[] = {"message", "category", "filename", "lineno",
"module", "registry", "module_globals", 0}; "module", "registry", "module_globals",
"source", 0};
PyObject *message; PyObject *message;
PyObject *category; PyObject *category;
PyObject *filename; PyObject *filename;
@ -811,10 +814,11 @@ warnings_warn_explicit(PyObject *self, PyObject *args, PyObject *kwds)
PyObject *module = NULL; PyObject *module = NULL;
PyObject *registry = NULL; PyObject *registry = NULL;
PyObject *module_globals = NULL; PyObject *module_globals = NULL;
PyObject *sourceobj = NULL;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "OOUi|OOO:warn_explicit", if (!PyArg_ParseTupleAndKeywords(args, kwds, "OOUi|OOOO:warn_explicit",
kwd_list, &message, &category, &filename, &lineno, &module, kwd_list, &message, &category, &filename, &lineno, &module,
&registry, &module_globals)) &registry, &module_globals, &sourceobj))
return NULL; return NULL;
if (module_globals) { if (module_globals) {
@ -870,14 +874,14 @@ warnings_warn_explicit(PyObject *self, PyObject *args, PyObject *kwds)
/* Handle the warning. */ /* Handle the warning. */
returned = warn_explicit(category, message, filename, lineno, module, returned = warn_explicit(category, message, filename, lineno, module,
registry, source_line); registry, source_line, sourceobj);
Py_DECREF(source_list); Py_DECREF(source_list);
return returned; return returned;
} }
standard_call: standard_call:
return warn_explicit(category, message, filename, lineno, module, return warn_explicit(category, message, filename, lineno, module,
registry, NULL); registry, NULL, sourceobj);
} }
static PyObject * static PyObject *
@ -892,14 +896,14 @@ warnings_filters_mutated(PyObject *self, PyObject *args)
static int static int
warn_unicode(PyObject *category, PyObject *message, warn_unicode(PyObject *category, PyObject *message,
Py_ssize_t stack_level) Py_ssize_t stack_level, PyObject *source)
{ {
PyObject *res; PyObject *res;
if (category == NULL) if (category == NULL)
category = PyExc_RuntimeWarning; category = PyExc_RuntimeWarning;
res = do_warn(message, category, stack_level); res = do_warn(message, category, stack_level, source);
if (res == NULL) if (res == NULL)
return -1; return -1;
Py_DECREF(res); Py_DECREF(res);
@ -907,12 +911,28 @@ warn_unicode(PyObject *category, PyObject *message,
return 0; return 0;
} }
static int
_PyErr_WarnFormatV(PyObject *source,
PyObject *category, Py_ssize_t stack_level,
const char *format, va_list vargs)
{
PyObject *message;
int res;
message = PyUnicode_FromFormatV(format, vargs);
if (message == NULL)
return -1;
res = warn_unicode(category, message, stack_level, source);
Py_DECREF(message);
return res;
}
int int
PyErr_WarnFormat(PyObject *category, Py_ssize_t stack_level, PyErr_WarnFormat(PyObject *category, Py_ssize_t stack_level,
const char *format, ...) const char *format, ...)
{ {
int ret; int res;
PyObject *message;
va_list vargs; va_list vargs;
#ifdef HAVE_STDARG_PROTOTYPES #ifdef HAVE_STDARG_PROTOTYPES
@ -920,17 +940,30 @@ PyErr_WarnFormat(PyObject *category, Py_ssize_t stack_level,
#else #else
va_start(vargs); va_start(vargs);
#endif #endif
message = PyUnicode_FromFormatV(format, vargs); res = _PyErr_WarnFormatV(NULL, category, stack_level, format, vargs);
if (message != NULL) {
ret = warn_unicode(category, message, stack_level);
Py_DECREF(message);
}
else
ret = -1;
va_end(vargs); va_end(vargs);
return ret; return res;
} }
int
PyErr_ResourceWarning(PyObject *source, Py_ssize_t stack_level,
const char *format, ...)
{
int res;
va_list vargs;
#ifdef HAVE_STDARG_PROTOTYPES
va_start(vargs, format);
#else
va_start(vargs);
#endif
res = _PyErr_WarnFormatV(source, PyExc_ResourceWarning,
stack_level, format, vargs);
va_end(vargs);
return res;
}
int int
PyErr_WarnEx(PyObject *category, const char *text, Py_ssize_t stack_level) PyErr_WarnEx(PyObject *category, const char *text, Py_ssize_t stack_level)
{ {
@ -938,7 +971,7 @@ PyErr_WarnEx(PyObject *category, const char *text, Py_ssize_t stack_level)
PyObject *message = PyUnicode_FromString(text); PyObject *message = PyUnicode_FromString(text);
if (message == NULL) if (message == NULL)
return -1; return -1;
ret = warn_unicode(category, message, stack_level); ret = warn_unicode(category, message, stack_level, NULL);
Py_DECREF(message); Py_DECREF(message);
return ret; return ret;
} }
@ -964,7 +997,7 @@ PyErr_WarnExplicitObject(PyObject *category, PyObject *message,
if (category == NULL) if (category == NULL)
category = PyExc_RuntimeWarning; category = PyExc_RuntimeWarning;
res = warn_explicit(category, message, filename, lineno, res = warn_explicit(category, message, filename, lineno,
module, registry, NULL); module, registry, NULL, NULL);
if (res == NULL) if (res == NULL)
return -1; return -1;
Py_DECREF(res); Py_DECREF(res);
@ -1028,7 +1061,7 @@ PyErr_WarnExplicitFormat(PyObject *category,
if (message != NULL) { if (message != NULL) {
PyObject *res; PyObject *res;
res = warn_explicit(category, message, filename, lineno, res = warn_explicit(category, message, filename, lineno,
module, registry, NULL); module, registry, NULL, NULL);
Py_DECREF(message); Py_DECREF(message);
if (res != NULL) { if (res != NULL) {
Py_DECREF(res); Py_DECREF(res);