Skip to main content

High-performance QUIC/HTTP3 library — picoquic-backed, qh3-compatible asyncio API

Project description

aiopquic - Async QUIC + WebTransport (picoquic)

aiopquic is a Python/Cython binding to picoquic, providing high-performance QUIC transport and WebTransport for asyncio applications.

Overview

aiopquic exposes picoquic's QUIC implementation through a lock-free SPSC ring buffer architecture that bridges the picoquic network thread with Python's asyncio event loop. It provides an asyncio QUIC/HTTP3 transport API in the spirit of aioquic (and its fork qh3) — similar shapes for QuicConfiguration, QuicConnection, connect / serve, and event types — plus a native WebTransport client/server layered on picoquic's H3 + h3zero. Not a drop-in replacement: semantics differ around backpressure (send_stream_data raises BufferError on full per-stream ring) and flow-control sizing.

Architecture

  • SPSC Ring Buffers -- Lock-free single producer/single consumer rings for event passing between threads, separate TX and RX rings per TransportContext.
  • TX path -- Asyncio pushes into per-stream byte ring; picoquic pulls at wire rate via prepare_to_send.
  • RX path -- picoquic pushes per-event StreamChunks; ownership transfers at pop for 1-copy delivery.
  • Cross-platform wake fd -- Linux eventfd for efficient asyncio add_reader() notification; pipe() self-pipe fallback on macOS / BSD.
  • Dedicated Network Thread -- picoquic runs in its own thread via picoquic_start_network_thread().
  • Cython Bridge -- Thin Cython layer over C callbacks, minimal overhead.
  • WebTransport -- asyncio.webtransport.WebTransportSession (client + server) over picoquic's picowt_* API and h3zero.

Features

  • QUIC client and server: connect, serve, QuicConnectionProtocol
  • Stream data send/receive with FIN signaling, stream reset, stop_sending
  • WebTransport client + server: serve_webtransport, WebTransportSession
  • QUIC datagram TX + RX (note: WebTransport datagram TX not yet wired)
  • Connection migration / 0-RTT (inherited from picoquic)
  • Connection management: create, close, idle timeout, application close codes
  • Per-cnx multiplexing on the server side via QuicEngine
  • TLS keylog (NSS Key Log Format) for pcap decryption
  • Native picoquic_ct / picohttp_ct subprocess smoke (catches upstream regressions on every submodule update)

Test Results

Tests pass on Linux and macOS. The interop suite is opt-in (network-dependent).

Suite Coverage
test_spsc_ring per-event malloc ring lifecycle
test_buffer Cython Buffer
test_transport Transport lifecycle, wake fd, wake-up, connection management
test_loopback 17 tests — handshake, streams, FIN, reset, datagrams, ALPN mismatch, idle timeout, app-close codes, stop_sending, many-streams stress, TX-ring overflow
test_asyncio client/server stream + datagram exchange via connect / serve
test_baton_pattern Pure-QUIC baton-style stream multiplexing (UNI ↔ BIDI)
test_native_picoquic picoquic_ct / picohttp_ct subprocess driver
test_interop Real public endpoints (opt-in)
tests/bench/ microbenches: ring push/pop, single-shot/sustained/parallel/bidirectional throughput, datagrams, RTT latency, handshake rate, byte-verifying object stress + stream churn + concurrent streams (opt-in via pytest tests/bench)

Performance

Sustained single-stream throughput, 30s steady-state, byte-verifying, high-level asyncio API (QuicConnection.send_stream_data):

platform 1 KiB 4 KiB 16 KiB
AMD Ryzen 7 PRO 7840U / WSL2 / Linux 6.6 1,570 Mbps 2,118 Mbps 2,031 Mbps
Apple M-series / macOS Sonoma 953 Mbps 1,130 Mbps 1,104 Mbps

These are over local UDP loopback at the QUIC default MTU (~1,400 B). The realistic ceiling at that MTU is the kernel's per-syscall sendmsg rate, not bandwidth. On Ryzen WSL2, raw iperf3 -u -l 1400 over loopback maxes at 3.15 Gbps (≈ 280 K syscalls/s); raise the datagram size and it climbs cleanly — 4 KiB → 7.9, 8 KiB → 12.8, 32 KiB → 33.7 Gbps. So QUIC pinned at MTU is in a regime where the syscall rate is the wall.

In that regime, here's where the layers land on Ryzen WSL2:

layer ss_mbps of UDP@1400 ceiling
iperf3 -u -l 1400 (raw UDP loopback) 3,150 100 %
picoquicdemo -a perf (picoquic over UDP) 2,184 69 %
aiopquic lowlevel (SPSC ring + UDP) 2,322 74 %
aiopquic highlevel (asyncio + SPSC + UDP) 2,031 64 %
sim_link_bench (picoquic only, no kernel UDP) 11,216 (off-axis)

The asyncio wrapper costs ~10 % below the lowlevel SPSC path; picoquic's own QUIC framing/encryption/ACK overhead accounts for ~25 % vs raw UDP. Both are normal for QUIC-over-loopback at MTU.

sim_link_bench (tests/bench/sim_link/) drives picoquic over its picoquictest_sim_link simulated link — packets are routed in-process between two picoquic_quic_t instances, no kernel UDP, no sockets, no syscall-rate ceiling. It isolates picoquic protocol CPU cost from the loopback wall and is platform-independent. The 11.2 Gbps number above is what picoquic can do without any kernel involvement on this hardware. Build with ./tests/bench/sim_link/build.sh after ./build_picoquic.sh.

Calibrate on your own hardware:

# UDP-over-loopback path (what aiopquic users actually see)
pytest tests/bench/bench_baselines_highlevel.py -s -v          # 30s default
pytest tests/bench/bench_baselines_highlevel.py -s -v --duration=60

# Protocol-only reference (no kernel UDP)
PICOQUIC_SOLUTION_DIR=third_party/picoquic/ \
    tests/bench/sim_link/sim_link_bench --duration-s 30 --rate-gbps 100

Microbenches (ring lifecycle, stream churn, concurrent-streams short bursts) live under tests/bench/ for development reference. Their reported numbers are not representative of sustained throughput — short windows inflate numbers from warmup transients (a 100-stream churn case at 256 B per stream measures ~1 ms of work, dominated by setup cost).

Installation

Wheels for cp312 / cp313 / cp314 on Linux (manylinux_2_34, glibc 2.34+) and macOS arm64 are published to PyPI:

uv pip install aiopquic     # or: pip install aiopquic

For older Linux (glibc 2.28–2.33) install via sdist; build toolchain required.

From source

git clone https://github.com/gmarzot/aiopquic.git
cd aiopquic
git submodule update --init --recursive
./bootstrap_python.sh    # creates .venv with uv-managed Python 3.14 (GIL build) and pins cython 3.2+
source .venv/bin/activate
./build_picoquic.sh      # builds picotls, picoquic, native test drivers
uv pip install -e '.[dev]'    # or: pip install -e '.[dev]'

On macOS, set OPENSSL_ROOT_DIR if Homebrew OpenSSL is not auto-detected (the build script tries openssl@3 then openssl@1.1).

Usage

Low-level Transport API

from aiopquic._binding._transport import TransportContext

server = TransportContext()
server.start(port=4433, cert_file="cert.pem", key_file="key.pem", alpn="moq-00", is_client=False)

client = TransportContext()
client.start(port=0, alpn="moq-00", is_client=True)
client.create_client_connection("127.0.0.1", 4433, sni="localhost", alpn="moq-00")

Asyncio API

from aiopquic.asyncio.client import connect
from aiopquic.quic.configuration import QuicConfiguration

configuration = QuicConfiguration(alpn_protocols=["myproto"], is_client=True)

async with connect("server", 4433, configuration=configuration) as protocol:
    quic = protocol._quic
    stream_id = quic.get_next_available_stream_id()
    quic.send_stream_data(stream_id, payload, end_stream=True)
    protocol.transmit()

payload is opaque bytes; the library doesn't impose framing. Consumers that want HTTP/3 layer on top of aiopquic's picowt-backed h3zero plumbing; consumers that want WebTransport use serve_webtransport / connect_webtransport. Most direct users of the asyncio API ship their own protocol bytes (MoQT, custom binary frames, etc.).

WebTransport

from aiopquic.asyncio.webtransport import (
    serve_webtransport, WebTransportSession,
)
# See src/aiopquic/asyncio/webtransport.py and tests/ for full examples.

Development

uv pip install -e '.[dev]'    # or: pip install -e '.[dev]'
python -m pytest tests/ -v -m "not interop and not native"

# Microbenches (opt-in)
python -m pytest tests/bench

Known Limitations

  • Free-threaded Python (3.14t) not yet supported -- the TX-ring producer side, TransportContext lifecycle, and the WebTransport engine state currently rely on the GIL for serialization. FT support deferred until a per-context locking audit lands.
  • STOP_SENDING error codes surface as 0 today: picoquic's public stream-error getter only returns the RESET_STREAM code. STOP_SENDING's code lives in stream->remote_stop_error in picoquic_internal.h (no public getter). A small helper that pulls the field is straightforward future work — see TODO in src/aiopquic/_binding/c/callback.h.
  • Per-stream wrapper cleanup before connection close -- per-stream aiopquic_stream_ctx_t* wrappers are freed at connection close rather than at stream RESET/FIN. Bounded leak per cnx; flagged for follow-up.

TODO

  • Windows support (eventfd alternative — IOCP / WSAEventSelect on the wake-fd path)
  • Free-threaded Python (3.14t) support after producer-side locking audit
  • STOP_SENDING error-code surfacing helper (read remote_stop_error from picoquic_internal.h)
  • Per-stream wrapper cleanup on RESET/FIN before connection close
  • WebTransport datagram TX path through the C bridge
  • Datagram benches: latency percentiles, payload-size sweep, loss / jitter under load (today's bench_datagram is fire-and-count throughput only)
  • Pure stream open/close microbench (lifecycle rate without payload, separate from bench_stream_churn_highlevel which bundles writes + FIN)
  • Submit aiopquic to the QUIC interop runner for cross-implementation coverage

Resources



A Marz Research project.
Author: G. S. Marzot <gmarzot@marzresearch.net>

License

MIT License -- see LICENSE

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

aiopquic-0.3.4.tar.gz (456.6 kB view details)

Uploaded Source

Built Distributions

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

aiopquic-0.3.4-cp314-cp314-manylinux_2_34_x86_64.whl (4.3 MB view details)

Uploaded CPython 3.14manylinux: glibc 2.34+ x86-64

aiopquic-0.3.4-cp314-cp314-manylinux_2_34_aarch64.whl (4.1 MB view details)

Uploaded CPython 3.14manylinux: glibc 2.34+ ARM64

aiopquic-0.3.4-cp314-cp314-macosx_14_0_arm64.whl (3.6 MB view details)

Uploaded CPython 3.14macOS 14.0+ ARM64

aiopquic-0.3.4-cp313-cp313-manylinux_2_34_x86_64.whl (4.3 MB view details)

Uploaded CPython 3.13manylinux: glibc 2.34+ x86-64

aiopquic-0.3.4-cp313-cp313-manylinux_2_34_aarch64.whl (4.0 MB view details)

Uploaded CPython 3.13manylinux: glibc 2.34+ ARM64

aiopquic-0.3.4-cp313-cp313-macosx_14_0_arm64.whl (3.6 MB view details)

Uploaded CPython 3.13macOS 14.0+ ARM64

aiopquic-0.3.4-cp312-cp312-manylinux_2_34_x86_64.whl (4.3 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.34+ x86-64

aiopquic-0.3.4-cp312-cp312-manylinux_2_34_aarch64.whl (4.1 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.34+ ARM64

aiopquic-0.3.4-cp312-cp312-macosx_14_0_arm64.whl (3.6 MB view details)

Uploaded CPython 3.12macOS 14.0+ ARM64

File details

Details for the file aiopquic-0.3.4.tar.gz.

File metadata

  • Download URL: aiopquic-0.3.4.tar.gz
  • Upload date:
  • Size: 456.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.5

File hashes

Hashes for aiopquic-0.3.4.tar.gz
Algorithm Hash digest
SHA256 72baaeae869cc0454c8ce1cd18b05042f52aa1ccfa965737020166d2f4822d8e
MD5 16778ba794140ec5f8f82f26d848e357
BLAKE2b-256 5f9efecd98f74dd912591aec25bec3a5fe30759cf313e575ac5de042d3c809d7

See more details on using hashes here.

File details

Details for the file aiopquic-0.3.4-cp314-cp314-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for aiopquic-0.3.4-cp314-cp314-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 ccd3e432d92719aabb1e7739fba42487180edcb5af7402c3fc0ad60d98b7fe77
MD5 96b9cf21b69f5b4b569672ec97032d52
BLAKE2b-256 944580a1ff495f38a380fa1de5a2eb0187a3bbf900d9429e90be070f05dfe6d3

See more details on using hashes here.

File details

Details for the file aiopquic-0.3.4-cp314-cp314-manylinux_2_34_aarch64.whl.

File metadata

File hashes

Hashes for aiopquic-0.3.4-cp314-cp314-manylinux_2_34_aarch64.whl
Algorithm Hash digest
SHA256 87a732f87b38871d81eca43b56eea7f48b26fb612513c3e15b78e68cca2e2694
MD5 3a37d01e75d6beb1f7d874cb72f79987
BLAKE2b-256 aed87c84c1d8ef2e49a8ab078b19bbdd75687717c1736a3c2faa129ec7414f47

See more details on using hashes here.

File details

Details for the file aiopquic-0.3.4-cp314-cp314-macosx_14_0_arm64.whl.

File metadata

File hashes

Hashes for aiopquic-0.3.4-cp314-cp314-macosx_14_0_arm64.whl
Algorithm Hash digest
SHA256 be61080e4bb70a9c65d5cf50e6e39c08384ce235a7de254d8722483b28bdf87c
MD5 07aba388ed2181b382b0e912c404e540
BLAKE2b-256 d323282a5760f71985afa2b5bd0c28a09d7a81cc748ce24b9d5fdd377bf50327

See more details on using hashes here.

File details

Details for the file aiopquic-0.3.4-cp313-cp313-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for aiopquic-0.3.4-cp313-cp313-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 735d7579cf10ca62bbb34a074d599105591e0447dee57321edc37bb4e2dc7090
MD5 6792c65133176a9f32d6942b7b06f66b
BLAKE2b-256 df3e71d97ae6df7dbbce2685123af37a0a92a4d23cd9d46fbe68836cd3e211f8

See more details on using hashes here.

File details

Details for the file aiopquic-0.3.4-cp313-cp313-manylinux_2_34_aarch64.whl.

File metadata

File hashes

Hashes for aiopquic-0.3.4-cp313-cp313-manylinux_2_34_aarch64.whl
Algorithm Hash digest
SHA256 da234312dbaf5cefb36726dae5460c23812d8786a8bd1cee51fe851ae66a8aec
MD5 85c1636f1904c2dc0c0aa97a46792a0e
BLAKE2b-256 46039906e63c72c9da1da88b8da92aace3fe9c48a344ad50540889bc66dd9d7a

See more details on using hashes here.

File details

Details for the file aiopquic-0.3.4-cp313-cp313-macosx_14_0_arm64.whl.

File metadata

File hashes

Hashes for aiopquic-0.3.4-cp313-cp313-macosx_14_0_arm64.whl
Algorithm Hash digest
SHA256 f8d4f229ab57faeab58867f3bd6d19d8547df7d8dbad9ec8fe890aac92d697d5
MD5 844aa16c816eae51323e7d5a67a3ddd7
BLAKE2b-256 90d156d41b96217b7cda041a7421b8fc72a216296ce12ba57311e2e8af3f53de

See more details on using hashes here.

File details

Details for the file aiopquic-0.3.4-cp312-cp312-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for aiopquic-0.3.4-cp312-cp312-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 2f3ae552f7132f8a90e60e61ef1594cdc1467e85e675376334ff4bec40650bdf
MD5 edcc0adeac526bbe4897e283e028f206
BLAKE2b-256 29ec71d0014f8044689b172478e30d42ce0b8502ac59ab65ed95a633e5eb4e4d

See more details on using hashes here.

File details

Details for the file aiopquic-0.3.4-cp312-cp312-manylinux_2_34_aarch64.whl.

File metadata

File hashes

Hashes for aiopquic-0.3.4-cp312-cp312-manylinux_2_34_aarch64.whl
Algorithm Hash digest
SHA256 f8b570c507e975e93908853825776d857152e57b1ba7321bad25e2626f5e9147
MD5 2bc165fc818de5ca95c02df4eab980ac
BLAKE2b-256 a405e11bc35572e4371aa58b154b87767ca40a1033edec725a9d04d3c8afdb1d

See more details on using hashes here.

File details

Details for the file aiopquic-0.3.4-cp312-cp312-macosx_14_0_arm64.whl.

File metadata

File hashes

Hashes for aiopquic-0.3.4-cp312-cp312-macosx_14_0_arm64.whl
Algorithm Hash digest
SHA256 55349c6186d1ab2a964e104026cd0b97eb93f25371b5512c8a510c8f8248bcd6
MD5 c7c72107882e219cef06f2c26d870122
BLAKE2b-256 857c85f27fc1cc436e497847a460815d3363415bf6e426f10a627e2c6a6609e3

See more details on using hashes here.

Supported by

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