Cooperative concurrency viaDocumentation Index
Fetch the complete documentation index at: https://edgepython.com/llms.txt
Use this file to discover all available pages before exploring further.
async def coroutines and await / yield. No preemption, a coroutine runs until it yields, sleeps, awaits, or returns. Single-threaded scheduler; concurrency by interleaving, not parallelism.
No asyncio module. Primitives, run, sleep, frame, gather, with_timeout, cancel, receive, are top-level builtins.
Output
Two kinds of callables
Adef body executes immediately. An async def body returns a coroutine value that does nothing until driven with run / gather. Only coroutines are cancellable (cancel) and can suspend on real time (sleep).
A plain def inside a coroutine (or at module top-level) can still call yielding builtins (sleep, receive, deferred host calls), the scheduler snapshots the helper’s frame, suspends the call chain, re-enters the helper on resume so its return value lands at the original call site. The module body runs as an implicit coroutine, so top-level statements suspend the same way. From the caller, a sync helper that internally sleeps is indistinguishable from one that doesn’t.
Driving coroutines
run(coro) executes a single coroutine to completion and returns its value.
Output
run(c1, c2, ...) accepts multiple coroutines. They run concurrently; the call returns the first argument’s result.
Sleeping
sleep(seconds) suspends until seconds of wall time pass. Without a host time hook, a virtual clock advances logically, coroutines interleave deterministically with no real wait (useful for tests).
Output
gather
gather(*coros) runs each concurrently, returns a list of results in argument order. If any raises, the first error (in argument order) propagates after all peers terminate. Survivors not auto-cancelled.
Output
max(delays), not the sum, b and c overlap with a’s sleep.
Errors
Output
Concurrent host calls
Deferred host calls (e.g.network.fetch) run concurrently under gather: each parks its coroutine, the host resolves them in parallel, and every result is routed back to the exact coroutine that issued it. A failed call raises only in its own coroutine, so a try/except lets the rest of the batch finish.
Output
with_timeout
with_timeout(seconds, coro) runs coro or raises TimeoutError on deadline; coro cancelled on timeout.
Output
with_timeout evaluates the coroutine eagerly, it’s a call, not an awaitable.
cancel
cancel(coro) flags a registered coroutine for cancellation. On its next scheduler tick it transitions to Cancelled and stops. The body does not observe a CancelledError, cancellation is cooperative and silent.
A coroutine in a tight synchronous loop without await/sleep cannot be cancelled until it yields:
with_timeout.
Exception types
| Exception | When |
|---|---|
TimeoutError | with_timeout deadline expired |
CancelledError | reserved for user-thrown; not auto-raised by cancel() |
except clauses normally.
Limitations
- No preemption,
while True: passinside a coroutine blocks the scheduler. - Silent cancellation,
cancel(coro)stops the coro; the body doesn’t seeCancelledError. Usewith_timeoutfor deadline-as-exception. - Cooperative host loop, scheduler suspends to the host when it can’t progress synchronously (pending timer/frame/event); embedder resumes via
run_start/run_resume/run_push_event. The legacy non-suspendingruncannot resume, code usingsleep(n>0),frame(), or an emptyreceive()must run via the driver loop; statements after a top-levelrun()don’t execute after a yield. async forworks against anyfor-iterable plus coroutines and async generators (async defwithyield). Each iteration resumes to the next yield. No__aiter__/__anext__dispatch on user classes, write anasync defgenerator. Behaviour over lists/tuples/dicts is identical to regularfor.async withreuses sync dispatch (__enter__/__exit__);__aenter__/__aexit__aren’t consulted. For async setup/teardown, usetry/finallywith explicitawait.- No async comprehensions,
[x async for x in it]unsupported. - No
gen.send/throw/close, generators and coroutines are one-way producers. For bidirectional flow, userun/gatherand pass messages via args. receive()blocks indefinitely, empty queue + norun_push_eventleaves the coro parked inWaitingEvent. Pair withwith_timeoutfor a deadline.
Time capability
Scheduler reads fromvm.time_hook. WASM hosts wire it to Date.now() * 1e6 via the host_now_ns import; native hosts use std::time::Instant. Without a hook, sleep advances a virtual clock so deterministic tests interleave correctly.