Skip to content

Commit d8d218f

Browse files
authored
[3.6] bpo-31970: Reduce performance overhead of asyncio debug mode. (GH-4314) (#4322)
* bpo-31970: Reduce performance overhead of asyncio debug mode.. (cherry picked from commit 921e943)
1 parent 518c6b9 commit d8d218f

File tree

7 files changed

+38
-7
lines changed

7 files changed

+38
-7
lines changed

Lib/asyncio/base_events.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1222,6 +1222,11 @@ def default_exception_handler(self, context):
12221222
handler is set, and can be called by a custom exception
12231223
handler that wants to defer to the default behavior.
12241224
1225+
This default handler logs the error message and other
1226+
context-dependent information. In debug mode, a truncated
1227+
stack trace is also appended showing where the given object
1228+
(e.g. a handle or future or task) was created, if any.
1229+
12251230
The context parameter has the same meaning as in
12261231
`call_exception_handler()`.
12271232
"""

Lib/asyncio/constants.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,8 @@
55

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

Lib/asyncio/coroutines.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import types
1111

1212
from . import compat
13+
from . import constants
1314
from . import events
1415
from . import base_futures
1516
from .log import logger
@@ -91,7 +92,7 @@ def __init__(self, gen, func=None):
9192
assert inspect.isgenerator(gen) or inspect.iscoroutine(gen), gen
9293
self.gen = gen
9394
self.func = func # Used to unwrap @coroutine decorator
94-
self._source_traceback = traceback.extract_stack(sys._getframe(1))
95+
self._source_traceback = events.extract_stack(sys._getframe(1))
9596
self.__name__ = getattr(gen, '__name__', None)
9697
self.__qualname__ = getattr(gen, '__qualname__', None)
9798

@@ -183,8 +184,9 @@ def __del__(self):
183184
tb = getattr(self, '_source_traceback', ())
184185
if tb:
185186
tb = ''.join(traceback.format_list(tb))
186-
msg += ('\nCoroutine object created at '
187-
'(most recent call last):\n')
187+
msg += (f'\nCoroutine object created at '
188+
f'(most recent call last, truncated to '
189+
f'{constants.DEBUG_STACK_DEPTH} last lines):\n')
188190
msg += tb.rstrip()
189191
logger.error(msg)
190192

Lib/asyncio/events.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
import threading
2020
import traceback
2121

22-
from asyncio import compat
22+
from . import compat
23+
from . import constants
2324

2425

2526
def _get_function_source(func):
@@ -77,6 +78,23 @@ def _format_callback_source(func, args):
7778
return func_repr
7879

7980

81+
def extract_stack(f=None, limit=None):
82+
"""Replacement for traceback.extract_stack() that only does the
83+
necessary work for asyncio debug mode.
84+
"""
85+
if f is None:
86+
f = sys._getframe().f_back
87+
if limit is None:
88+
# Limit the amount of work to a reasonable amount, as extract_stack()
89+
# can be called for each coroutine and future in debug mode.
90+
limit = constants.DEBUG_STACK_DEPTH
91+
stack = traceback.StackSummary.extract(traceback.walk_stack(f),
92+
limit=limit,
93+
lookup_lines=False)
94+
stack.reverse()
95+
return stack
96+
97+
8098
class Handle:
8199
"""Object returned by callback registration methods."""
82100

@@ -90,7 +108,7 @@ def __init__(self, callback, args, loop):
90108
self._cancelled = False
91109
self._repr = None
92110
if self._loop.get_debug():
93-
self._source_traceback = traceback.extract_stack(sys._getframe(1))
111+
self._source_traceback = extract_stack(sys._getframe(1))
94112
else:
95113
self._source_traceback = None
96114

Lib/asyncio/futures.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ def __init__(self, *, loop=None):
159159
self._loop = loop
160160
self._callbacks = []
161161
if self._loop.get_debug():
162-
self._source_traceback = traceback.extract_stack(sys._getframe(1))
162+
self._source_traceback = events.extract_stack(sys._getframe(1))
163163

164164
_repr_info = base_futures._future_repr_info
165165

Lib/test/test_asyncio/test_tasks.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1918,7 +1918,7 @@ def coro_noop():
19181918

19191919
regex = (r'^<CoroWrapper %s\(?\)? .* at %s:%s, .*> '
19201920
r'was never yielded from\n'
1921-
r'Coroutine object created at \(most recent call last\):\n'
1921+
r'Coroutine object created at \(most recent call last, truncated to \d+ last lines\):\n'
19221922
r'.*\n'
19231923
r' File "%s", line %s, in test_coroutine_never_yielded\n'
19241924
r' coro_noop\(\)$'
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Reduce performance overhead of asyncio debug mode.

0 commit comments

Comments
 (0)