Skip to content

Commit 6cb5387

Browse files
committed
gh-148736: Use ag_frame when walking async generators in asyncio.graph
_build_graph_for_future appended FrameCallGraphEntry(coro.cr_frame) in the ag_await branch; async generators expose ag_frame. The line is unreachable via standard async-for (async_generator_asend has neither cr_await nor ag_await) but is reachable from duck-typed chains.
1 parent 4b33308 commit 6cb5387

File tree

3 files changed

+28
-1
lines changed

3 files changed

+28
-1
lines changed

Lib/asyncio/graph.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def _build_graph_for_future(
6262
coro = coro.cr_await
6363
elif hasattr(coro, 'ag_await'):
6464
# A native async generator or duck-type compatible iterator
65-
st.append(FrameCallGraphEntry(coro.cr_frame))
65+
st.append(FrameCallGraphEntry(coro.ag_frame))
6666
coro = coro.ag_await
6767
else:
6868
break

Lib/test/test_asyncio/test_graph.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import asyncio
22
import io
3+
import sys
34
import unittest
5+
import unittest.mock
46

57

68
# To prevent a warning "test altered the execution environment"
@@ -146,6 +148,28 @@ async def main():
146148
'async generator CallStackTestBase.test_stack_async_gen.<locals>.gen()',
147149
stack_for_gen_nested_call[1])
148150

151+
def test_ag_frame_used_for_async_generator(self):
152+
# Regression test for gh-148736: the ag_await branch of
153+
# _build_graph_for_future must read ag_frame, not cr_frame.
154+
from asyncio.graph import _build_graph_for_future
155+
156+
sentinel_frame = sys._getframe()
157+
158+
class FakeAsyncGen:
159+
ag_await = None
160+
ag_frame = sentinel_frame
161+
162+
class FakeCoro:
163+
cr_frame = sentinel_frame
164+
cr_await = FakeAsyncGen()
165+
166+
fut = unittest.mock.Mock()
167+
fut.get_coro = lambda: FakeCoro()
168+
fut._asyncio_awaited_by = None
169+
170+
result = _build_graph_for_future(fut)
171+
self.assertEqual(len(result.call_stack), 2)
172+
149173
async def test_stack_gather(self):
150174

151175
stack_for_deep = None
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix a latent :exc:`AttributeError` in :mod:`asyncio` call-graph capture when
2+
walking an async generator's ``ag_await`` chain: the walker referenced
3+
``cr_frame`` instead of ``ag_frame``.

0 commit comments

Comments
 (0)