zuvloop
A fast, drop-in asyncio event loop, powered by libuv and written in Zig ⚡
Documentation: https://zuvloop.marcelotryle.com
Source Code: https://github.com/Kludex/zuvloop
zuvloop is a replacement for the built-in asyncio event loop.
Your code stays the same. The loop underneath gets faster. 🚀
The key features are:
- Fast: Scheduling, timers, sockets, and DNS run in native code, driven by libuv — the same engine behind Node.js. Up to 6x faster than asyncio and faster than uvloop on every benchmark below.
- Drop-in: One line to switch. Everything is standard
asyncio— sameTaskobjects, same protocols, same APIs. - Fully typed: Ships type hints for everything and passes strict mypy. Your editor will love it. ✨
- Observable: Built-in OpenTelemetry instrumentation — slow-callback spans, unhandled-exception spans, loop metrics. Zero cost until you turn it on.
- Modern: Built for Python 3.14, including the new asyncio introspection tools (
python -m asyncio ps, call graphs, and friends).
Performance
Throughput relative to stock asyncio (higher is better), measured with the suite in
benchmarks/ on an M3 Max, macOS 26, CPython 3.14. The labels show the absolute numbers.
| Benchmark | asyncio | uvloop | zuvloop |
|---|---|---|---|
call_soon |
2.59M/s | 5.66M/s | 5.90M/s |
call_soon with arguments |
2.45M/s | 3.55M/s | 6.36M/s |
call_soon_threadsafe |
0.44M/s | 5.06M/s | 6.51M/s |
| timer schedule + cancel | 1.55M/s | 2.57M/s | 9.58M/s |
| bulk stream | 8.6 GiB/s | 9.0 GiB/s | 10.7 GiB/s |
| echo round trips, 1 KiB | 38.4k/s | 53.0k/s | 58.4k/s |
| uvicorn, plaintext | 54.0k req/s | 70.5k req/s | 76.7k req/s |
| uvicorn, 10 KiB body | 51.6k req/s | 69.5k req/s | 74.2k req/s |
| aiohttp server | 48.8k req/s | 61.0k req/s | 62.6k req/s |
| aiohttp client | 13.4k req/s | 16.6k req/s | 16.9k req/s |
getaddrinfo, numeric host |
28.3k/s | 1.58M/s | 1.91M/s |
Curious how? The architecture docs explain the design:
argument storage inside handles (no tuple per callback), a native timer heap behind a single
uv_timer_t, per-turn vectored write batching, zero-copy reads, and a getaddrinfo fast path
for address literals.
Requirements
- Python 3.14+
- Linux or macOS
Installation
$ pip install zuvloop
To build from source you also need Zig 0.16.
Example
Write normal asyncio code, run it with zuvloop:
import asyncio
import zuvloop
async def main() -> None:
reader, writer = await asyncio.open_connection("example.com", 80)
writer.write(b"GET / HTTP/1.0\r\nHost: example.com\r\n\r\n")
await writer.drain()
print(await reader.read(64))
writer.close()
await writer.wait_closed()
zuvloop.run(main())
Prefer to keep asyncio.run()? Hand it the loop factory:
asyncio.run(main(), loop_factory=zuvloop.new_event_loop)
That's it. That's the migration. 🎉
Observability
zuvloop emits plain OpenTelemetry. The only runtime dependency is opentelemetry-api — not the
SDK, nothing vendor-specific. Providers can be configured before the loop starts or from inside
it — logfire.configure() in main() works: zuvloop checks at each run_forever() entry and
re-checks on its sampling interval (loop.metrics_interval, 10 seconds by default) while the
loop runs. Until a provider is installed the instruments are no-ops and slow-callback timing
stays off.
Anything that speaks OpenTelemetry can collect it. For example, with Logfire:
import logfire
import zuvloop
logfire.configure() # installs the OTel providers
async def main() -> None: ...
zuvloop.run(main())
That's all — there is no zuvloop-specific setup. Spans and counters are emitted as events happen, and the loop gauges are sampled automatically while the loop runs (published only once a real provider is installed; without one the snapshot is dropped).
You get:
zuvloop.slow_callbackspans — with real start/end timestamps measured byuv_hrtime()in native code, and the awaiting call graph attached (viaasyncio.format_call_graph()), so you see why a callback was running, not just its repr.zuvloop.unhandled_exceptionspans — with the exception recorded.- Counters, a callback-duration histogram, and live loop gauges (
loop_count,events,idle_time_ns,ready,timers,watchers, ...).
And because zuvloop schedules real asyncio.Task objects, the Python 3.14 introspection tools
work unchanged:
$ python -m asyncio ps <pid>
$ python -m asyncio pstree <pid>
Compatibility
zuvloop is checked against CPython's own conformance suite and against the test suites of the projects that exercise an event loop hardest — run unmodified, with the loop swapped underneath:
| Suite | Result |
|---|---|
CPython test_asyncio |
88 passed, 4 skipped, none failing |
| uvicorn | 1257 passed, no failures |
| aiohttp | 4473 passed, 36 failed — 33 of which also fail on stock asyncio |
scripts/conformance.py runs CPython's EventLoopTestsMixin, SubprocessTestsMixin and
BaseSockTestsMixin against zuvloop, downloading the source of whichever interpreter is running
so the suite always matches it. Each test runs in its own process, so a hang is reported rather
than stopping the run. Three of the four skips are white-box tests of CPython's own internals -
two patch asyncio.base_events.socket, one counts calls to BaseEventLoop._run_once - which no
loop outside the standard library can satisfy.
Of aiohttp's three remaining failures, two are blockbuster reporting a blocking os.stat that
the standard library makes on the same path, and the third is the loop.time() difference below.
(For reference: uvloop cannot complete the aiohttp suite — it fails fifteen tests and then hangs.)
There is one intentional difference: patching loop.time() does not move the scheduler.
zuvloop keeps its timer heap in native code and reads the clock directly, so monkeypatching
time() — a trick some test suites use to fast-forward timeouts — changes what loop.time()
returns and nothing else. A loop that needs a controllable clock should schedule against one
explicitly.
One more deliberate divergence: handles
returned by call_soon implement the asyncio.Handle interface but are not instances of it:
the base class is 56 bytes of storage such a handle never writes, measured at 2% of call_soon,
which is the object the loop allocates more often than any other. call_later and call_at
do return real asyncio.TimerHandle instances, so they order and compare by deadline, and
call_soon_threadsafe returns a real asyncio.Handle, because 3.14 requires cancelling one
from another thread to block until a callback that has already started finishes.
Development
$ uv venv --python 3.14
$ uv pip install -e . --group dev
$ uv run pytest
$ uv run mypy
$ uv run ruff check
$ uv run ruff format --check
$ ./scripts/check-zig # requires ZLint 0.9.1 on PATH
$ uv run --group bench python benchmarks/run.py
The extension is rebuilt by hatch_build.py on every install. To rebuild in place:
$ python scripts/build.py
vendor/libuv is an unmodified upstream release tarball; see vendor/README.md. Update it with
./vendor/update-libuv.sh <version> <sha256>.
License
This project is licensed under the terms of the MIT license.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file zuvloop-0.0.4.tar.gz.
File metadata
- Download URL: zuvloop-0.0.4.tar.gz
- Upload date:
- Size: 1.4 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3c98f24486324ed407456bae266bf025f9bc91a58c1dfff530c94c663464513a
|
|
| MD5 |
125b961cf96b6de21c142a891b77460e
|
|
| BLAKE2b-256 |
1c02acfc6cf12934ee27c5c4b5fef121b4ac037297198edf786e516d5e087f8c
|
Provenance
The following attestation bundles were made for zuvloop-0.0.4.tar.gz:
Publisher:
publish.yml on Kludex/zuvloop
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
zuvloop-0.0.4.tar.gz -
Subject digest:
3c98f24486324ed407456bae266bf025f9bc91a58c1dfff530c94c663464513a - Sigstore transparency entry: 2435176898
- Sigstore integration time:
-
Permalink:
Kludex/zuvloop@49a8b5b5ef92f6d98c05ddb68d23d7480bc944fc -
Branch / Tag:
refs/tags/v0.0.4 - Owner: https://github.com/Kludex
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@49a8b5b5ef92f6d98c05ddb68d23d7480bc944fc -
Trigger Event:
push
-
Statement type:
File details
Details for the file zuvloop-0.0.4-cp314-cp314-musllinux_1_2_x86_64.whl.
File metadata
- Download URL: zuvloop-0.0.4-cp314-cp314-musllinux_1_2_x86_64.whl
- Upload date:
- Size: 1.5 MB
- Tags: CPython 3.14, musllinux: musl 1.2+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
704aba7bdb3364662b4763a479a4e0d41955830f2f01bd48495f36aa24f802de
|
|
| MD5 |
5768aa861d385ec6617f0ea7850714a4
|
|
| BLAKE2b-256 |
0566c717f926f0d8f855bf6c15dd0df5fa714b13321517910323cd9622852fc8
|
Provenance
The following attestation bundles were made for zuvloop-0.0.4-cp314-cp314-musllinux_1_2_x86_64.whl:
Publisher:
publish.yml on Kludex/zuvloop
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
zuvloop-0.0.4-cp314-cp314-musllinux_1_2_x86_64.whl -
Subject digest:
704aba7bdb3364662b4763a479a4e0d41955830f2f01bd48495f36aa24f802de - Sigstore transparency entry: 2435176975
- Sigstore integration time:
-
Permalink:
Kludex/zuvloop@49a8b5b5ef92f6d98c05ddb68d23d7480bc944fc -
Branch / Tag:
refs/tags/v0.0.4 - Owner: https://github.com/Kludex
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@49a8b5b5ef92f6d98c05ddb68d23d7480bc944fc -
Trigger Event:
push
-
Statement type:
File details
Details for the file zuvloop-0.0.4-cp314-cp314-musllinux_1_2_aarch64.whl.
File metadata
- Download URL: zuvloop-0.0.4-cp314-cp314-musllinux_1_2_aarch64.whl
- Upload date:
- Size: 1.5 MB
- Tags: CPython 3.14, musllinux: musl 1.2+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5574d0652f5547c091d7fe585ae9f5f85b29cabdd6093c714bff570a702c2148
|
|
| MD5 |
5ec0e9793e2b36aa31cb0150b7b88246
|
|
| BLAKE2b-256 |
efde9465ed55c08fced11aa4c7291ed16c20ff0161dc0fdbb709bb8853ddc278
|
Provenance
The following attestation bundles were made for zuvloop-0.0.4-cp314-cp314-musllinux_1_2_aarch64.whl:
Publisher:
publish.yml on Kludex/zuvloop
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
zuvloop-0.0.4-cp314-cp314-musllinux_1_2_aarch64.whl -
Subject digest:
5574d0652f5547c091d7fe585ae9f5f85b29cabdd6093c714bff570a702c2148 - Sigstore transparency entry: 2435177468
- Sigstore integration time:
-
Permalink:
Kludex/zuvloop@49a8b5b5ef92f6d98c05ddb68d23d7480bc944fc -
Branch / Tag:
refs/tags/v0.0.4 - Owner: https://github.com/Kludex
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@49a8b5b5ef92f6d98c05ddb68d23d7480bc944fc -
Trigger Event:
push
-
Statement type:
File details
Details for the file zuvloop-0.0.4-cp314-cp314-manylinux_2_28_x86_64.whl.
File metadata
- Download URL: zuvloop-0.0.4-cp314-cp314-manylinux_2_28_x86_64.whl
- Upload date:
- Size: 1.6 MB
- Tags: CPython 3.14, manylinux: glibc 2.28+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7e04958f2ed7f809ce27afd923fcf1e61fe79d366102d74beb0e69ca0d1d5cdc
|
|
| MD5 |
c83e4a46299dd406380307e4559dc908
|
|
| BLAKE2b-256 |
ef1fc3e7883c037faa87d21a694ec89ef8af2971c5b575fb0672ba71624cd147
|
Provenance
The following attestation bundles were made for zuvloop-0.0.4-cp314-cp314-manylinux_2_28_x86_64.whl:
Publisher:
publish.yml on Kludex/zuvloop
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
zuvloop-0.0.4-cp314-cp314-manylinux_2_28_x86_64.whl -
Subject digest:
7e04958f2ed7f809ce27afd923fcf1e61fe79d366102d74beb0e69ca0d1d5cdc - Sigstore transparency entry: 2435177195
- Sigstore integration time:
-
Permalink:
Kludex/zuvloop@49a8b5b5ef92f6d98c05ddb68d23d7480bc944fc -
Branch / Tag:
refs/tags/v0.0.4 - Owner: https://github.com/Kludex
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@49a8b5b5ef92f6d98c05ddb68d23d7480bc944fc -
Trigger Event:
push
-
Statement type:
File details
Details for the file zuvloop-0.0.4-cp314-cp314-manylinux_2_28_aarch64.whl.
File metadata
- Download URL: zuvloop-0.0.4-cp314-cp314-manylinux_2_28_aarch64.whl
- Upload date:
- Size: 1.5 MB
- Tags: CPython 3.14, manylinux: glibc 2.28+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5f86ef2b2e3a3352eadd06f80bf2e58fbdc043d2493a24c19f7d8607cc680ec9
|
|
| MD5 |
e716455d4f06ee9477ee64a1858747da
|
|
| BLAKE2b-256 |
bcae1fd54c1e72b3c2f0d40727f546fcec54523be542de120a2ab7142040f533
|
Provenance
The following attestation bundles were made for zuvloop-0.0.4-cp314-cp314-manylinux_2_28_aarch64.whl:
Publisher:
publish.yml on Kludex/zuvloop
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
zuvloop-0.0.4-cp314-cp314-manylinux_2_28_aarch64.whl -
Subject digest:
5f86ef2b2e3a3352eadd06f80bf2e58fbdc043d2493a24c19f7d8607cc680ec9 - Sigstore transparency entry: 2435177555
- Sigstore integration time:
-
Permalink:
Kludex/zuvloop@49a8b5b5ef92f6d98c05ddb68d23d7480bc944fc -
Branch / Tag:
refs/tags/v0.0.4 - Owner: https://github.com/Kludex
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@49a8b5b5ef92f6d98c05ddb68d23d7480bc944fc -
Trigger Event:
push
-
Statement type:
File details
Details for the file zuvloop-0.0.4-cp314-cp314-macosx_11_0_arm64.whl.
File metadata
- Download URL: zuvloop-0.0.4-cp314-cp314-macosx_11_0_arm64.whl
- Upload date:
- Size: 330.6 kB
- Tags: CPython 3.14, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e4ef1251205235a7b247dd7c610cf63ce2bebe54c8e8a6b8fef3fc8361c5512c
|
|
| MD5 |
7e27c92750e43d392b28aaedd03b6752
|
|
| BLAKE2b-256 |
7fce96a8401cbfa914ea56eb3be21418ea4b24e1e347fb3185b74b534d176e79
|
Provenance
The following attestation bundles were made for zuvloop-0.0.4-cp314-cp314-macosx_11_0_arm64.whl:
Publisher:
publish.yml on Kludex/zuvloop
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
zuvloop-0.0.4-cp314-cp314-macosx_11_0_arm64.whl -
Subject digest:
e4ef1251205235a7b247dd7c610cf63ce2bebe54c8e8a6b8fef3fc8361c5512c - Sigstore transparency entry: 2435177109
- Sigstore integration time:
-
Permalink:
Kludex/zuvloop@49a8b5b5ef92f6d98c05ddb68d23d7480bc944fc -
Branch / Tag:
refs/tags/v0.0.4 - Owner: https://github.com/Kludex
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@49a8b5b5ef92f6d98c05ddb68d23d7480bc944fc -
Trigger Event:
push
-
Statement type:
File details
Details for the file zuvloop-0.0.4-cp314-cp314-macosx_10_15_x86_64.whl.
File metadata
- Download URL: zuvloop-0.0.4-cp314-cp314-macosx_10_15_x86_64.whl
- Upload date:
- Size: 331.4 kB
- Tags: CPython 3.14, macOS 10.15+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e3655e04bda3ad89410ebfe7c5c2a2fc72cf220cc83e04ac587b84a928c36091
|
|
| MD5 |
03f8fbd3dd71b61c2392000af4cdfd7b
|
|
| BLAKE2b-256 |
bcd7e0db7fa26b5b1fc758bab9ca1c54ea71e3a542a072cc3940c066849f0cc4
|
Provenance
The following attestation bundles were made for zuvloop-0.0.4-cp314-cp314-macosx_10_15_x86_64.whl:
Publisher:
publish.yml on Kludex/zuvloop
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
zuvloop-0.0.4-cp314-cp314-macosx_10_15_x86_64.whl -
Subject digest:
e3655e04bda3ad89410ebfe7c5c2a2fc72cf220cc83e04ac587b84a928c36091 - Sigstore transparency entry: 2435177298
- Sigstore integration time:
-
Permalink:
Kludex/zuvloop@49a8b5b5ef92f6d98c05ddb68d23d7480bc944fc -
Branch / Tag:
refs/tags/v0.0.4 - Owner: https://github.com/Kludex
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@49a8b5b5ef92f6d98c05ddb68d23d7480bc944fc -
Trigger Event:
push
-
Statement type: