diff options
author | Nathaniel J. Smith <njs@pobox.com> | 2018-01-21 14:44:07 (GMT) |
---|---|---|
committer | Yury Selivanov <yury@magic.io> | 2018-01-21 14:44:07 (GMT) |
commit | fc2f407829d9817ddacccae6944dd0879cfaca24 (patch) | |
tree | 1775a28a8181975363798f9b3e7cb2bb100e49a2 /Python | |
parent | 1211c9a9897a174b7261ca258cabf289815a40d8 (diff) | |
download | cpython-fc2f407829d9817ddacccae6944dd0879cfaca24.zip cpython-fc2f407829d9817ddacccae6944dd0879cfaca24.tar.gz cpython-fc2f407829d9817ddacccae6944dd0879cfaca24.tar.bz2 |
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
Diffstat (limited to 'Python')
-rw-r--r-- | Python/_warnings.c | 47 | ||||
-rw-r--r-- | Python/ceval.c | 15 | ||||
-rw-r--r-- | Python/clinic/sysmodule.c.h | 66 | ||||
-rw-r--r-- | Python/pystate.c | 2 | ||||
-rw-r--r-- | Python/sysmodule.c | 55 |
5 files changed, 185 insertions, 0 deletions
diff --git a/Python/_warnings.c b/Python/_warnings.c index c286364..c3417cc 100644 --- a/Python/_warnings.c +++ b/Python/_warnings.c @@ -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."); diff --git a/Python/ceval.c b/Python/ceval.c index 9276755..2b7c0c8 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -4388,6 +4388,21 @@ PyEval_SetTrace(Py_tracefunc func, PyObject *arg) } 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) { PyThreadState *tstate = PyThreadState_GET(); diff --git a/Python/clinic/sysmodule.c.h b/Python/clinic/sysmodule.c.h new file mode 100644 index 0000000..3e14805 --- /dev/null +++ b/Python/clinic/sysmodule.c.h @@ -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]*/ diff --git a/Python/pystate.c b/Python/pystate.c index 028292e..9c25a26 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -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; diff --git a/Python/sysmodule.c b/Python/sysmodule.c index c054243..873657f 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -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, |