bpo-31970: Reduce performance overhead of asyncio debug mode. (#4314)

* bpo-31970: Reduce performance overhead of asyncio debug mode.
This commit is contained in:
Antoine Pitrou 2017-11-07 17:23:29 +01:00 committed by GitHub
parent 1e5d54cfa0
commit 921e9432a1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 38 additions and 6 deletions

View file

@ -1223,6 +1223,11 @@ def default_exception_handler(self, context):
handler is set, and can be called by a custom exception
handler that wants to defer to the default behavior.
This default handler logs the error message and other
context-dependent information. In debug mode, a truncated
stack trace is also appended showing where the given object
(e.g. a handle or future or task) was created, if any.
The context parameter has the same meaning as in
`call_exception_handler()`.
"""

View file

@ -5,3 +5,8 @@
# Seconds to wait before retrying accept().
ACCEPT_RETRY_DELAY = 1
# Number of stack entries to capture in debug mode.
# The large the number, the slower the operation in debug mode
# (see extract_stack() in events.py)
DEBUG_STACK_DEPTH = 10

View file

@ -10,6 +10,7 @@
import types
from . import compat
from . import constants
from . import events
from . import base_futures
from .log import logger
@ -91,7 +92,7 @@ def __init__(self, gen, func=None):
assert inspect.isgenerator(gen) or inspect.iscoroutine(gen), gen
self.gen = gen
self.func = func # Used to unwrap @coroutine decorator
self._source_traceback = traceback.extract_stack(sys._getframe(1))
self._source_traceback = events.extract_stack(sys._getframe(1))
self.__name__ = getattr(gen, '__name__', None)
self.__qualname__ = getattr(gen, '__qualname__', None)
@ -183,8 +184,9 @@ def __del__(self):
tb = getattr(self, '_source_traceback', ())
if tb:
tb = ''.join(traceback.format_list(tb))
msg += ('\nCoroutine object created at '
'(most recent call last):\n')
msg += (f'\nCoroutine object created at '
f'(most recent call last, truncated to '
f'{constants.DEBUG_STACK_DEPTH} last lines):\n')
msg += tb.rstrip()
logger.error(msg)

View file

@ -19,6 +19,8 @@
import threading
import traceback
from . import constants
def _get_function_source(func):
func = inspect.unwrap(func)
@ -72,6 +74,23 @@ def _format_callback_source(func, args):
return func_repr
def extract_stack(f=None, limit=None):
"""Replacement for traceback.extract_stack() that only does the
necessary work for asyncio debug mode.
"""
if f is None:
f = sys._getframe().f_back
if limit is None:
# Limit the amount of work to a reasonable amount, as extract_stack()
# can be called for each coroutine and future in debug mode.
limit = constants.DEBUG_STACK_DEPTH
stack = traceback.StackSummary.extract(traceback.walk_stack(f),
limit=limit,
lookup_lines=False)
stack.reverse()
return stack
class Handle:
"""Object returned by callback registration methods."""
@ -85,7 +104,7 @@ def __init__(self, callback, args, loop):
self._cancelled = False
self._repr = None
if self._loop.get_debug():
self._source_traceback = traceback.extract_stack(sys._getframe(1))
self._source_traceback = extract_stack(sys._getframe(1))
else:
self._source_traceback = None

View file

@ -79,7 +79,7 @@ def __init__(self, *, loop=None):
self._loop = loop
self._callbacks = []
if self._loop.get_debug():
self._source_traceback = traceback.extract_stack(sys._getframe(1))
self._source_traceback = events.extract_stack(sys._getframe(1))
_repr_info = base_futures._future_repr_info

View file

@ -1981,7 +1981,7 @@ def coro_noop():
regex = (r'^<CoroWrapper %s\(?\)? .* at %s:%s, .*> '
r'was never yielded from\n'
r'Coroutine object created at \(most recent call last\):\n'
r'Coroutine object created at \(most recent call last, truncated to \d+ last lines\):\n'
r'.*\n'
r' File "%s", line %s, in test_coroutine_never_yielded\n'
r' coro_noop\(\)$'

View file

@ -0,0 +1 @@
Reduce performance overhead of asyncio debug mode.