Repository navigation
Description
Bug report
Bug description:
sys.monitoring branch events for async loops are wrong in two cases:
import asyncio, sys
M = sys.monitoring; E = M.events; M.use_tool_id(3, "t"); ev = []
M.register_callback(3, E.BRANCH_LEFT, lambda c, s, d: ev.append(("L", s, d)))
M.register_callback(3, E.BRANCH_RIGHT, lambda c, s, d: ev.append(("R", s, d)))
async def agen(): yield 1; yield 2
async def comp(): return [x async for x in agen()]
async def two():
async for x in agen(): pass
async for x in agen(): pass
for f in (comp, two):
M.set_local_events(3, f.__code__, E.BRANCH_LEFT | E.BRANCH_RIGHT); ev.clear()
asyncio.run(f()); print(f.__name__, ev, list(f.__code__.co_branches()))Output with 3.14.3:
comp [('R', 48, 64)] [(48, 52, 64)]
two [('L', 40, 44), ('L', 40, 44), ('R', 40, 56), ('L', 92, 96), ('L', 92, 96), ('R', 90, 104)] [(40, 44, 56), (90, 94, 104)]
- The async comprehension never emits
BRANCH_LEFT, although the loop body runs twice. The firstasync forloop intwoemits it once per item, as expected. - In the second
async forloop oftwo,BRANCH_RIGHThas source offset 90 andBRANCH_LEFThas source offset 92.co_branches()says(90, 94, 104), so theBRANCH_LEFTevents match neither its source nor its left target. Offset 90 is theCLEANUP_THROWof that loop and 92 is itsEND_SEND.
Expected: BRANCH_LEFT once per item and BRANCH_RIGHT once per loop. For each loop, its BRANCH_LEFT and BRANCH_RIGHT events share one source offset, and co_branches() lists that source with the same targets as the events.
With only one of the two functions in the loop, a debug build of main (cc98745) aborts on each. For comp, in co_branches():
Assertion failed: (_Py_GetBaseCodeUnit(bi->bi_code, src_offset+1).op.code == NOT_TAKEN), function branchesiter_next, file instrumentation.c, line 3183.
For two, while the monitored function runs:
Assertion failed: ((next_instr-oparg)->op.code == END_SEND || (next_instr-oparg)->op.code >= MIN_INSTRUMENTED_OPCODE), function _PyEval_EvalFrameDefault, file generated_cases.c.h, line 7553.
Tools that measure branch coverage with these events see the body of an async comprehension as never entered, and cannot pair the two directions of the second loop.
gh-128534 (#130569) added NOT_TAKEN after the END_SEND in codegen_async_for(); codegen_async_comprehension_generator() does not have it. gh-123076 is the older report about async for; gh-161928 is a different problem in the same events (exception from __anext__).
I have a small fix for both cases (Python/codegen.c, Python/assemble.c) with tests in test_monitoring and can open a PR.
CPython versions tested on:
3.14, CPython main branch
Operating systems tested on:
macOS
Output from running 'python -VV' on the command line:
Python 3.14.3 (main, Mar 25 2026, 03:28:18) [Clang 22.1.1 ]
Python 3.16.0a2+dev (heads/main:cc987451e8, Nov 14 2026, 23:57:44) [Clang 21.0.0 (clang-2100.1.1.101)]
Activity
itxtarun2727 commentedon Nov 15, 2026
i am reproducing this in my local environment , will ship the PR soon