diff --git a/Lib/asyncio/graph.py b/Lib/asyncio/graph.py index ce23339782c16e8..d76d2f6383343d5 100644 --- a/Lib/asyncio/graph.py +++ b/Lib/asyncio/graph.py @@ -2,6 +2,7 @@ import dataclasses import io +import operator import sys import types @@ -160,7 +161,8 @@ def capture_call_graph( call_stack: list[FrameCallGraphEntry] = [] - f = sys._getframe(depth) if limit != 0 else None + # gh-161124: find the task boundary before skipping frames + f = sys._getframe(0) if limit != 0 else None try: while f is not None: # gh-156988: sync gen should not clear the call chain @@ -178,6 +180,8 @@ def capture_call_graph( finally: del f + call_stack = call_stack[max(0, operator.index(depth)):] + awaited_by = [] if future._asyncio_awaited_by: for parent in future._asyncio_awaited_by: diff --git a/Lib/test/test_asyncio/test_graph.py b/Lib/test/test_asyncio/test_graph.py index 71853ab642f451f..8af42a851707a56 100644 --- a/Lib/test/test_asyncio/test_graph.py +++ b/Lib/test/test_asyncio/test_graph.py @@ -785,6 +785,11 @@ async def main(): self.assertTrue(stack_for_fut[1].startswith('* Future(id=')) + async def test_call_graph_depth_beyond_task(self): + # gh-161124 + graph = asyncio.capture_call_graph(depth=2) + self.assertEqual(graph.call_stack, ()) + async def test_capture_call_graph_positive_limit(self): captured = None diff --git a/Misc/NEWS.d/next/Library/2026-11-01-22-07-24.gh-issue-161124.c2YmcV.rst b/Misc/NEWS.d/next/Library/2026-11-01-22-07-24.gh-issue-161124.c2YmcV.rst new file mode 100644 index 000000000000000..32cfb56adb83167 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-11-01-22-07-24.gh-issue-161124.c2YmcV.rst @@ -0,0 +1,2 @@ +Fix :func:`asyncio.print_call_graph` reporting event loop frames when +*depth* exceeds the task stack. Patch by Timofei Ivankov.