summaryrefslogtreecommitdiffstats
path: root/Python
diff options
context:
space:
mode:
authorNathaniel J. Smith <njs@pobox.com>2018-01-21 14:44:07 (GMT)
committerYury Selivanov <yury@magic.io>2018-01-21 14:44:07 (GMT)
commitfc2f407829d9817ddacccae6944dd0879cfaca24 (patch)
tree1775a28a8181975363798f9b3e7cb2bb100e49a2 /Python
parent1211c9a9897a174b7261ca258cabf289815a40d8 (diff)
downloadcpython-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.c47
-rw-r--r--Python/ceval.c15
-rw-r--r--Python/clinic/sysmodule.c.h66
-rw-r--r--Python/pystate.c2
-rw-r--r--Python/sysmodule.c55
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,