Skip to content

Commit 9028577

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 9028577

File tree

3 files changed

+30
-1
lines changed

3 files changed

+30
-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: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import asyncio
22
import io
3+
import sys
34
import unittest
45

56

@@ -146,6 +147,31 @@ async def main():
146147
'async generator CallStackTestBase.test_stack_async_gen.<locals>.gen()',
147148
stack_for_gen_nested_call[1])
148149

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

151177
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)