https://github.com/python/cpython/commit/6c425f1d18d3915cb32e3372647db24bad7ab92d
commit: 6c425f1d18d3915cb32e3372647db24bad7ab92d
branch: main
author: Timofei Ivankov <[email protected]>
committer: kumaraditya303 <[email protected]>
date: 2026-08-29T10:04:57Z
summary:
gh-156408: Fix asyncio.print_call_graph() on a finished task (#156410)
files:
A Misc/NEWS.d/next/Library/2026-08-26-12-48-10.gh-issue-156408.-Yd0k7.rst
M Lib/asyncio/graph.py
M Lib/test/test_asyncio/test_graph.py
diff --git a/Lib/asyncio/graph.py b/Lib/asyncio/graph.py
index 7e85e08c4291a0..e240c6dd77d124 100644
--- a/Lib/asyncio/graph.py
+++ b/Lib/asyncio/graph.py
@@ -58,7 +58,8 @@ def _build_graph_for_future(
while coro is not None:
if hasattr(coro, 'cr_await'):
# A native coroutine or duck-type compatible iterator
- st.append(FrameCallGraphEntry(coro.cr_frame))
+ if coro.cr_frame is not None:
+ st.append(FrameCallGraphEntry(coro.cr_frame))
coro = coro.cr_await
elif hasattr(coro, 'ag_await'):
# A native async generator or duck-type compatible iterator
diff --git a/Lib/test/test_asyncio/test_graph.py
b/Lib/test/test_asyncio/test_graph.py
index 220c0179f27bf3..2c5bb2e8f52e66 100644
--- a/Lib/test/test_asyncio/test_graph.py
+++ b/Lib/test/test_asyncio/test_graph.py
@@ -484,6 +484,24 @@ def test_capture_call_graph_non_future(self):
with self.assertRaises(TypeError):
asyncio.capture_call_graph("not a future")
+ async def test_call_graph_finished_task(self):
+ # gh-156408: the call graph must not record a finished coroutine's
None frame
+ async def boom():
+ raise ValueError
+
+ done = asyncio.create_task(asyncio.sleep(0), name='done')
+ failed = asyncio.create_task(boom(), name='failed')
+ cancelled = asyncio.create_task(asyncio.Event().wait(),
name='cancelled')
+ cancelled.cancel()
+ await asyncio.gather(done, failed, cancelled, return_exceptions=True)
+
+ for task in (done, failed, cancelled):
+ with self.subTest(task=task.get_name()):
+ buf = io.StringIO()
+ asyncio.print_call_graph(task, file=buf)
+ self.assertEqual(asyncio.capture_call_graph(task).call_stack,
())
+ self.assertIn(f"name={task.get_name()!r}", buf.getvalue())
+
async def test_capture_call_graph_no_current_task(self):
results = []
diff --git
a/Misc/NEWS.d/next/Library/2026-08-26-12-48-10.gh-issue-156408.-Yd0k7.rst
b/Misc/NEWS.d/next/Library/2026-08-26-12-48-10.gh-issue-156408.-Yd0k7.rst
new file mode 100644
index 00000000000000..62c92838b5c126
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-08-26-12-48-10.gh-issue-156408.-Yd0k7.rst
@@ -0,0 +1,2 @@
+Fix :func:`asyncio.print_call_graph` raising :exc:`AttributeError` when
+called on a task that has already finished.
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]