0.8.1
Patch release for HTTP/3 reload/drain parity, the subinterpreter FD leak fix, shared request-pipeline consolidation, and the drain benchmark profile
Highlights
- HTTP/3 reload & drain parity —
graceful_reloadnow rotates HTTP/3 (QUIC) workers alongside TCP workers so the H3 generation picks up reimported app code, and HTTP/3 graceful shutdown drains in-flight stream tasks withinshutdown_timeoutinstead of hard-cancelling them. - Subinterpreter FD leak fixed — subinterpreter workers no longer leak the dup'd listener file descriptor on abnormal stop, and the supervisor reclaims FDs on crash-respawn and after force-stopping a non-draining old generation during reload, so repeated crashes/reloads no longer exhaust the FD budget.
- Shared request pipeline — the H2/H3 per-request prelude and the sync worker's response paths now route through the shared
_request_pipelineandnegotiate_compressor_from_metahelpers, restoring RFC 9842 dictionary-compressed zstd (dcz) negotiation on HTTP/2 and HTTP/3. No user-visible behavior change. - Drain/reload regression gate — a new reload/drain-under-load benchmark profile drives steady keep-alive load through the real
pounce serveCLI, fires SIGHUP then SIGTERM, and emits an artifact-schema JSON that feeds reload/drain health into the regression gate.
Added
- Reload/drain-under-load benchmark profile (
benchmarks/drain_profile.py): drives steady keep-alive/fast+ in-flight/slow+/streamload through the realpounce serveCLI, fires SIGHUP then SIGTERM, and emits an artifact-schema JSON whose per-sampledrainblock records in-flight completion, the 503/disconnect rate, drain duration, and orphan-worker absence. (#141)
Changed
- Benchmark hygiene: rewrote the banned PEP 758 parenless multi-
exceptform (except A, B:) inbenchmarks/into the portable parenthesized tupleexcept (A, B): # fmt: skip, and addedbenchmarks/to the code-quality scan so the guard now covers it. No runtime behavior change. (#155) - Consolidated the H2/H3 per-request prelude (trusted-peer detection, content-encoding negotiation, and access-log filtering) onto the shared
_request_pipelinehelpers, which also restores the already-advertised RFC 9842 dictionary-compressed zstd (dcz) negotiation on HTTP/2 and HTTP/3. No user-visible behavior change. (#160) - Routed the sync worker's SyncApp and ASGI response paths through the shared
negotiate_compressor_from_metaentry point and a local_finalize_response_headershelper, removing the duplicated content-encoding/content-length rewrite blocks. No user-visible behavior change. (#162) - Extracted the duplicated
FIRST_COMPLETEDrace-and-drain logic shared by the disconnect monitor, body reader, and the HTTP/2 and HTTP/1.1 WebSocket bridges into a singlepounce._concurrencyhelper that always cancels and awaits the losing task so no task is leaked; behavior is unchanged. (#163)
Fixed
- Subinterpreter workers no longer leak the dup'd listener file descriptor when they stop abnormally: the bootstrap now closes the reconstructed socket in a
finallyblock (covering a startup-hook/asyncio.start_server/asyncio.runfailure before the normalserver.close()), and the supervisor records each worker's dup'd FD and reclaims it on crash-respawn and after force-stopping a non-draining old generation during graceful reload, so repeated crashes/reloads no longer exhaust the FD budget. (#106) graceful_reloadnow rotates HTTP/3 (QUIC) workers alongside TCP workers so the H3 generation also picks up the reimported app — fixing a split-brain where old H3 workers kept serving stale code; each old H3 worker is retired via a per-worker reload event (not the shared shutdown event) and its in-flight streams drain withinshutdown_timeoutbefore its UDP transport closes, so HTTP/3 reload is brief-downtime rather than zero-downtime. (#111)- HTTP/3 graceful shutdown now drains in-flight stream tasks within
shutdown_timeoutinstead of hard-cancelling them: existing requests finish (only stragglers past the deadline are aborted), new connections/streams are refused while draining, andCONNECTION_CLOSEis sent after the bounded wait — mirroring the TCP worker's stop-new / bounded-wait / abort-stragglers sequence. (#112) - Packaged a
chirpinstall extra (pip install bengal-pounce[chirp]) and addedbengal-chirp>=0.7.1to the dev dependency-group so the chirp example/compat tests now actually run in CI instead of being skipped. (#150)
Install
uv add "bengal-pounce==0.8.1"