-
-
Notifications
You must be signed in to change notification settings - Fork 30k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
bpo-32591: Add native coroutine origin tracking #5250
Changes from 6 commits
091dc24
d504810
5d8f591
82d0c3e
6c7f73a
7738cc4
2157af3
b0e52ec
c0feb3b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1212,6 +1212,27 @@ always available. | |
This function has been added on a provisional basis (see :pep:`411` | ||
for details.) | ||
|
||
.. function:: set_coroutine_origin_tracking_depth(depth) | ||
|
||
Allows enabling or disabling coroutine origin tracking. When | ||
enabled, the ``cr_origin`` attribute on coroutine objects will | ||
contain a list of (filename, line number, function name) tuples | ||
describing the traceback where the coroutine object was created. | ||
When disabled, ``cr_origin`` will be None. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need to specify how the list is ordered. |
||
|
||
To enable, pass a *depth* value greater than zero; this sets the | ||
number of frames whose information will be captured. To disable, | ||
pass set *depth* to zero. | ||
|
||
Returns the old value of *depth*. | ||
|
||
This setting is thread-local. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thread-local -> thread-specific |
||
|
||
.. versionadded:: 3.7 | ||
|
||
.. note:: | ||
This function has been added on a provisional basis (see :pep:`411` | ||
for details.) Use it only for debugging purposes. | ||
|
||
.. function:: set_coroutine_wrapper(wrapper) | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -56,6 +56,10 @@ PyErr_WarnExplicitFormat(PyObject *category, | |
#define PyErr_Warn(category, msg) PyErr_WarnEx(category, msg, 1) | ||
#endif | ||
|
||
#ifndef Py_LIMITED_API | ||
PyAPI_FUNC(void) _PyErr_WarnUnawaitedCoroutine(PyObject *coro); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Strictly speaking we don't need |
||
#endif | ||
|
||
#ifdef __cplusplus | ||
} | ||
#endif | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -40,6 +40,7 @@ | |
from . import sslproto | ||
from . import tasks | ||
from .log import logger | ||
from .constants import DEBUG_STACK_DEPTH | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In asyncio we always import modules, so please change to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. like in the line above, (yes I fixed it) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That logger import is probably the only offender, will fix it in a separate PR :) |
||
|
||
|
||
__all__ = 'BaseEventLoop', | ||
|
@@ -224,7 +225,8 @@ def __init__(self): | |
self.slow_callback_duration = 0.1 | ||
self._current_handle = None | ||
self._task_factory = None | ||
self._coroutine_wrapper_set = False | ||
self._coroutine_origin_tracking_enabled = False | ||
self._coroutine_origin_tracking_saved_depth = None | ||
|
||
if hasattr(sys, 'get_asyncgen_hooks'): | ||
# Python >= 3.6 | ||
|
@@ -382,7 +384,7 @@ def run_forever(self): | |
if events._get_running_loop() is not None: | ||
raise RuntimeError( | ||
'Cannot run the event loop while another loop is running') | ||
self._set_coroutine_wrapper(self._debug) | ||
self._set_coroutine_origin_tracking(self._debug) | ||
self._thread_id = threading.get_ident() | ||
if self._asyncgens is not None: | ||
old_agen_hooks = sys.get_asyncgen_hooks() | ||
|
@@ -398,7 +400,7 @@ def run_forever(self): | |
self._stopping = False | ||
self._thread_id = None | ||
events._set_running_loop(None) | ||
self._set_coroutine_wrapper(False) | ||
self._set_coroutine_origin_tracking(False) | ||
if self._asyncgens is not None: | ||
sys.set_asyncgen_hooks(*old_agen_hooks) | ||
|
||
|
@@ -1531,39 +1533,20 @@ def _run_once(self): | |
handle._run() | ||
handle = None # Needed to break cycles when an exception occurs. | ||
|
||
def _set_coroutine_wrapper(self, enabled): | ||
try: | ||
set_wrapper = sys.set_coroutine_wrapper | ||
get_wrapper = sys.get_coroutine_wrapper | ||
except AttributeError: | ||
return | ||
|
||
enabled = bool(enabled) | ||
if self._coroutine_wrapper_set == enabled: | ||
def _set_coroutine_origin_tracking(self, enabled): | ||
if enabled == self._coroutine_origin_tracking_enabled: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I don't like using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, that would be different – the no- if ((enabled and self._coroutine_origin_tracking_enabled) or (not enabled and not self._coroutine_origin_tracking_enabled)):
... I find the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Got it, then I'd cast them both to |
||
return | ||
|
||
wrapper = coroutines.debug_wrapper | ||
current_wrapper = get_wrapper() | ||
|
||
if enabled: | ||
if current_wrapper not in (None, wrapper): | ||
warnings.warn( | ||
f"loop.set_debug(True): cannot set debug coroutine " | ||
f"wrapper; another wrapper is already set " | ||
f"{current_wrapper!r}", | ||
RuntimeWarning) | ||
else: | ||
set_wrapper(wrapper) | ||
self._coroutine_wrapper_set = True | ||
self._coroutine_origin_tracking_saved_depth = ( | ||
sys.set_coroutine_origin_tracking_depth(DEBUG_STACK_DEPTH) | ||
) | ||
else: | ||
if current_wrapper not in (None, wrapper): | ||
warnings.warn( | ||
f"loop.set_debug(False): cannot unset debug coroutine " | ||
f"wrapper; another wrapper was set {current_wrapper!r}", | ||
RuntimeWarning) | ||
else: | ||
set_wrapper(None) | ||
self._coroutine_wrapper_set = False | ||
sys.set_coroutine_origin_tracking_depth( | ||
self._coroutine_origin_tracking_saved_depth | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please move the closing paren to the prev line: sys.set_coroutine_origin_tracking_depth(
self._coroutine_origin_tracking_saved_depth) (that's just the code style in asyncio package that I'd like to maintain for consistency; and I'm slowly fixing places where it's different) |
||
|
||
self._coroutine_origin_tracking_enabled = enabled | ||
|
||
def get_debug(self): | ||
return self._debug | ||
|
@@ -1572,4 +1555,4 @@ def set_debug(self, enabled): | |
self._debug = enabled | ||
|
||
if self.is_running(): | ||
self._set_coroutine_wrapper(enabled) | ||
self.call_soon_threadsafe(self._set_coroutine_origin_tracking, enabled) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,14 +32,6 @@ def _is_debug_mode(): | |
_DEBUG = _is_debug_mode() | ||
|
||
|
||
def debug_wrapper(gen): | ||
# This function is called from 'sys.set_coroutine_wrapper'. | ||
# We only wrap here coroutines defined via 'async def' syntax. | ||
# Generator-based coroutines are wrapped in @coroutine | ||
# decorator. | ||
return CoroWrapper(gen, None) | ||
|
||
|
||
class CoroWrapper: | ||
# Wrapper for coroutine object in _DEBUG mode. | ||
|
||
|
@@ -141,8 +133,6 @@ def coroutine(func): | |
if inspect.iscoroutinefunction(func): | ||
# In Python 3.5 that's all we need to do for coroutines | ||
# defined with "async def". | ||
# Wrapping in CoroWrapper will happen via | ||
# 'sys.set_coroutine_wrapper' function. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can also remove |
||
return func | ||
|
||
if inspect.isgeneratorfunction(func): | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a link to the
set_coroutine_origin_tracking_depth
documentation snippet?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, that was my original thought. The problem is that
is too long to fit in the ascii-art box. So... either we need a shorter name for the function, or we need to redraw this whole giant table, and I couldn't think of a satisfactory way to do either in the 2 minutes I spent thinking about it :-). Any suggestions?
I guess we could use that weird ReST substitution thing? I'm not sure how that works.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this work: http://docutils.sourceforge.net/docs/user/rst/quickref.html#hyperlink-targets ?