Skip to content

Commit e208bf0

Browse files
authored
gh-156988: Fix asyncio call graph breaking at sync generators (#156991)
1 parent 14a93f4 commit e208bf0

3 files changed

Lines changed: 25 additions & 1 deletion

File tree

Lib/asyncio/graph.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,9 @@ def capture_call_graph(
155155
f = sys._getframe(depth) if limit != 0 else None
156156
try:
157157
while f is not None:
158-
is_async = f.f_generator is not None
158+
# gh-156988: sync gen should not clear the call chain
159+
is_async = isinstance(
160+
f.f_generator, (types.CoroutineType, types.AsyncGeneratorType))
159161
call_stack.append(FrameCallGraphEntry(f))
160162

161163
if is_async:

Lib/test/test_asyncio/test_graph.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,26 @@ async def main():
593593
await main()
594594
self.assertRegex(output[0], r'in generator [\w.<>]+\.gen\(\)')
595595

596+
async def test_capture_call_graph_generator_keeps_caller_frames(self):
597+
# gh-156988: sync gen should not clear the call chain
598+
stack = None
599+
600+
def gen():
601+
nonlocal stack
602+
graph = asyncio.capture_call_graph()
603+
stack = [entry.frame.f_code.co_name for entry in graph.call_stack]
604+
yield
605+
606+
def middle():
607+
for _ in gen():
608+
pass
609+
610+
async def main():
611+
middle()
612+
613+
await main()
614+
self.assertEqual(stack[:3], ['gen', 'middle', 'main'])
615+
596616

597617
@unittest.skipIf(
598618
not hasattr(asyncio.futures, "_c_future_add_to_awaited_by"),
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
:func:`asyncio.print_call_graph` no longer truncates the call stack at a
2+
synchronous generator.

0 commit comments

Comments
 (0)