Skip to content

Async generators and async comprehensions in dynamic builds - #231

Merged
ets-public merged 6 commits into
mainfrom
claude/project-thread-ytswfg
Nov 6, 2026
Merged

ets-public merged 6 commits into
mainfrom
claude/project-thread-ytswfg

Conversation

@ets-public

Copy link
Copy Markdown
Owner

Requested by Adam · project thread

What this changes

Follow-up to #230.

  • Async generators (async def with yield) now build natively. This covers async for, anext, asend, athrow, aclose, ag_running, and CPython's errors: already running, reuse of an awaited asend, ignored GeneratorExit, and StopAsyncIteration raised inside the generator. The body runs on its own stack. A yield hands back a value. A pending future deep inside the body is passed up to the task.
  • Finalization works as in CPython. Brevi's event loop installs the asyncgen hooks. A generator left part way, for example after a break out of async for, is closed by the loop in a task at the same point CPython closes it, so its finally prints in the same place. asyncio.run closes the generators still open when it ends (shutdown_asyncgens). Without a loop, the generator is closed when it is dropped.
  • Async list, set and dict comprehensions now build natively, including async for clauses and await inside them. They run in place, as CPython 3.12+ runs them.
  • Still refused, so the file runs in CPython: asynchronous generator expressions. yield from inside an async def and return value inside an async generator are refused too; CPython rejects both as syntax errors.

How it was tested

  • sh tools/ci_local.sh passes, using CI's rustc 1.99.1: 2003 baseline solutions, 0 regressions
  • A test under tests/ covers the change: tests/lib/asyncio_agen_{basics,flow,errors,close}
  • A line under "Unreleased" in CHANGELOG.md
  • I extended the asyncio fuzzer with async-generator producers that use try/finally and are consumed several ways: full async for, break part way, async comprehensions, partly consumed then dropped, and asend/aclose. It generated 240 random programs. Each was run under both CPython and Brevi, and the outputs were compared.
    • 239 matched.
    • The other one has two timers with almost the same deadline, so its output changes from run to run in CPython itself. On repeat runs CPython and Brevi printed the same output.
  • Performance: 300k items through async for plus an async comprehension takes 0.106 s in Brevi and 0.122 s in CPython.

Generated by Claude Code

async def, await, async for and async with build in the dynamic build.
A coroutine awaited by another runs in place on the awaiting task's
stack; each task's coroutine runs on its own stack (rt_gen.rs Coro, which
gains throw()). asyncio is Brevi's own: CPython 3.13's asyncio ported to
src/dynpy/py/asyncio.py and cut down to callbacks and timers, so tasks
interleave in CPython's order. Programs using more of asyncio, or async
code with a library CPython runs, are refused at build time.

Also fixes time.sleep (it did not sleep in dynamic builds) and
time.monotonic/perf_counter/process_time (they read the wall clock), and
adds a lookup cache to classes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0136hkZhPtdW14Wtm51vEuNk
…e their group

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0136hkZhPtdW14Wtm51vEuNk
… guard-unread tests use a metaclass

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0136hkZhPtdW14Wtm51vEuNk
@ets-public ets-public self-assigned this Nov 6, 2026
@ets-public
ets-public merged commit 41686d2 into main Nov 6, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants