Skip to main content

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(). One worker thread per TransportContext; multiple contexts share the asyncio event loop within a single Python process.
  • 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
  • WebTransport client and server: connect_webtransport, serve_webtransport, WebTransportSession; subprotocol negotiation (wt_available_protocols / wt_supported_protocols); static files over HTTP/3 (serve_webtransport(web_folder=...))
  • Raw QUIC and WebTransport on one UDP port, dispatched by ALPN: serve_dispatch
  • Streams: FIN, reset, stop_sending, and RESET_STREAM_AT (reset_stream(..., reliable_size=N)); all-or-nothing writes with drain events for backpressure
  • QUIC and WebTransport datagrams, TX + RX
  • Stream and datagram priority on one scale: set_stream_priority, set_datagram_priority
  • Congestion control by name: bbr (v3, default), bbr1, cubic, dcubic, newreno, fast, prague, c4
  • Address validation with Retry (serve(..., retry=True)), QUIC v2 by compatible version negotiation (supported_versions), session resumption and 0-RTT (session_ticket_file)
  • TLS: certificate verification against certifi's roots or a private CA, post-quantum key exchange (KEX_X25519MLKEM768, OpenSSL 3.5), cipher suite selection, key update interval
  • Dual-stack IPv4/IPv6 servers, a bind host, ip_family to pin one family
  • Connection limits: max_connections, and set_max_connections at runtime (new connections are refused with CONNECTION_REFUSED)
  • Path MTU discovery policy: pmtud="basic" | "delayed" | "blocked"
  • Connection management: idle timeout, keep-alive, application close codes
  • Connection migration (inherited from picoquic)
  • Per-connection multiplexing on the server side via QuicEngine
  • qlog (qlog_dir), TLS keylog (NSS Key Log Format) for pcap decryption, and a version report (python -m aiopquic.versions)

Performance

Multi-Gbps sustained throughput over loopback on commodity hardware: 2+ Gbps through the full asyncio API at the QUIC default MTU (where the kernel's sendmsg syscall rate is the wall), 11+ Gbps protocol-only. Numbers, methodology, reproduction commands, build flags, and runtime tuning (jemalloc, GSO, wake thresholds) live in PERFORMANCE.md. The TX/RX dataflow and flow-control model — including the TX backpressure configuration parameters — are documented in DATAFLOW.md.

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
./bootstrap_python.sh    # creates .venv with uv-managed Python 3.14 (GIL build) and pins cython 3.2+
source .venv/bin/activate
./build.sh               # reconcile submodules, build picotls/picoquic + drivers, relink + verify
uv pip install -e '.[dev]'    # add dev/test extras — or: pip install -e '.[dev]'

Re-run ./build.sh after any git checkout or submodule bump — it is idempotent (a matching fingerprint skips the compile) and guarantees the imported aiopquic reflects the current source, host-tuned. ./build.sh --check is a read-only doctor that reports submodule drift, a stale native build, or a portable wheel shadowing the editable install, and exits nonzero — handy as a pre-benchmark or CI gate.

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

Reporting issues

Include the full version report in any issue — it captures aiopquic plus the picoquic + picotls submodule SHAs the binding was built from:

python -m aiopquic.versions   # or the console script: aiopquic-versions

Sample output:

aiopquic:  0.7.0rc2 (.venv/lib/python3.13/site-packages/aiopquic) [2026-10-29 09:21]
  - picoquic:  1.1.53.0 (2026.10.22.70b5fd5-0-70b5fd5a) [2026-10-22]
  - picotls:   master (f06553b8) [2026-09-30]
  - openssl:   OpenSSL 3.5.8 (.venv/lib/python3.13/site-packages/aiopquic.libs/libcrypto-c37e28d8.so.3.5.8)

If you're running aiomoqt on top, prefer python -m aiomoqt.versions — it chains through to this report and includes the aiomoqt version too.

Usage

QUIC client and server

Subclass QuicConnectionProtocol and override quic_event_received; self._quic is the connection. An echo server:

import asyncio

from aiopquic.asyncio import QuicConnectionProtocol, serve
from aiopquic.quic.configuration import QuicConfiguration
from aiopquic.quic.events import StreamDataReceived


class Echo(QuicConnectionProtocol):
    def quic_event_received(self, event):
        if isinstance(event, StreamDataReceived):
            self._quic.send_stream_data(event.stream_id, bytes(event.data),
                                        end_stream=event.end_stream)


async def main():
    cfg = QuicConfiguration(is_client=False, alpn_protocols=["echo"])
    cfg.load_cert_chain("cert.pem", "key.pem")
    server = await serve("::", 4433, configuration=cfg, create_protocol=Echo)
    try:
        await asyncio.Future()          # serve until cancelled
    finally:
        server.close()

asyncio.run(main())

And a client for it:

import asyncio

from aiopquic.asyncio import QuicConnectionProtocol, connect
from aiopquic.quic.configuration import QuicConfiguration
from aiopquic.quic.events import StreamDataReceived


class Client(QuicConnectionProtocol):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.reply = bytearray()
        self.done = asyncio.Event()

    def quic_event_received(self, event):
        if isinstance(event, StreamDataReceived):
            self.reply += event.data
            if event.end_stream:
                self.done.set()


async def main():
    cfg = QuicConfiguration(is_client=True, alpn_protocols=["echo"],
                            server_name="localhost", cafile="ca.pem")
    async with connect("localhost", 4433, configuration=cfg,
                       create_protocol=Client) as client:
        quic = client._quic
        sid = quic.get_next_available_stream_id()
        quic.send_stream_data(sid, b"hello", end_stream=True)
        await client.done.wait()
        print(bytes(client.reply))

asyncio.run(main())

Payloads are opaque bytes; the library imposes no framing. send_stream_data is all-or-nothing: BufferError means the stream's TX ring is full and nothing was taken, so resend the same buffer, or await send_stream_data_drained(...) to wait. DATAFLOW.md covers the backpressure model.

Clients verify the server's certificate chain and name against certifi's roots. A private CA goes in QuicConfiguration.cafile (or load_verify_locations(cafile=...)), and connect_webtransport(ca_file=...) takes the same file. To accept any certificate, such as a self-signed test server, set verify_mode=ssl.CERT_NONE or pass connect_webtransport(verify_peer=False). Clients offer a classic key share first; QuicConfiguration.key_exchange_groups (or start(key_exchange_groups=...)) puts KEX_X25519MLKEM768 first for a post-quantum key exchange with servers that support it.

WebTransport

The server calls handler(session) once per accepted CONNECT. session.events() yields new streams, datagrams and the session's close; receive_stream_data(sid) yields one stream's data up to its FIN or reset.

import asyncio

from aiopquic.asyncio.webtransport import serve_webtransport
from aiopquic.quic.events import (WebTransportNewStream,
                                  WebTransportStreamDataReceived)


async def echo(session, sid):
    async for ev in session.receive_stream_data(sid):
        if isinstance(ev, WebTransportStreamDataReceived):
            session.send_stream_data(sid, bytes(ev.data), end_stream=ev.end_stream)


async def handler(session):             # once per accepted CONNECT
    tasks = set()
    async for ev in session.events():
        if isinstance(ev, WebTransportNewStream):
            task = asyncio.create_task(echo(session, ev.stream_id))
            tasks.add(task)
            task.add_done_callback(tasks.discard)


async def main():
    server = await serve_webtransport("::", 4433, "/echo", handler=handler,
                                      cert_file="cert.pem", key_file="key.pem")
    try:
        await asyncio.Future()
    finally:
        server.close()

asyncio.run(main())
import asyncio

from aiopquic.asyncio.webtransport import connect_webtransport
from aiopquic.quic.events import WebTransportStreamDataReceived


async def main():
    async with connect_webtransport("localhost", 4433, "/echo",
                                    ca_file="ca.pem") as wt:
        sid = await wt.create_stream()
        wt.send_stream_data(sid, b"hello", end_stream=True)
        reply = bytearray()
        async for ev in wt.receive_stream_data(sid):
            if isinstance(ev, WebTransportStreamDataReceived):
                reply += ev.data
        print(bytes(reply))

asyncio.run(main())

connect_webtransport() and serve_webtransport() without configuration= run picoquic's defaults, not QuicConfiguration's; pass one to set congestion control, flow-control windows and the rest.

Raw QUIC and WebTransport on one port

serve_dispatch routes each connection by ALPN: raw QUIC to create_protocol, h3 to the WebTransport handler. With the Echo protocol and handler above:

server = await serve_dispatch("::", 4433, configuration=cfg, create_protocol=Echo,
                              wt_path="/echo", wt_handler=handler)

Low-level binding: TransportContext

The asyncio API runs on TransportContext, which owns one picoquic instance and its network thread. Use it directly only to share one transport between WebTransport sessions (connect_webtransport(..., transport=t), serve_webtransport(..., transport=t)) or in tests; the rest of aiopquic._binding._transport is internal.

from aiopquic._binding._transport import TransportContext

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

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

Development

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

# picoquic's own picoquic_ct / picohttp_ct suites, run as subprocesses (catches upstream
# regressions on a submodule update)
python -m pytest tests -m native

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

Test-suite coverage, benchmark methodology, performance builds (AIOPQUIC_PERF=1, io_uring scaffolding), and runtime tuning are documented in PERFORMANCE.md.

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.

TODO

  • Windows support (eventfd alternative — IOCP / WSAEventSelect on the wake-fd path)
  • Free-threaded Python (3.14t) support after producer-side locking audit
  • 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)
  • QUIC interop runner: client and server endpoints proposed upstream in quic-interop-runner#518, awaiting review

Acknowledgments

aiopquic is built on picoquic: QUIC, HTTP/3 and WebTransport all come from it. Many thanks to Christian Huitema, picoquic's author, and to its contributors for that work.

Resources



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

License

MIT License -- see LICENSE

Metadata

Release files for aiopquic 0.7.1

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

Source distribution (sdist)

Source distribution for aiopquic 0.7.1
File Size Uploaded Provenance Actions
aiopquic-0.7.1.tar.gz 3.9 MB Without provenance Details for aiopquic-0.7.1.tar.gz

Built distributions (wheels)

Table of built distributions (wheels) for aiopquic 0.7.1
File Actions
aiopquic-0.7.1-cp314-cp314-manylinux_2_34_x86_64.whl CPython 3.14 CPython 3.14 Linux glibc 2.34+ x86-64 Details for aiopquic-0.7.1-cp314-cp314-manylinux_2_34_x86_64.whl
aiopquic-0.7.1-cp314-cp314-manylinux_2_34_aarch64.whl CPython 3.14 CPython 3.14 Linux glibc 2.34+ ARM64 Details for aiopquic-0.7.1-cp314-cp314-manylinux_2_34_aarch64.whl
aiopquic-0.7.1-cp314-cp314-macosx_15_0_arm64.whl CPython 3.14 CPython 3.14 macOS 15.0+ ARM64 Details for aiopquic-0.7.1-cp314-cp314-macosx_15_0_arm64.whl
aiopquic-0.7.1-cp313-cp313-manylinux_2_34_x86_64.whl CPython 3.13 CPython 3.13 Linux glibc 2.34+ x86-64 Details for aiopquic-0.7.1-cp313-cp313-manylinux_2_34_x86_64.whl
aiopquic-0.7.1-cp313-cp313-manylinux_2_34_aarch64.whl CPython 3.13 CPython 3.13 Linux glibc 2.34+ ARM64 Details for aiopquic-0.7.1-cp313-cp313-manylinux_2_34_aarch64.whl
aiopquic-0.7.1-cp313-cp313-macosx_15_0_arm64.whl CPython 3.13 CPython 3.13 macOS 15.0+ ARM64 Details for aiopquic-0.7.1-cp313-cp313-macosx_15_0_arm64.whl
aiopquic-0.7.1-cp312-cp312-manylinux_2_34_x86_64.whl CPython 3.12 CPython 3.12 Linux glibc 2.34+ x86-64 Details for aiopquic-0.7.1-cp312-cp312-manylinux_2_34_x86_64.whl
aiopquic-0.7.1-cp312-cp312-manylinux_2_34_aarch64.whl CPython 3.12 CPython 3.12 Linux glibc 2.34+ ARM64 Details for aiopquic-0.7.1-cp312-cp312-manylinux_2_34_aarch64.whl
aiopquic-0.7.1-cp312-cp312-macosx_15_0_arm64.whl CPython 3.12 CPython 3.12 macOS 15.0+ ARM64 Details for aiopquic-0.7.1-cp312-cp312-macosx_15_0_arm64.whl

Total release size: 51.0 MB

Release files / aiopquic-0.7.1.tar.gz

Download URL aiopquic-0.7.1.tar.gz
Size 3.9 MB
Tags Source
SHA-256 checksum
How to use checksums
03b0987b12b2b9327f1965f498dace42a6e3f7bcb091ed5433e60a000cedfddc
BLAKE2b-256 checksum
How to use checksums
75c420525c7efbdc3de03dec862db5725069ca70b54b39c9a87a30bdb5605596
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.10

Release files / aiopquic-0.7.1-cp314-cp314-manylinux_2_34_x86_64.whl

Download URL aiopquic-0.7.1-cp314-cp314-manylinux_2_34_x86_64.whl
Size 5.8 MB
Tags CPython 3.14 Linux glibc 2.34+ x86-64
SHA-256 checksum
How to use checksums
c9e0a771c30f57461543250f6f7f0855b5140ab58a94adfbe8b1aaee1ff4e365
BLAKE2b-256 checksum
How to use checksums
5d4ddc6f8bb532c15f08db67a92febb6391a2e96f1a937058d0bb86315c49592
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.10

Release files / aiopquic-0.7.1-cp314-cp314-manylinux_2_34_aarch64.whl

Download URL aiopquic-0.7.1-cp314-cp314-manylinux_2_34_aarch64.whl
Size 5.6 MB
Tags CPython 3.14 Linux glibc 2.34+ ARM64
SHA-256 checksum
How to use checksums
17116f650d008e99a4528b103908ae851f13f82253eb6512dd1b9a94e81c24c4
BLAKE2b-256 checksum
How to use checksums
5e60c939176237b77d779b2c9f63c62c84f5886cc80f00175e85c415e532ce19
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.10

Release files / aiopquic-0.7.1-cp314-cp314-macosx_15_0_arm64.whl

Download URL aiopquic-0.7.1-cp314-cp314-macosx_15_0_arm64.whl
Size 4.3 MB
Tags CPython 3.14 macOS 15.0+ ARM64
SHA-256 checksum
How to use checksums
ca216357bba60790a64df88d324fa1ca4e90e840a489051671fa28081dae0237
BLAKE2b-256 checksum
How to use checksums
e599feb6ecdd88b5ce969aa4da0d49c8ad2129e6996a4753cfff08aea809d6ae
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.10

Release files / aiopquic-0.7.1-cp313-cp313-manylinux_2_34_x86_64.whl

Download URL aiopquic-0.7.1-cp313-cp313-manylinux_2_34_x86_64.whl
Size 5.8 MB
Tags CPython 3.13 Linux glibc 2.34+ x86-64
SHA-256 checksum
How to use checksums
134fe725879636f4ec433e767d1a118e11cefe6631a3eab7a02b4842734c79c8
BLAKE2b-256 checksum
How to use checksums
b94d920ccddfd268656b60ebfbb88f4cf21b090f434bec7317f601d6b1705775
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.10

Release files / aiopquic-0.7.1-cp313-cp313-manylinux_2_34_aarch64.whl

Download URL aiopquic-0.7.1-cp313-cp313-manylinux_2_34_aarch64.whl
Size 5.5 MB
Tags CPython 3.13 Linux glibc 2.34+ ARM64
SHA-256 checksum
How to use checksums
c554f2fd85795cbeea2f3e281c4a3d675e2a0ac9fff5507d1631f569b1bfaf91
BLAKE2b-256 checksum
How to use checksums
b56a5cd3c6a3ea733ed9d34e84247b1d9cdf3815bf82bb73df23ba8eafbdf1bb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.10

Release files / aiopquic-0.7.1-cp313-cp313-macosx_15_0_arm64.whl

Download URL aiopquic-0.7.1-cp313-cp313-macosx_15_0_arm64.whl
Size 4.3 MB
Tags CPython 3.13 macOS 15.0+ ARM64
SHA-256 checksum
How to use checksums
1bf65853f25426b429afad5945dff69645dead9f1c27f4aad328984b69bfcb73
BLAKE2b-256 checksum
How to use checksums
a1216d2e0584b9586916f99b9f42de2839b48d7096bf41d6ecf307b3790f0833
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.10

Release files / aiopquic-0.7.1-cp312-cp312-manylinux_2_34_x86_64.whl

Download URL aiopquic-0.7.1-cp312-cp312-manylinux_2_34_x86_64.whl
Size 5.9 MB
Tags CPython 3.12 Linux glibc 2.34+ x86-64
SHA-256 checksum
How to use checksums
66df7851e7a7f28d76d832e8cdbba9a5dad9dc965809fbc2a28b6fb1e176e7b7
BLAKE2b-256 checksum
How to use checksums
279abc65922a99f3fad98e188106984a4ac8d43f813d1e369682389d6be22b0e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.10

Release files / aiopquic-0.7.1-cp312-cp312-manylinux_2_34_aarch64.whl

Download URL aiopquic-0.7.1-cp312-cp312-manylinux_2_34_aarch64.whl
Size 5.5 MB
Tags CPython 3.12 Linux glibc 2.34+ ARM64
SHA-256 checksum
How to use checksums
3c6c228dc2e4643efd1ebd5fbdc238eff682be7c8c7b53ec5ac2323fc1bdac40
BLAKE2b-256 checksum
How to use checksums
67b42e1d3c82c9c44983c861edb7d1376e15026144d0162067efb24abfdcb22a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.10

Release files / aiopquic-0.7.1-cp312-cp312-macosx_15_0_arm64.whl

Download URL aiopquic-0.7.1-cp312-cp312-macosx_15_0_arm64.whl
Size 4.3 MB
Tags CPython 3.12 macOS 15.0+ ARM64
SHA-256 checksum
How to use checksums
2ddef2a704bd23721a223683681fdb11533ed31f974985eef3a09295b206ebcb
BLAKE2b-256 checksum
How to use checksums
09dd216dc35d50f0258891f73c48747b44bf39dfa7ef0e0eb1310597bf5d9d65
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.10

Security for aiopquic 0.7.1

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.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.7.1 This release

10 release files No provenance

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