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. Over 20x faster than asyncio at thread-safe scheduling and faster than uvloop on 10 of the 11 benchmarks 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.3, and libuv 1.51.0. The labels show the absolute numbers.

Benchmark asyncio uvloop zuvloop
call_soon 2.35M/s 5.05M/s 6.54M/s
call_soon with arguments 2.27M/s 3.53M/s 6.07M/s
call_soon_threadsafe 0.38M/s 5.15M/s 8.48M/s
timer schedule + cancel 1.46M/s 2.37M/s 8.95M/s
bulk stream 6.6 GiB/s 6.9 GiB/s 9.4 GiB/s
echo round trips, 1 KiB 37.2k/s 42.3k/s 51.9k/s
uvicorn, plaintext 47.0k req/s 63.6k req/s 67.3k req/s
uvicorn, 10 KiB body 44.5k req/s 59.9k req/s 64.3k req/s
aiohttp server 42.5k req/s 51.7k req/s 52.9k req/s
aiohttp client 9.49k req/s 11.5k req/s 11.1k req/s
getaddrinfo, numeric host 20.7k/s 1.47M/s 1.77M/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, macOS or Windows
  • Prebuilt wheels for Linux x86-64/AArch64, macOS x86-64/arm64 and Windows AMD64/ARM64

Installation

$ pip install zuvloop

Source distributions install a pinned Zig 0.16 toolchain in their isolated build environment. Direct native development commands require Zig 0.16 on PATH. Free-threaded CPython builds are not supported and fail explicitly at build time.

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. Its small runtime surface is opentelemetry-api plus typing-extensions for the shipped type declarations — no SDK and 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_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

Every pull request is held behind three stable aggregate gates. The first covers the in-repository suite on Linux and macOS, the portable suite on Windows, plus musl runtime tests, cross-compilation, ReleaseSafe builds and documentation. The second runs CPython conformance and pinned upstream suites from aiohttp, uvicorn, AnyIO, websockets, aioquic, Tornado and HTTPX2 with zuvloop swapped underneath. The third runs the native sanitizer build and a 500-cycle resource-ownership soak.

The weekly compatibility run also tests CPython 3.14.0, the newest 3.14 patch and the 3.15 prerelease, then exercises gRPC AsyncIO, asyncpg, Psycopg and redis-py against real local services. The immutable commits and exact commands in .github/workflows/compatibility.yml are the source of truth.

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.

The aiohttp compatibility run disables its optional blockbuster plugin because that plugin exempts stdlib asyncio calls by source filename and therefore reports equivalent os.stat and os.sendfile calls from any third-party loop. Its remaining strict expected failure is the loop.time() difference below. One concurrent WebSocket-close test is skipped because it assumes selector-loop ready/I/O ordering and fails intermittently on uvloop too; every other aiohttp test remains enforced.

(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 the hatch-ziglang build hook 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.

Metadata

Release files for zuvloop 0.0.8

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for zuvloop 0.0.8
File Size Uploaded
zuvloop-0.0.8.tar.gz 1.4 MB Details

Built distributions (wheels)

Table of built distributions (wheels) for zuvloop 0.0.8
File
zuvloop-0.0.8-cp314-cp314-win_arm64.whl CPython 3.14 CPython 3.14 Windows ARM64 Details
zuvloop-0.0.8-cp314-cp314-win_amd64.whl CPython 3.14 CPython 3.14 Windows x86-64 Details
zuvloop-0.0.8-cp314-cp314-musllinux_1_2_x86_64.whl CPython 3.14 CPython 3.14 Linux musl 1.2+ x86-64 Details
zuvloop-0.0.8-cp314-cp314-musllinux_1_2_aarch64.whl CPython 3.14 CPython 3.14 Linux musl 1.2+ ARM64 Details
zuvloop-0.0.8-cp314-cp314-manylinux_2_28_x86_64.whl CPython 3.14 CPython 3.14 Linux glibc 2.28+ x86-64 Details
zuvloop-0.0.8-cp314-cp314-manylinux_2_28_aarch64.whl CPython 3.14 CPython 3.14 Linux glibc 2.28+ ARM64 Details
zuvloop-0.0.8-cp314-cp314-macosx_11_0_arm64.whl CPython 3.14 CPython 3.14 macOS 11.0+ ARM64 Details
zuvloop-0.0.8-cp314-cp314-macosx_10_15_x86_64.whl CPython 3.14 CPython 3.14 macOS 10.15+ x86-64 Details

Total release size: 9.1 MB

Release files / zuvloop-0.0.8.tar.gz

Download URL zuvloop-0.0.8.tar.gz
Size 1.4 MB
Tags Source
SHA-256 checksum
How to use checksums
70f69569d87a506f5d26ca5029d839148c391007d8fc4fa0ce6181274fa93cf2
BLAKE2b-256 checksum
How to use checksums
4814417783bfbedb09fb29af6ea7fef113bc5f870319c67349037e361a4398bb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 18, 2026.

Transparency log

Release files / zuvloop-0.0.8-cp314-cp314-win_arm64.whl

Download URL zuvloop-0.0.8-cp314-cp314-win_arm64.whl
Size 417.9 kB
Tags CPython 3.14 Windows ARM64
SHA-256 checksum
How to use checksums
e2a0bd88ff334dff8b265c965aed47f58bcf77390be80c75de5b26fc64860a77
BLAKE2b-256 checksum
How to use checksums
d557dd9d36eb6f98a69a0c9e9e8e6c1b86bc1add971d1f418f2a26da430e2691
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 18, 2026.

Transparency log

Release files / zuvloop-0.0.8-cp314-cp314-win_amd64.whl

Download URL zuvloop-0.0.8-cp314-cp314-win_amd64.whl
Size 436.3 kB
Tags CPython 3.14 Windows x86-64
SHA-256 checksum
How to use checksums
26facd69be33b1b8d9ecf4d418eb3fe1f06aceb0b943f053640a330511b6fe80
BLAKE2b-256 checksum
How to use checksums
db6e9ec55e8dbc5a77e00225617fb360aed87b22eedda4d001dec630fe734629
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 18, 2026.

Transparency log

Release files / zuvloop-0.0.8-cp314-cp314-musllinux_1_2_x86_64.whl

Download URL zuvloop-0.0.8-cp314-cp314-musllinux_1_2_x86_64.whl
Size 1.5 MB
Tags CPython 3.14 Linux musl 1.2+ x86-64
SHA-256 checksum
How to use checksums
ea485ca37394ff3a82cc46e1ee5e3ff64df98aebdd55b5844db941ed3b3f3124
BLAKE2b-256 checksum
How to use checksums
68f523bb13a602a56a099e51bb7439540f712cad36ca73a0009d2030676a19df
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 18, 2026.

Transparency log

Release files / zuvloop-0.0.8-cp314-cp314-musllinux_1_2_aarch64.whl

Download URL zuvloop-0.0.8-cp314-cp314-musllinux_1_2_aarch64.whl
Size 1.5 MB
Tags CPython 3.14 Linux musl 1.2+ ARM64
SHA-256 checksum
How to use checksums
3d0211e4cdaf754c890c95390794940b46bb5ee0031b70ff130b8e92c4f79fcb
BLAKE2b-256 checksum
How to use checksums
91dae72a2865ba57ce00121306be0d7eac4ab21e1814b586659aa3e432f3fc07
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 18, 2026.

Transparency log

Release files / zuvloop-0.0.8-cp314-cp314-manylinux_2_28_x86_64.whl

Download URL zuvloop-0.0.8-cp314-cp314-manylinux_2_28_x86_64.whl
Size 1.6 MB
Tags CPython 3.14 Linux glibc 2.28+ x86-64
SHA-256 checksum
How to use checksums
ed2536170459e8f595e3cea081f4c3d5d91e776df0887f412604e02e39da3815
BLAKE2b-256 checksum
How to use checksums
da66e0380fa6016d6eaa57bfd252637108ac0fe8d164ee91696a9d0780c1d887
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 18, 2026.

Transparency log

Release files / zuvloop-0.0.8-cp314-cp314-manylinux_2_28_aarch64.whl

Download URL zuvloop-0.0.8-cp314-cp314-manylinux_2_28_aarch64.whl
Size 1.5 MB
Tags CPython 3.14 Linux glibc 2.28+ ARM64
SHA-256 checksum
How to use checksums
1b5819bf21ae6a94d0209f0b7fc3078ab0d5169ff2b239fcfcc6a1341b867ecf
BLAKE2b-256 checksum
How to use checksums
d3cd272f19781c40c41ef496ed903094001bd7af2b0e03bdd57694b36c118f58
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 18, 2026.

Transparency log

Release files / zuvloop-0.0.8-cp314-cp314-macosx_11_0_arm64.whl

Download URL zuvloop-0.0.8-cp314-cp314-macosx_11_0_arm64.whl
Size 335.5 kB
Tags CPython 3.14 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
a0490193dad9f1e89e3935f85d5983af0ce9e136164a509fd74802d86c1f0dce
BLAKE2b-256 checksum
How to use checksums
b8941187e698530e09557f463434301c6de30d443abc79f1714897626d245ec2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 18, 2026.

Transparency log

Release files / zuvloop-0.0.8-cp314-cp314-macosx_10_15_x86_64.whl

Download URL zuvloop-0.0.8-cp314-cp314-macosx_10_15_x86_64.whl
Size 336.1 kB
Tags CPython 3.14 macOS 10.15+ x86-64
SHA-256 checksum
How to use checksums
6f092597890d3e4049cfa69e4951bc2e8fa3972ce3c93170e3e470493e3d9146
BLAKE2b-256 checksum
How to use checksums
74f93ae602d3bf007abaae8855a18479d1ec0d39fb2c4a7b84c0a631089fa55b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 18, 2026.

Transparency log

Release history Release notifications | RSS feed

0.0.9

17 release files

This release

0.0.8 This release

9 release files

0.0.7

9 release files

0.0.6

7 release files

0.0.5

7 release files

0.0.4

7 release files

0.0.3

7 release files

0.0.2

7 release files

0.0.1

7 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page