Skip to main content

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 — same Task objects, 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

zuvloop vs uvloop vs asyncio benchmarks

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.69M/s 4.69M/s 5.91M/s
call_soon with arguments 2.43M/s 3.87M/s 6.47M/s
timer schedule + cancel 1.58M/s 2.62M/s 9.55M/s
bulk stream 8.4 GiB/s 8.5 GiB/s 10.2 GiB/s
echo round trips, 1 KiB 39.0k/s 56.8k/s 58.5k/s
uvicorn, plaintext 55.3k req/s 71.9k req/s 75.9k req/s
uvicorn, 10 KiB body 52.6k req/s 68.7k req/s 73.6k req/s
aiohttp server 49.0k req/s 59.8k req/s 60.6k req/s
aiohttp client 13.2k req/s 16.2k req/s 16.9k req/s
getaddrinfo, numeric host 28.5k/s 1.57M/s 1.90M/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. Until your application installs a provider, the instruments are no-ops and cost nothing.

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 (only when a real provider is installed, so an uninstrumented program never pays for sampling).

You get:

  • zuvloop.slow_callback spans — with real start/end timestamps measured by uv_hrtime() in native code, and the awaiting call graph attached (via asyncio.format_call_graph()), so you see why a callback was running, not just its repr.
  • zuvloop.unhandled_exception spans — 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.

Also not implemented: sendfile() and sock_sendfile() raise NotImplementedError. Handles returned by call_soon and call_later implement the asyncio.Handle and asyncio.TimerHandle interfaces but are not instances of those classes; call_soon_threadsafe does return 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 --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>.

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

zuvloop-0.0.1.tar.gz (1.4 MB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

zuvloop-0.0.1-cp314-cp314-musllinux_1_2_x86_64.whl (1.5 MB view details)

Uploaded CPython 3.14musllinux: musl 1.2+ x86-64

zuvloop-0.0.1-cp314-cp314-musllinux_1_2_aarch64.whl (1.4 MB view details)

Uploaded CPython 3.14musllinux: musl 1.2+ ARM64

zuvloop-0.0.1-cp314-cp314-manylinux_2_28_x86_64.whl (1.6 MB view details)

Uploaded CPython 3.14manylinux: glibc 2.28+ x86-64

zuvloop-0.0.1-cp314-cp314-manylinux_2_28_aarch64.whl (1.5 MB view details)

Uploaded CPython 3.14manylinux: glibc 2.28+ ARM64

zuvloop-0.0.1-cp314-cp314-macosx_11_0_arm64.whl (314.2 kB view details)

Uploaded CPython 3.14macOS 11.0+ ARM64

zuvloop-0.0.1-cp314-cp314-macosx_10_15_x86_64.whl (316.6 kB view details)

Uploaded CPython 3.14macOS 10.15+ x86-64

File details

Details for the file zuvloop-0.0.1.tar.gz.

File metadata

  • Download URL: zuvloop-0.0.1.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

Hashes for zuvloop-0.0.1.tar.gz
Algorithm Hash digest
SHA256 a4ca5131ca01814b130de4371e5cc8cb57a83970af46ffbb21be238076bd272c
MD5 6da58d26a38948dfb5e66f7c649a8994
BLAKE2b-256 eeacfcd28da6d6184b8d1353c4e217809e973b51fa5204e182e1ee067c7e4168

See more details on using hashes here.

Provenance

The following attestation bundles were made for zuvloop-0.0.1.tar.gz:

Publisher: publish.yml on Kludex/zuvloop

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file zuvloop-0.0.1-cp314-cp314-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for zuvloop-0.0.1-cp314-cp314-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 9be476fdfaf8ef86fcb4fb461949c6476d8ea643e4e460a33f8e57fe735b902f
MD5 7cd8a778178234317d7c8d2551ddd74a
BLAKE2b-256 daaa10beaf0d09c82a6a088710adf413e6eb19e327b0e68a823e3ebafae5968c

See more details on using hashes here.

Provenance

The following attestation bundles were made for zuvloop-0.0.1-cp314-cp314-musllinux_1_2_x86_64.whl:

Publisher: publish.yml on Kludex/zuvloop

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file zuvloop-0.0.1-cp314-cp314-musllinux_1_2_aarch64.whl.

File metadata

File hashes

Hashes for zuvloop-0.0.1-cp314-cp314-musllinux_1_2_aarch64.whl
Algorithm Hash digest
SHA256 938fa122b4d5f608dcafc5311ebef385905dcf7ea94127becb71340d50e6ba2b
MD5 964c323f60a9bbf53510d3cafbdbdd4d
BLAKE2b-256 ed77005a49c80cd207eba919e3dd2d0d102bf28d248a9886fe136c0ddc319bef

See more details on using hashes here.

Provenance

The following attestation bundles were made for zuvloop-0.0.1-cp314-cp314-musllinux_1_2_aarch64.whl:

Publisher: publish.yml on Kludex/zuvloop

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file zuvloop-0.0.1-cp314-cp314-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for zuvloop-0.0.1-cp314-cp314-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 2ce256551ce0ae0d7a1a456583aa8e98db746cff2b12aa74fbffaed3cc9b3b7c
MD5 1a5d053e9900fb27effe7170080e62ba
BLAKE2b-256 cb7ea53013ada496562fbfe27a49ab59f3e5ec16792bac784cb8d66a3a24f324

See more details on using hashes here.

Provenance

The following attestation bundles were made for zuvloop-0.0.1-cp314-cp314-manylinux_2_28_x86_64.whl:

Publisher: publish.yml on Kludex/zuvloop

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file zuvloop-0.0.1-cp314-cp314-manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for zuvloop-0.0.1-cp314-cp314-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 4238936344c2fb43d5f210b1373c7392314c15187a26de5cf4b34f8cdbf42655
MD5 5e358ba5dd6d204e1c050aa7afd4bc52
BLAKE2b-256 8ca8ccd5c31a9c01247798631dae84c065617edcd19052e4faa8d5f17cff0f07

See more details on using hashes here.

Provenance

The following attestation bundles were made for zuvloop-0.0.1-cp314-cp314-manylinux_2_28_aarch64.whl:

Publisher: publish.yml on Kludex/zuvloop

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file zuvloop-0.0.1-cp314-cp314-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for zuvloop-0.0.1-cp314-cp314-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 e6b9e2769179be1f9de27246b574c40cbc4ef046e8c27a228032c9b7ac227d5c
MD5 02fabdf59768d646981fcd102aa6cf20
BLAKE2b-256 feeba0cea3639c4423a333bf7dfc7162cc1579595befdbafc3ae8c5405c085cc

See more details on using hashes here.

Provenance

The following attestation bundles were made for zuvloop-0.0.1-cp314-cp314-macosx_11_0_arm64.whl:

Publisher: publish.yml on Kludex/zuvloop

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file zuvloop-0.0.1-cp314-cp314-macosx_10_15_x86_64.whl.

File metadata

File hashes

Hashes for zuvloop-0.0.1-cp314-cp314-macosx_10_15_x86_64.whl
Algorithm Hash digest
SHA256 993242e8ab7bdc630ced5fde124b42672921a777ce97db503dca95989bdbf2d0
MD5 92e310f2d079f9b7ec800fbef6ad78d0
BLAKE2b-256 a5b570410af9f6be549157189f9d497f3e856ceb8e75d9bf9a4259f573d576a9

See more details on using hashes here.

Provenance

The following attestation bundles were made for zuvloop-0.0.1-cp314-cp314-macosx_10_15_x86_64.whl:

Publisher: publish.yml on Kludex/zuvloop

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page