-
Notifications
You must be signed in to change notification settings - Fork 29
Releases: justrach/turboAPI
v1.0.30
ca65dff v1.0.30 — Real WebSockets + Hot-Path Perf
TL;DR
- Real WebSocket support on the Zig HTTP core.
@app.websocket()was a stub backed by in-memory queues since day one; this release wires it to actual RFC 6455 frame I/O — handshake, frames, masking, fragmentation, ping/pong, close handshake. Closes #114. - Hot-path handler dispatch is 30–75% faster than v1.0.29 on the TestClient throughput benchmark, from 5 focused perf PRs that strip dead
kwargs.get("headers", {})defaults, cache_returns_model/model_dumpdetection, and hoist parse_qs / HTTPException imports out of fast-handler closures. - CI version-sync guard (#154) fixed — was failing on every PR since 1.0.28.
Features
Real WebSockets (#167)
from turboapi import TurboAPI from turboapi.websockets import WebSocket, WebSocketDisconnect app = TurboAPI() @app.websocket("/ws") async def handler(ws: WebSocket): await ws.accept() try: while True: msg = await ws.receive_text() await ws.send_text(f"echo: {msg}") except WebSocketDisconnect: pass
Coverage:
- HTTP Upgrade handshake with
Sec-WebSocket-Acceptvalidation - All RFC 6455 frame opcodes — text, binary, close, ping, pong, continuation
- All three payload-length encodings (7-bit / 16-bit / 64-bit)
- Server enforces client-frame masking; auto-pong on ping; close handshake on either side
- Fragmented client messages are reassembled before delivery
- Connected-mode
WebSocketwraps the live Zig connection via a PyCapsule; FFI releases the GIL around blocking socket I/O so other threads (and free-threaded interpreters) stay free duringrecvwaits - In-memory mode preserved — existing FastAPI-parity tests work unchanged
Not yet supported (follow-ups, not blocking the v1.0.30 ship):
- Path parameters on WS routes (e.g.
/ws/{room}) — exact match only permessage-deflatecompression- Subprotocol negotiation
- Routes registered after
app.run()
Performance
Five hot-path PRs targeting handler dispatch closures, header kwarg handling, and model-return detection caching. Measured against the pre-1.0.30 baseline committed at benchmarks/baseline.json (2026年04月27日). Same hardware (Apple M-series), same Python 3.14t (free-threaded), same benchmarks/bench_throughput.py harness (TestClient, in-process).
| Route | v1.0.29 baseline | v1.0.30 | Δ |
|---|---|---|---|
GET / |
86,806 r/s | 132,167 r/s | +52% |
POST /items |
48,826 r/s | 85,687 r/s | +75% |
GET /items/{id} |
~80,000 r/s* | 77,271 r/s | ~flat |
*Closest analogue in baseline.json is GET /users/123 at 80,165 r/s — not exactly the same route, take as directional.
Wire-level numbers (real socket, not TestClient) hold the ~140k req/s figure from the project README. The perf PRs in this release matter most under realistic FastAPI-style POST validation + JSON-return handler patterns.
WebSocket throughput (loopback)
5,000-message round-trip latency bench, single connection, 100 warmup. Same code, different platform — Linux numbers from an idle sandbox VM (sandbox.trilok.ai), macOS from a dev box running editors / Chrome.
| Route | Platform | msgs/s | p50 | p99 |
|---|---|---|---|---|
/ws-echo (Zig only, no Python in the loop) |
Linux x86_64 | 23,529 | 38 μs | 61 μs |
/ws-echo |
macOS arm64 | 15,666 | 57 μs | 129 μs |
/ws-py (Python handler) |
Linux x86_64 | 18,519 | 53 μs | 63 μs |
/ws-py |
macOS arm64 | 15,295 | 60 μs | 124 μs |
Sub-millisecond p99 on both platforms; Python overhead is ~15 μs per round-trip on Linux (well-defined) and ~3 μs on macOS (absorbed into ambient scheduling jitter). For an LLM token-stream use case (~50 tok/s producer), the WS path provides &g×ばつ headroom on either platform.
Internals & fixes
- #154 fix: stale version-sync guard fails CI on every PR since 1.0.28 — the equality check rejected anything that wasn't an exact match; changed to
>=so monotonic version bumps pass. - #148 perf: hoist
parse_qs/HTTPExceptionimports out of fast-handler closures (saves aLOAD_GLOBALper request). - #152 perf: cache joined CORS header strings +
max_agestr()in__init__. - #155, #159 perf: drop wasted
{}defaults inkwargs.get("headers", {})calls —or {}is faster than a default factory. - #156 perf: cache
model_dumpdetection at handler-creation time in fast handlers (skip the per-requesthasattrcheck). - #161 perf: extend
_returns_modelcaching topos_handler,async_pos_handler,fast_model_handler(the previous PR only covered fast_sync). - #167 feat: real WebSocket support (see above).
- #168 release: merge v1.0.30 into main.
Issues closed
- #114 — zig runtime: real WebSocket support on current turboapi-core architecture
Cross-platform verification
WebSocket implementation was verified end-to-end on both macOS arm64 (local dev) and Linux x86_64 (via the turbobox sandbox at sandbox.trilok.ai). The Linux run surfaced a @memcpy length-mismatch bug in a test fixture that macOS Zig 0.16 had elided — fixed in commit d7f14b8 before tagging.
| Check | macOS arm64 | Linux x86_64 |
|---|---|---|
zig build test |
26/26 pass | 26/26 pass |
pytest tests/test_websocket_e2e.py |
10/10 pass, ~90s | 10/10 pass, ~90s |
pytest tests/test_fastapi_parity.py::TestWebSocket |
4/4 pass (no longer deselected) | — |
| Full pytest regression on release tip | 404 pass, 0 regress | — |
Upgrade
pip install --upgrade turboapi
Backwards compatible. No code changes required — @app.websocket(...) handlers that previously only worked in test mode now serve real traffic. In-memory WebSocket() instantiation still works for unit tests.
Full Changelog: v1.0.29...v1.0.30
Assets 5
v1.0.29
TurboAPI v1.0.29
This is the real Zig 0.16.0 + Python 3.14t release. It supersedes v1.0.28.
v1.0.28 contained the runtime/performance work, but release smoke testing found packaging problems after publish:
- free-threaded wheels were tagged as
cp314t-cp314t, which pip/uv do not select forpython3.14t - the macOS extension linked against a GitHub Actions-only
PythonT.frameworkpath
v1.0.29 fixes those packaging issues and was smoke-tested from a clean PyPI install with the native Zig backend.
Install
uv python install 3.14t python3.14t -m pip install turboapi==1.0.29
TurboAPI now requires a free-threaded Python build. A regular GIL-enabled Python 3.14 install will refuse to import TurboAPI and tell you to use python3.14t.
Published artifacts:
turboapi-1.0.29-cp314-cp314t-macosx_10_15_universal2.whlturboapi-1.0.29-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whlturboapi-1.0.29.tar.gz
Major Runtime Changes
- Migrated the Zig HTTP runtime to Zig
0.16.0. - Moved the server onto
std.Io.netlistener and stream APIs. - Centralized the shared
std.Io.Threadedlifecycle inzig/src/runtime.zig. - Kept TurboAPI's explicit cross-core connection worker pool so each worker owns a reusable Python thread state.
- Added
TURBO_THREAD_POOL_SIZEsupport for controlling worker count at runtime. - Updated runtime synchronization to Zig 0.16-compatible
std.Io.Mutex/std.Io.Conditionpaths where applicable. - Updated all CI and release workflows to use Zig
0.16.0. - Fixed Zig 0.16 compatibility issues across networking, time APIs, pthread mutexes, Smith fuzz callbacks, libc linking, multipart ownership, and removed std APIs.
Performance Work
- Added eager no-await async dispatch for simple async routes:
simple_async_eager. - Added eager no-await async dispatch for async body routes:
body_async_eager. - Added safety checks so eager async mode is skipped for handlers that actually await, yield, or use loop-sensitive asyncio APIs.
- Added reusable per-worker asyncio event-loop helpers for true-await async handlers.
- Added async vectorcall/noargs fast paths.
- Added cached async response handling.
- Added entry-local atomic cached body pointers for no-arg handlers.
- Added cached JSON response writing that avoids re-entering Python on cache hits.
- Added precomputed scalar JSON body parsing for fast sync/async body wrappers.
- Avoided redundant defensive body copies when Zig already passed immutable Python
bytes. - Reused Zig helpers for
path_paramskwargs and skipped empty path-param dict allocation. - Switched hot route/parameter parsing paths to
std.StaticStringMap. - Added SIMD-assisted header-end scanning and router child dispatch where available.
- Reworked URL percent-decoding to bulk-copy clean spans and branch only on
%/+. - Reworked telemetry JSON escaping to bulk-copy clean spans.
- Replaced DHI field type parsing chains with
StaticStringMap. - Used
PyUnicode_AsUTF8AndSizein SQL value encoding to avoid extra length scans. - Reworked SQL literal escaping to bulk-copy normal spans and only escape when needed.
HTTP, Forms, Multipart, and Middleware
- Added Zig-native multipart/form-data parsing support.
- Fixed multipart, form, URL-encoded, and raw body handling across native, ASGI fallback, and TestClient paths.
- Added
UploadFile/File()/Form()TestClient coverage. - Added raw
body: bytesinjection. - Added populated
Requestinjection for handlers that ask forrequest: Request. - Converted
turboapi.middlewareinto a FastAPI-compatible package with submodules:turboapi.middleware.gzipturboapi.middleware.corsturboapi.middleware.trustedhostturboapi.middleware.httpsredirectturboapi.middleware.sessions
- Fixed middleware route handling so async routes under middleware stay on the correct enhanced path.
- Fixed middleware compatibility for HTTPS redirect, custom logging-style middleware hooks, GZip passthrough, and header tunneling.
- Made
app.mount("/static", StaticFiles(...))register real GET routes so the Zig runtime can serve mounted files. - Ensured middleware can apply to static-file responses.
Bug Fixes
- Fixed implicit HTTP header binding for plain string parameters.
- Fixed query parameter coercion for annotated types like
int,float,bool, lists, and enums. - Fixed path parameter detection so parameter names are matched as
{param}instead of substring matches. - Fixed response normalization to avoid mutating caller dictionaries.
- Fixed response tuple ABI mismatches in the Zig FFI path.
- Fixed
set_cookie()propagation and multi-cookie response handling. - Fixed
StreamingResponse/FileResponsecookie initialization. - Fixed middleware exception status propagation.
- Fixed middleware
on_errorresponse content handling. - Fixed OpenAPI handling for
Optional[X]/Union[X, None]. - Fixed TestClient async dependency resolution.
- Fixed ASGI lifespan handling to use
__aenter__/__aexit__. - Fixed Python 3.14 request failures caused by inline
import inspectshadowing. - Fixed static file path traversal by replacing string-prefix checks with path ancestry checks.
- Fixed CORS regex origin matching to use full matches.
- Fixed CSRF cookie handling so existing cookies are not overwritten.
- Fixed telemetry JSON hex escaping for control characters.
- Fixed native turbopg fallback behavior and multiple DB route/runtime issues.
- Fixed DB query parameter parsing, bool handling, large raw-cell lengths, and insert allocation leaks.
- Fixed DHI schema arena cleanup.
- Fixed turboapi-core router partial mutation on allocation failure.
Structured Logging and Telemetry
- Added
zig/src/telemetry.zigwith event-sourced log/span/counter/histogram/gauge events. - Added text and JSON-lines stderr exporters.
- Added
TURBO_LOG_LEVELandTURBO_LOG_FORMAT. - Added
zig/src/logger.zigwrappers for leveled Zig logs. - Replaced direct
std.debug.printcalls in the server and DB layer with structured logger calls. - Added Python logging helpers and JSON formatting in
python/turboapi/logger.py. - Added telemetry/logging regression coverage.
Protocol and Deployment Docs
- Added
docs/HTTP3_QUIC.md. - Clarified that native QUIC/HTTP/3 is not implemented in TurboAPI yet.
- Documented the supported production path for QUIC/HTTP/3: put TurboAPI behind Caddy, nginx, Cloudflare,
cloudflared, or another edge/reverse proxy. - Updated HTTP/2 docs to avoid claiming unsupported native protocol behavior.
- Updated TLS docs to recommend edge termination / reverse proxy TLS for production.
- Updated WebSocket docs to match the current runtime support model.
- Updated performance tuning docs for worker-pool sizing and
TURBO_THREAD_POOL_SIZE. - Added Zig 0.16 migration docs and a project-agnostic Zig 0.15.2 to 0.16 migration reference.
- Added a zigup multi-version workflow guide.
- Added a frontend release notes page for the 1.0.28/1.0.29 work.
Benchmarking and CI
- Added PR performance regression gating.
- Added benchmark trend/history tooling.
- Added
benchmarks/thresholds.json. - Added
scripts/bench-http.sh. - Added worker-count tracking through
WORKERS=N ./scripts/bench-http.sh. - Updated benchmark result JSON/history/PR output to record worker count, duration, wrk threads, and connections.
- Raised adversarial benchmark thresholds for GitHub-hosted runner stability.
- Fixed the release workflow so it can push tags and dispatch the publish workflow.
- Fixed the build/publish workflow so manual dispatch creates the GitHub Release.
- Added wheel smoke checks in CI:
- install the produced wheel into a fresh venv
- import
turboapi.turbonet - assert
TurboServerandResponseViewexist - on macOS, assert the wheel does not link to
Python.frameworkorPythonT.framework
- Limited release wheels to the actually supported Python
3.14truntime.
Packaging Fixes in v1.0.29
- Fixed free-threaded wheel tags from
cp314t-cp314ttocp314-cp314t. - Stopped linking the Zig extension against libpython for release wheels, so macOS wheels no longer embed a non-portable framework path.
- Verified the macOS wheel links only against
@rpath/libturbonet.dyliband/usr/lib/libSystem.B.dylib. - Verified a clean PyPI install of
turboapi==1.0.29imports the native backend. - Verified a clean PyPI install starts the native Zig HTTP server.
Performance Results
Local no-await async body route measurements with cache disabled:
| Route | Before | After |
|---|---|---|
POST /body0 |
~70.9k req/s | ~110.8k req/s |
POST /body_params |
~67.2k req/s | ~103.6k req/s |
POST /body_sleep0 |
~57k req/s | ~57k req/s |
body_sleep0 is unchanged because it truly awaits and stays on the event-loop path.
Latest mixed-app local HTTP benchmark with TURBO_DISABLE_CACHE=1, TURBO_THREAD_POOL_SIZE=24, and wrk -t8 -c128 -d5s --latency:
| Route | Median |
|---|---|
GET /sync |
91.7k req/s |
GET /async no-await |
89.0k req/s |
POST /body no-await async body |
83.1k req/s |
The same local POST /body route was 49.2k req/s before the precomputed parser pass, so this was about a 69% improvement under the same load.
Validation
Local validation before merge/release:
make checkpassed- full test suite passed:
388 passed, 20 warnings - sample app smoke passed for:
- sync GET
- no-await async GET
- async POST body
- path/query route
- true-await async route
- benchmark wrapper smoke passed with
WORKERS=4 BENCH_DURATION=1 BENCH_THREADS=2 BENCH_CONNECTIONS=16 ./scripts/bench-http.sh /tmp/bench_results.jsonrecorded"workers": 4
Release validation for v1.0.29:
- Release workflow passed
- Build & Publish workflow passed
- Linux
3.14twheel built and imported the native backend in CI - macOS
3.14twheel built, imported the native backend in CI, and passed the no-Python-fr...
Assets 5
v1.0.27
f7ec31e Assets 9
v1.0.24
d0ee8b9 v1.0.24
Fixes
- restored Zig-runtime gzip middleware body passthrough so compressed responses keep the correct
Content-Encoding: gzipheader and the actual compressed body - normalized middleware-visible request headers to lowercase before Python-side processing
- preserved raw
bytesresponse bodies in the Zig bridge instead of JSON-serializing compressed middleware output tonull
Packaging
- synced version metadata to
1.0.24acrosspyproject.toml,python/setup.py, andpython/turboapi/__init__.py - added README and changelog entries for the gzip fix
References
What's Changed
- fix: resolve yxlyx issues #97, #98; prep #96 by @justrach in #110
- fix: restore gzip middleware body passthrough on Zig runtime by @justrach in #111
- release: bump TurboAPI to v1.0.24 by @justrach in #112
Full Changelog: v1.0.23...v1.0.24
Assets 9
v1.0.23
What's Changed
Full Changelog: v1.0.22...v1.0.23
Assets 9
v1.0.22
Assets 9
v1.0.21
b3adabb What's Changed
- Fix Zig response cache crash under load by @justrach in #95
- fix: close verified compat gaps in TestClient, lifespan, docs, router deps, and static mounts by @justrach in #105
Full Changelog: v1.0.20...v1.0.21
Assets 2
v1.0.20
What's Changed
- feat: TurboBoto — Zig-accelerated boto3 drop-in replacement by @justrach in #62
- Tighten benchmark methodology and publish corrected results by @justrach in #77
Full Changelog: v1.0.17...v1.0.20
Assets 9
v1.0.17
43a7191 What's Changed
- bench: asyncpg vs TurboAPI+pg.zig Docker benchmarks by @justrach in #56
- fix: add TURBO_DISABLE_CACHE env var by @justrach in #58
Full Changelog: v1.0.16...v1.0.17
Assets 16
v1.0.16
What's Changed
- feat: Claude Code skills for TurboAPI development by @justrach in #53
- feat: Zig-native Postgres via pg.zig — zero-Python CRUD by @justrach in #50
Full Changelog: v1.0.15...v1.0.16