https://github.com/python/cpython/commit/b8ce41861d83063fe85fd11bf8c4452aa4154623
commit: b8ce41861d83063fe85fd11bf8c4452aa4154623
branch: 3.14
author: Timofei Ivankov <[email protected]>
committer: kumaraditya303 <[email protected]>
date: 2026-09-06T15:59:45+05:30
summary:

[3.14] gh-156988: Fix asyncio call graph loss at sync generators (#157005)

files:
A Misc/NEWS.d/next/Library/2026-09-05-18-23-30.gh-issue-156988.nEUQE6.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 54df7afa5553b9..d0a54f3625b37e 100644
--- a/Lib/asyncio/graph.py
+++ b/Lib/asyncio/graph.py
@@ -155,7 +155,9 @@ def capture_call_graph(
     f = sys._getframe(depth) if limit != 0 else None
     try:
         while f is not None:
-            is_async = f.f_generator is not None
+            # gh-156988: sync gen should not clear the call chain
+            is_async = isinstance(
+                f.f_generator, (types.CoroutineType, types.AsyncGeneratorType))
             call_stack.append(FrameCallGraphEntry(f))
 
             if is_async:
diff --git a/Lib/test/test_asyncio/test_graph.py 
b/Lib/test/test_asyncio/test_graph.py
index 690f8f58b8f10d..50fa18e888fd22 100644
--- a/Lib/test/test_asyncio/test_graph.py
+++ b/Lib/test/test_asyncio/test_graph.py
@@ -425,6 +425,26 @@ async def boom():
                 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_generator_keeps_caller_frames(self):
+        # gh-156988: sync gen should not clear the call chain
+        stack = None
+
+        def gen():
+            nonlocal stack
+            graph = asyncio.capture_call_graph()
+            stack = [entry.frame.f_code.co_name for entry in graph.call_stack]
+            yield
+
+        def middle():
+            for _ in gen():
+                pass
+
+        async def main():
+            middle()
+
+        await main()
+        self.assertEqual(stack[:3], ['gen', 'middle', 'main'])
+
 
 @unittest.skipIf(
     not hasattr(asyncio.futures, "_c_future_add_to_awaited_by"),
diff --git 
a/Misc/NEWS.d/next/Library/2026-09-05-18-23-30.gh-issue-156988.nEUQE6.rst 
b/Misc/NEWS.d/next/Library/2026-09-05-18-23-30.gh-issue-156988.nEUQE6.rst
new file mode 100644
index 00000000000000..6cb2f1f4cf14ae
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-09-05-18-23-30.gh-issue-156988.nEUQE6.rst
@@ -0,0 +1,2 @@
+:func:`asyncio.print_call_graph` no longer truncates the call stack at a
+synchronous generator.

_______________________________________________
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]

Reply via email to