mirror of
https://github.com/python/cpython.git
synced 2025-08-30 21:48:47 +00:00
bpo-32591: Add native coroutine origin tracking (#5250)
* Add coro.cr_origin and sys.set_coroutine_origin_tracking_depth * Use coroutine origin information in the unawaited coroutine warning * Stop using set_coroutine_wrapper in asyncio debug mode * In BaseEventLoop.set_debug, enable debugging in the correct thread
This commit is contained in:
parent
1211c9a989
commit
fc2f407829
20 changed files with 485 additions and 100 deletions
|
@ -1153,6 +1153,53 @@ exit:
|
|||
return ret;
|
||||
}
|
||||
|
||||
void
|
||||
_PyErr_WarnUnawaitedCoroutine(PyObject *coro)
|
||||
{
|
||||
/* First, we attempt to funnel the warning through
|
||||
warnings._warn_unawaited_coroutine.
|
||||
|
||||
This could raise an exception, due to:
|
||||
- a bug
|
||||
- some kind of shutdown-related brokenness
|
||||
- succeeding, but with an "error" warning filter installed, so the
|
||||
warning is converted into a RuntimeWarning exception
|
||||
|
||||
In the first two cases, we want to print the error (so we know what it
|
||||
is!), and then print a warning directly as a fallback. In the last
|
||||
case, we want to print the error (since it's the warning!), but *not*
|
||||
do a fallback. And after we print the error we can't check for what
|
||||
type of error it was (because PyErr_WriteUnraisable clears it), so we
|
||||
need a flag to keep track.
|
||||
|
||||
Since this is called from __del__ context, it's careful to never raise
|
||||
an exception.
|
||||
*/
|
||||
_Py_IDENTIFIER(_warn_unawaited_coroutine);
|
||||
int warned = 0;
|
||||
PyObject *fn = get_warnings_attr(&PyId__warn_unawaited_coroutine, 1);
|
||||
if (fn) {
|
||||
PyObject *res = PyObject_CallFunctionObjArgs(fn, coro, NULL);
|
||||
Py_DECREF(fn);
|
||||
if (res || PyErr_ExceptionMatches(PyExc_RuntimeWarning)) {
|
||||
warned = 1;
|
||||
}
|
||||
Py_XDECREF(res);
|
||||
}
|
||||
|
||||
if (PyErr_Occurred()) {
|
||||
PyErr_WriteUnraisable(coro);
|
||||
}
|
||||
if (!warned) {
|
||||
PyErr_WarnFormat(PyExc_RuntimeWarning, 1,
|
||||
"coroutine '%.50S' was never awaited",
|
||||
((PyCoroObject *)coro)->cr_qualname);
|
||||
/* Maybe *that* got converted into an exception */
|
||||
if (PyErr_Occurred()) {
|
||||
PyErr_WriteUnraisable(coro);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PyDoc_STRVAR(warn_explicit_doc,
|
||||
"Low-level inferface to warnings functionality.");
|
||||
|
|
|
@ -4387,6 +4387,21 @@ PyEval_SetTrace(Py_tracefunc func, PyObject *arg)
|
|||
|| (tstate->c_profilefunc != NULL));
|
||||
}
|
||||
|
||||
void
|
||||
_PyEval_SetCoroutineOriginTrackingDepth(int new_depth)
|
||||
{
|
||||
assert(new_depth >= 0);
|
||||
PyThreadState *tstate = PyThreadState_GET();
|
||||
tstate->coroutine_origin_tracking_depth = new_depth;
|
||||
}
|
||||
|
||||
int
|
||||
_PyEval_GetCoroutineOriginTrackingDepth(void)
|
||||
{
|
||||
PyThreadState *tstate = PyThreadState_GET();
|
||||
return tstate->coroutine_origin_tracking_depth;
|
||||
}
|
||||
|
||||
void
|
||||
_PyEval_SetCoroutineWrapper(PyObject *wrapper)
|
||||
{
|
||||
|
|
66
Python/clinic/sysmodule.c.h
Normal file
66
Python/clinic/sysmodule.c.h
Normal file
|
@ -0,0 +1,66 @@
|
|||
/*[clinic input]
|
||||
preserve
|
||||
[clinic start generated code]*/
|
||||
|
||||
PyDoc_STRVAR(sys_set_coroutine_origin_tracking_depth__doc__,
|
||||
"set_coroutine_origin_tracking_depth($module, /, depth)\n"
|
||||
"--\n"
|
||||
"\n"
|
||||
"Enable or disable origin tracking for coroutine objects in this thread.\n"
|
||||
"\n"
|
||||
"Coroutine objects will track \'depth\' frames of traceback information about\n"
|
||||
"where they came from, available in their cr_origin attribute. Set depth of 0\n"
|
||||
"to disable.");
|
||||
|
||||
#define SYS_SET_COROUTINE_ORIGIN_TRACKING_DEPTH_METHODDEF \
|
||||
{"set_coroutine_origin_tracking_depth", (PyCFunction)sys_set_coroutine_origin_tracking_depth, METH_FASTCALL|METH_KEYWORDS, sys_set_coroutine_origin_tracking_depth__doc__},
|
||||
|
||||
static PyObject *
|
||||
sys_set_coroutine_origin_tracking_depth_impl(PyObject *module, int depth);
|
||||
|
||||
static PyObject *
|
||||
sys_set_coroutine_origin_tracking_depth(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
|
||||
{
|
||||
PyObject *return_value = NULL;
|
||||
static const char * const _keywords[] = {"depth", NULL};
|
||||
static _PyArg_Parser _parser = {"i:set_coroutine_origin_tracking_depth", _keywords, 0};
|
||||
int depth;
|
||||
|
||||
if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser,
|
||||
&depth)) {
|
||||
goto exit;
|
||||
}
|
||||
return_value = sys_set_coroutine_origin_tracking_depth_impl(module, depth);
|
||||
|
||||
exit:
|
||||
return return_value;
|
||||
}
|
||||
|
||||
PyDoc_STRVAR(sys_get_coroutine_origin_tracking_depth__doc__,
|
||||
"get_coroutine_origin_tracking_depth($module, /)\n"
|
||||
"--\n"
|
||||
"\n"
|
||||
"Check status of origin tracking for coroutine objects in this thread.");
|
||||
|
||||
#define SYS_GET_COROUTINE_ORIGIN_TRACKING_DEPTH_METHODDEF \
|
||||
{"get_coroutine_origin_tracking_depth", (PyCFunction)sys_get_coroutine_origin_tracking_depth, METH_NOARGS, sys_get_coroutine_origin_tracking_depth__doc__},
|
||||
|
||||
static int
|
||||
sys_get_coroutine_origin_tracking_depth_impl(PyObject *module);
|
||||
|
||||
static PyObject *
|
||||
sys_get_coroutine_origin_tracking_depth(PyObject *module, PyObject *Py_UNUSED(ignored))
|
||||
{
|
||||
PyObject *return_value = NULL;
|
||||
int _return_value;
|
||||
|
||||
_return_value = sys_get_coroutine_origin_tracking_depth_impl(module);
|
||||
if ((_return_value == -1) && PyErr_Occurred()) {
|
||||
goto exit;
|
||||
}
|
||||
return_value = PyLong_FromLong((long)_return_value);
|
||||
|
||||
exit:
|
||||
return return_value;
|
||||
}
|
||||
/*[clinic end generated code: output=4a3ac42b97d710ff input=a9049054013a1b77]*/
|
|
@ -305,6 +305,8 @@ new_threadstate(PyInterpreterState *interp, int init)
|
|||
tstate->on_delete = NULL;
|
||||
tstate->on_delete_data = NULL;
|
||||
|
||||
tstate->coroutine_origin_tracking_depth = 0;
|
||||
|
||||
tstate->coroutine_wrapper = NULL;
|
||||
tstate->in_coroutine_wrapper = 0;
|
||||
|
||||
|
|
|
@ -34,6 +34,13 @@ extern void *PyWin_DLLhModule;
|
|||
extern const char *PyWin_DLLVersionString;
|
||||
#endif
|
||||
|
||||
/*[clinic input]
|
||||
module sys
|
||||
[clinic start generated code]*/
|
||||
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=3726b388feee8cea]*/
|
||||
|
||||
#include "clinic/sysmodule.c.h"
|
||||
|
||||
_Py_IDENTIFIER(_);
|
||||
_Py_IDENTIFIER(__sizeof__);
|
||||
_Py_IDENTIFIER(_xoptions);
|
||||
|
@ -710,9 +717,51 @@ sys_setrecursionlimit(PyObject *self, PyObject *args)
|
|||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
/*[clinic input]
|
||||
sys.set_coroutine_origin_tracking_depth
|
||||
|
||||
depth: int
|
||||
|
||||
Enable or disable origin tracking for coroutine objects in this thread.
|
||||
|
||||
Coroutine objects will track 'depth' frames of traceback information about
|
||||
where they came from, available in their cr_origin attribute. Set depth of 0
|
||||
to disable.
|
||||
[clinic start generated code]*/
|
||||
|
||||
static PyObject *
|
||||
sys_set_coroutine_origin_tracking_depth_impl(PyObject *module, int depth)
|
||||
/*[clinic end generated code: output=0a2123c1cc6759c5 input=9083112cccc1bdcb]*/
|
||||
{
|
||||
if (depth < 0) {
|
||||
PyErr_SetString(PyExc_ValueError, "depth must be >= 0");
|
||||
return NULL;
|
||||
}
|
||||
_PyEval_SetCoroutineOriginTrackingDepth(depth);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
/*[clinic input]
|
||||
sys.get_coroutine_origin_tracking_depth -> int
|
||||
|
||||
Check status of origin tracking for coroutine objects in this thread.
|
||||
[clinic start generated code]*/
|
||||
|
||||
static int
|
||||
sys_get_coroutine_origin_tracking_depth_impl(PyObject *module)
|
||||
/*[clinic end generated code: output=3699f7be95a3afb8 input=335266a71205b61a]*/
|
||||
{
|
||||
return _PyEval_GetCoroutineOriginTrackingDepth();
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
sys_set_coroutine_wrapper(PyObject *self, PyObject *wrapper)
|
||||
{
|
||||
if (PyErr_WarnEx(PyExc_DeprecationWarning,
|
||||
"set_coroutine_wrapper is deprecated", 1) < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (wrapper != Py_None) {
|
||||
if (!PyCallable_Check(wrapper)) {
|
||||
PyErr_Format(PyExc_TypeError,
|
||||
|
@ -737,6 +786,10 @@ Set a wrapper for coroutine objects."
|
|||
static PyObject *
|
||||
sys_get_coroutine_wrapper(PyObject *self, PyObject *args)
|
||||
{
|
||||
if (PyErr_WarnEx(PyExc_DeprecationWarning,
|
||||
"get_coroutine_wrapper is deprecated", 1) < 0) {
|
||||
return NULL;
|
||||
}
|
||||
PyObject *wrapper = _PyEval_GetCoroutineWrapper();
|
||||
if (wrapper == NULL) {
|
||||
wrapper = Py_None;
|
||||
|
@ -1512,6 +1565,8 @@ static PyMethodDef sys_methods[] = {
|
|||
{"call_tracing", sys_call_tracing, METH_VARARGS, call_tracing_doc},
|
||||
{"_debugmallocstats", sys_debugmallocstats, METH_NOARGS,
|
||||
debugmallocstats_doc},
|
||||
SYS_SET_COROUTINE_ORIGIN_TRACKING_DEPTH_METHODDEF
|
||||
SYS_GET_COROUTINE_ORIGIN_TRACKING_DEPTH_METHODDEF
|
||||
{"set_coroutine_wrapper", sys_set_coroutine_wrapper, METH_O,
|
||||
set_coroutine_wrapper_doc},
|
||||
{"get_coroutine_wrapper", sys_get_coroutine_wrapper, METH_NOARGS,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue