Skip to content

Fix anext awaitable ag gen - #157035

Closed
anshuman0123 wants to merge 2 commits into
python:mainfrom
anshuman0123:fix-anext-awaitable-ag-gen
Closed

Fix anext awaitable ag gen#157035
anshuman0123 wants to merge 2 commits into
python:mainfrom
anshuman0123:fix-anext-awaitable-ag-gen

Conversation

@anshuman0123

Copy link
Copy Markdown

Description

Fixes gh-157032.

When calling anext(agen, default) with a default value, Python wraps the underlying anext() awaitable in an internal
anext_awaitable instance (iterobject.c:406-410 in Objects/iterobject.c).

During task call-stack inspection, _build_graph_for_future in graph.py:40-82 traverses cr_await, ag_await, and ag_gen to walk
through coroutines, async generators, and generator-driving awaitables. Because anext_awaitable lacked an ag_gen attribute,
traversal terminated early upon encountering anext_awaitable, losing the entire call stack inside the async generator and any
child coroutines it was waiting on.

This PR exposes a read-only ag_gen attribute on anext_awaitable that delegates to self.wrapped.ag_gen.
──────

Changes Made:

  1. iterobject.c:
    • Added getter anextawaitable_getag_gen to read ag_gen from obj->wrapped.
    • Added anextawaitable_getsetlist and hooked it into _PyAnextAwaitable_Type.tp_getset.
  2. graph.py:
    • Updated the comment on ag_gen traversal to reference both asyncio.print_call_graph() loses the call stack at asynchronous generators #156980 and asyncio.print_call_graph() loses the call stack at anext(agen, default) #157032.
  3. test_asyncgen.py:
    • Added test_async_gen_anext_default_ag_gen to verify anext(agen, default).ag_gen returns the generator, is read-only, and
    raises AttributeError for non-generator async iterators.
  4. test_graph.py:
    • Added test_stack_async_gen_anext_default testing both C and Python event loop implementations.
  5. functions.rst:
    • Documented the ag_gen attribute on the awaitable returned by anext().
  6. Misc/NEWS.d/next/Library/:
    • Added NEWS entry for asyncio.print_call_graph() loses the call stack at anext(agen, default) #157032.

──────

Repro & Verification

import asyncio

async def deep():
    await asyncio.Event().wait()

async def rows():
    await deep()
    yield 1

async def worker():
    await anext(rows(), None)

async def main():
    t = asyncio.create_task(worker(), name='worker')
    await asyncio.sleep(0.05)
    asyncio.print_call_graph(t)
    t.cancel()
    try:
        await t
    except asyncio.CancelledError:
        pass

asyncio.run(main())

Before:

* Task(name='worker', id=0x1035c89b0)
  + Call stack:
  |   File 'repro.py', line 11, in async worker()

After:

* Task(name='worker', id=0x244549256d0)
  + Call stack:
  |   File '.../Lib/asyncio/locks.py', line 210, in async Event.wait()
  |   File 'repro.py', line 5, in async deep()
  |   File 'repro.py', line 8, in async generator rows()
  |   File 'repro.py', line 12, in async worker()

──────

Test Suite Results:

• python -m test test_asyncgen: Passed (100%)
• python -m test test_asyncio: Passed (35/35 test suites, 2,749 tests, 0 failures)

deadlovelll and others added 2 commits September 6, 2026 18:40
…graph

Expose ag_gen attribute on anext_awaitable objects (returned by
anext(agen, default)) that delegates to the wrapped awaitable's
ag_gen. This allows asyncio.print_call_graph() and capture_call_graph()
to traverse through anext_awaitable to reach the underlying async
generator without truncating the call stack.
@picnixz

picnixz commented Sep 6, 2026

Copy link
Copy Markdown
Member

The issue says:

Important: Currently this issue have a blocker from #156984 (issue - #156980) because it depends on the fix of it. I have a fix ready and will open a PR once #156984 is merged

Don't open issues when the OP already suggests to open a PR as it's rude, especially if such PRs are fully AI generated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

asyncio.print_call_graph() loses the call stack at anext(agen, default)

3 participants