Skip to content

Releases: Kludex/zuvloop

v0.0.12 - Free-threaded event loop scaling

Choose a tag to compare

@Kludex Kludex released this 28 Aug 12:21
827db37

🧵 Free-threaded event loop scaling

zuvloop 0.0.12 replaces process-wide synchronization with per-loop and per-object locking, allowing independent event loops to run concurrently on free-threaded CPython.

pip install zuvloop==0.0.12
  • Independent loops no longer serialize behind one global lock, improving four-loop throughput scaling from 1.01x to 2.94x (#131).
  • Cross-thread callbacks use a protected per-loop inbox, while callback execution and task context switches remain outside the loop lock (#131).
  • Performance coverage now includes four concurrent event loops, recording the free-threaded scaling baseline alongside asyncio and uvloop (#130, #131).

Full changelog: v0.0.11...v0.0.12

v0.0.11 - Faster task and ready-queue scheduling

Choose a tag to compare

@Kludex Kludex released this 28 Aug 08:30
74cbc72

⚡ Faster task and ready-queue scheduling

zuvloop 0.0.11 makes task creation and ready callback chains substantially faster while preserving timer and I/O fairness.

pip install zuvloop==0.0.11
  • Task creation and ready-only callback chains require less overhead, increasing task throughput from about 648k to 783k tasks/s and sleep(0) throughput from about 70k to 2.07M iterations/s (#126).
  • Ready callback chains stay fast while I/O handles remain open, improving the idle-connection workload from 53.2k to 361.2k iterations/s while retaining bounded I/O latency (#128).
  • Performance coverage now includes task creation and ready chains with idle connections, and the refreshed comparison shows zuvloop ahead of uvloop on 11 of 12 README benchmarks (#125, #127, #129).

Full changelog: v0.0.10...v0.0.11

v0.0.10 - Python 3.15 support

Choose a tag to compare

@Kludex Kludex released this 27 Aug 12:06
c3f6fc8

🐍 Python 3.15 support

zuvloop 0.0.10 adds standard and free-threaded CPython 3.15 wheels on every supported platform.

pip install zuvloop==0.0.10
  • CPython 3.15 wheels now ship for Linux glibc and musl, macOS, and Windows on x86-64 and ARM64 (#124).
  • Free-threaded CPython 3.15 handles the interpreter's object-alignment and static-module initialization changes, with dedicated compatibility and platform CI coverage (#124).

Full changelog: v0.0.9...v0.0.10

v0.0.9 - Free-threaded CPython and runtime hardening

Choose a tag to compare

@Kludex Kludex released this 27 Aug 06:06
7075423

🧵 Free-threaded CPython and runtime hardening

zuvloop 0.0.9 adds supported free-threaded CPython wheels and hardens lifecycle, networking, and cross-thread scheduling paths.

pip install zuvloop==0.0.9
  • Free-threaded CPython 3.14 wheels now ship for Linux, macOS, and Windows, with explicit critical sections, thread-state handling, atomic reference counting, and dedicated compatibility coverage (#123).
  • Loop shutdown races no longer admit new work while resolving DNS, spawning processes, adopting datagrams, or creating poll watchers (#101, #103, #104, #105).
  • Networking setup and error paths settle predictably across STARTTLS, getnameinfo, buffered reads, stream and datagram closure, process exit, and timer delivery (#102, #106, #110, #111, #112, #113, #114, #115).
  • UDP and resolver behavior aligns more closely with the standard library by delivering connection setup before reads, preserving adopted-socket reuse options, normalizing Unicode hostnames with IDNA, and rejecting embedded NULs (#107, #108, #109, #116, #117).
  • Cross-thread scheduling does less work by coalescing wakeups and allocating empty callback contexts only when needed (#119, #120).

Full changelog: v0.0.8...v0.0.9

v0.0.8 - Call graphs for completed slow tasks

Choose a tag to compare

@Kludex Kludex released this 18 Aug 19:23
be67785

🔭 Call graphs survive task completion

zuvloop 0.0.8 keeps slow-callback telemetry useful when the blocked callback is the final step of a coroutine.

pip install zuvloop==0.0.8
  • Completed slow tasks retain their identity in the asyncio.call_graph span attribute instead of falling into the no-call-graph bucket (#98).
  • The fallback matches the shape of asyncio.format_call_graph() and includes the task name, completion state, coroutine qualname, and source location.
  • Task results and exception messages stay out of telemetry. The fallback renders controlled metadata instead of the task repr, which may contain application data.
  • Active tasks keep the full standard-library call graph. The fallback only applies after Python has released the completed coroutine's frames.

Full changelog: v0.0.7...v0.0.8

v0.0.7 - Windows support and production quality gates

Choose a tag to compare

@Kludex Kludex released this 15 Aug 15:24
85f4248

🪟 Windows support and stronger release confidence

zuvloop 0.0.7 adds native Windows wheels and expands the project's production quality gates across platforms and downstream integrations.

pip install zuvloop==0.0.7
  • Native Windows wheels are built and tested for AMD64 and ARM64, alongside the existing Linux and macOS wheels.
  • Release-wheel validation now exercises the actual cibuildwheel path on both Windows architectures, with scoped build constraints and bounded tests.
  • Pull requests have an aggregate quality gate backed by re-actors/alls-green, covering the platform matrix, native hardening, documentation, and compatibility suites.
  • Downstream compatibility coverage includes aiohttp, AnyIO, aioquic, HTTPX2, Tornado, Uvicorn, websockets, and supported CPython versions.
  • Dependency maintenance is deterministic: aiohttp's test requirements are captured locally for Dependabot-compatible lock updates, including cryptography 50.0.0.
  • Documentation is aligned with the shipped API and instrumentation dependencies.

Full changelog: v0.0.6...v0.0.7

v0.0.6 - Production readiness and verifiable releases

Choose a tag to compare

@Kludex Kludex released this 14 Aug 15:08
7ce0959

🛡️ Production behavior you can trust

zuvloop 0.0.6 closes the gap between a fast event loop and one that can be operated, diagnosed, and released with confidence. Exception handling is safer, native behavior tracks CPython more closely, and platform-specific paths now run through much stronger compatibility gates.

pip install zuvloop==0.0.6
  • Unhandled exceptions always reach the asyncio logger, while OpenTelemetry remains additive and fail-open (#91). Exporter failures, hostile reprs, and broken custom handlers cannot suppress the original diagnostic or destabilize the loop; telemetry strings and recorded exception data are bounded to 4 KiB.
  • Exception handlers now run in the owning task or handle context, matching Python 3.14's contextvars semantics. SystemExit and KeyboardInterrupt continue to propagate through every defensive reporting layer.
  • Scheduling handles align more precisely with CPython: timers are real asyncio.TimerHandle instances and preserve the original integer or float deadline, native handles expose their context, and thread-safe handles retain cross-thread cancellation behavior.
  • Networking and process edge cases are stricter and more portable, including address validation, TLS transport checks, named-FIFO lifetime, sendfile file-like objects, Darwin IPv6 scope normalization, musl resolver parity, and subprocess exit races.
  • Unsupported free-threaded CPython builds fail at compile time with an actionable message rather than producing a wheel whose GIL assumptions are unsafe.

📦 Verifiable builds and releases

  • The native build toolchain and isolated build requirements are exactly pinned and hash-locked; GitHub Actions are SHA-pinned and run with least-privilege permissions.
  • cibuildwheel invokes uv directly so hash verification is scoped to the project build without affecting platform bootstrap tools (#93).
  • Release preflight verifies that tags belong to main, exercises native safety checks and CPython conformance, and publishes the exact wheel and sdist artifacts produced by the gated build.
  • Published distributions receive GitHub artifact attestations plus a CycloneDX SBOM that includes the compiled libuv dependency before trusted PyPI publishing.
  • Source distributions bring their pinned Zig toolchain into the isolated build environment and are tested on a host with no system Zig available.
  • The aiohttp compatibility environment is locked with hashes, while immutable upstream aiohttp and uvicorn suites prevent dependency drift from changing the test inputs unexpectedly.

🧪 Broader release confidence

  • Runtime coverage now includes glibc and musl, checked native builds, sanitizer runs, scheduled soak tests, CPython 3.14.0/current 3.14/3.15 prereleases, and installed-wheel conformance.
  • Real aiohttp and uvicorn workloads, property tests, strict native stubs, and 100% branch coverage guard the public behavior users depend on.
  • Compatibility, installation, instrumentation privacy, supported platforms, and the release architecture are now documented explicitly.
  • README benchmarks were rerun on CPython 3.14.3 and libuv 1.51.0 (#90); zuvloop leads uvloop in 10 of the 11 published rows, with the aiohttp client result called out honestly.

Full changelog: v0.0.5...v0.0.6

v0.0.5 - Network resilience and protocol hardening

Choose a tag to compare

@Kludex Kludex released this 13 Aug 12:03
897813c

🌊 Network resilience under pressure

zuvloop 0.0.5 hardens the paths that only become interesting when a server is busy, shutting down, upgrading to TLS, or running out of operating-system resources. It also turns musl and real aiohttp/uvicorn traffic into release gates.

pip install zuvloop==0.0.5
  • Accept loops now back off on EMFILE, ENFILE, ENOBUFS, and ENOMEM instead of spinning and consuming a CPU core (#78, #88, #89). Recovery follows asyncio's retry model, remains safe after server closure, and cannot be bypassed by calling start_serving() or serve_forever() again.
  • Python socket views are disarmed before libuv closes the native descriptor (#75). This closes a double-close race where another thread could reuse the descriptor number before delayed cleanup ran.
  • Failed stream and datagram construction closes adopted sockets exactly once (#80), including protocol-factory failures and partial native adoption.
  • Cancelling serve_forever() now closes connected clients and waits for shutdown (#87), so cancellation no longer leaves accepted connections alive.
  • UNIX server cleanup always wakes wait_closed() callers (#84), even when removing the socket path fails.
  • Datagram protocols stop receiving error and flow-control callbacks after close or abort (#77).

🔐 Protocol and native-boundary correctness

  • Server-side start_tls() carries already-buffered plaintext into TLS (#85). Proxy headers and a coalesced ClientHello can no longer strand handshake bytes in the replaced stream protocol.
  • Linux abstract UNIX datagram addresses round-trip with their exact identity (#86). Abstract names are exposed as bytes, unnamed senders as None, and the required libuv changes are preserved as a patch that is reapplied during vendor updates.
  • Socket addresses are validated before entering native structures (#81). Embedded NULs and out-of-range IPv6 flowinfo or scope_id values now fail in Python instead of being truncated or wrapped.
  • Invalid write-buffer limits fail before unsigned conversion (#79); valid low-only configurations still derive a safe high watermark.
  • Infinite and extremely large timer delays are clamped safely (#76) across scheduling, wait_for(), and metrics instrumentation.
  • PID reuse cannot lose a newer subprocess registration (#82) when a stale reaper callback finishes late.

🧪 Release confidence and portability

  • aiohttp and uvicorn now run end to end in the normal test suite (#49), covering real protocol, flow-control, large-response, concurrency, and shutdown paths on zuvloop.EventLoop.
  • The complete suite now runs under musl, and framework traffic runs under ReleaseSafe (#50). Published Alpine-compatible wheels are tested at runtime instead of only being cross-compiled.
  • Timer fairness under sustained call_soon_threadsafe() traffic now has a recorded CodSpeed baseline (#83) for the follow-up scheduler work.
  • Release workflows use the latest pinned checkout and artifact actions (#73).

Full changelog: v0.0.4...v0.0.5

v0.0.4 - Security and lifecycle hardening

Choose a tag to compare

@Kludex Kludex released this 12 Aug 10:36
49a8b5b

🔒 Security and ownership hardening

zuvloop 0.0.4 focuses on making native ownership boundaries match asyncio and CPython even under reentrancy, finalization, and subprocess edge cases.

pip install zuvloop==0.0.4
  • Mutable write buffers are snapshotted before write() returns (#61). Exact bytes objects retain the zero-copy path; bytearray, custom buffer exporters, and other potentially mutable inputs can no longer change queued network bytes after submission.
  • Subprocess strings reject embedded NULs (#59) rather than being silently truncated by libuv's C-string boundary.
  • Linux subprocesses preserve close_fds semantics (#56). Unrelated inheritable descriptors stay out of children, including when pass_fds is used, and one bounded process-wide reaper bridges stdlib child exits back to the event loop.
  • Signal handlers and the wakeup fd have explicit per-loop ownership (#62). Finalizing an older loop can no longer reset a newer loop's process-global signal state; rollback is safe across deferred-finalizer races.
  • Reentrant handle cancellation keeps vectorcall references alive (#64) without adding allocation or refcount work to every callback.
  • DNS GC traversal stops when the native resolver reaper owns the requests (#60), removing a native use-after-free race during loop shutdown.

🧭 Asyncio compatibility and lifecycle fixes

  • Reentrant writelines() cannot queue bytes after write_eof() (#67).
  • A failed metrics sampler start now restores running-loop and async-generator state (#65).
  • Connected IPv6 datagram validation includes flowinfo (#66).
  • A failed uv_udp_open reports the original error without reopening a closing handle (#68).
  • Slow-callback telemetry skips call-graph formatting for completed tasks (#69).
  • Self-pipe EOF unregisters the reader instead of leaving the loop in a busy spin (#70).

🏗️ Release and supply-chain controls

  • Release builds pin hatch-vcs (#55), publish only from v* tags (#57), and do not expose OIDC credentials to pull-request benchmark code (#58).
  • Vendored libuv archives are checksum-verified (#63); a weekly signature-verifying workflow now tests new releases and opens update PRs (#71).
  • CI now enforces zig fmt and a checksum-pinned ZLint safety ruleset alongside Ruff and strict mypy (#72).

v0.0.3 - sendfile, and telemetry that arms late

Choose a tag to compare

@Kludex Kludex released this 11 Aug 12:53
46773d9

📁 sendfile

zuvloop 0.0.3 implements the two loop methods that previously raised NotImplementedError.

pip install zuvloop==0.0.3
  • sendfile() and sock_sendfile() use the sendfile(2) system call (#53) - the kernel moves the bytes, nothing is copied through Python. For sendfile(transport, ...) the loop first lets the transport's buffered writes drain and pauses reading, so the file cannot reorder around data written before it; readiness is polled through a duplicate of the descriptor, since libuv allows one handle per descriptor.
  • Targets the syscall cannot serve fall back to a read-and-write loop - a BytesIO, a TLS transport, a pipe - or raise under fallback=False, matching asyncio. On macOS, sendfile(2) does not serve AF_UNIX sockets, so unix connections take the fallback there transparently (native on Linux).

🔭 Telemetry survives late configuration

  • OpenTelemetry providers installed after the loop starts are now noticed (#54). Slow-callback monitoring and the loop gauges were armed once, at run_forever() entry - an application calling logfire.configure() from inside main() silently lost both for the life of the process. The loop's unreferenced native sampler now doubles as the re-check: monitoring arms within one metrics_interval (10s by default) of a provider appearing, the answer is latched so the probe stops once it says yes, and uninstrumented programs keep the native fast path with no per-callback clock reads.
  • Slow-callback spans are warnings now, not errors (#54). OpenTelemetry span status has no warning level, so the status stays unset and the severity travels as a logfire.level_num attribute, which backends that do not know it ignore.

🏗️ Release plumbing

  • The version now comes from the git tag (#52) via hatch-vcs; publishing this release is what stamps 0.0.3 onto the wheels.