Compatibility¶
zuvloop is checked by running the test suites of the projects that exercise an event loop hardest, unmodified, with the loop swapped underneath.
| Suite | Result |
|---|---|
| uvicorn | 1257 passed, no failures |
| aiohttp | 4473 passed, 36 failed — 33 of which also fail on stock asyncio |
Three aiohttp failures are zuvloop's alone. Two are the blockbuster plugin flagging
os.stat inside create_unix_server — a call stdlib asyncio makes in the same
place, and which the plugin exempts by file path rather than by behaviour. The
third is a genuine difference, below.
For reference, uvloop cannot complete that suite: it fails fifteen tests in
test_client_functional.py and then hangs.
Where each loop diverges from asyncio¶
These were measured, not asserted — each is a case where one loop disagrees with the standard library.
| Behaviour | asyncio | uvloop | zuvloop |
|---|---|---|---|
isinstance(t, asyncio.Transport) |
True | False | True |
isinstance(t, asyncio.DatagramTransport) |
True | False | True |
isinstance(t, asyncio.ReadTransport) for a read pipe |
True | False | True |
get_extra_info("socket") |
TransportSocket |
PseudoSocket |
TransportSocket |
loop.time() equals time.monotonic() |
yes | yes | yes |
getaddrinfo("fe80::1%lo0") keeps the zone |
yes | no | yes |
connect_read_pipe on a regular file |
ValueError |
accepted | ValueError |
The isinstance rows are not pedantry. aiohttp's test suite asserts
isinstance(transport, asyncio.Transport) inside connection_made; a loop that
fails it leaves the protocol half-initialised and the connection open with
nobody to answer on it.
The getaddrinfo row is the sharpest: uvloop's literal shortcut drops the IPv6
zone index, so fe80::1%lo0 resolves to scope 0 — the wrong interface. Across
2430 combinations of host, port, family, type and flags, zuvloop disagrees with
socket.getaddrinfo on 81 and uvloop on 303.
Known differences¶
Patching loop.time() does not move the scheduler. asyncio runs its timers
off self.time(), so replacing that method fast-forwards the loop — a trick test
suites use to expire timeouts without waiting. zuvloop keeps the timer heap in Zig
and reads the clock directly, so a patched time() changes what loop.time()
returns and nothing else.
Making the scheduler consult Python on every timer operation would cost more than the compatibility is worth. Code that needs a controllable clock should schedule against one explicitly.
sock_sendfile and sendfile raise NotImplementedError. asyncio falls
back to a read-and-write loop when a loop declines them, so this degrades rather
than breaks.
host="" is not treated as NULL. socket.getaddrinfo resolves the empty
string as an unspecified host; zuvloop raises OSError. This is the whole of its
81-case disagreement above.
Not yet verified¶
CPython's own test_asyncio — the conformance suite — has not been run against
zuvloop. Everything on this page is a proxy for it.
Linux is exercised by CI on every commit, but the framework suites and every benchmark here were run on macOS. libuv's Linux backend takes a different path for stream I/O than kqueue does.