0.9.0
Minor release for stable subinterpreter web workers, bounded stream and timeout lifecycle, protocol hardening, and deployment-backed evidence
Highlights
- Stable isolated ASGI web workers — explicit
worker_mode="subinterpreter"now has replacement-readiness gating, old-generation acceptor retirement, reload-under-load proof, exact lifespan-state recovery, and listener ownership fixes. Stability is scoped to async ASGI web workers with importable apps and compatible dependencies; job and hybrid roles remain future work. - Bounded lifecycle for real traffic — request-body progress, blocked response writes, accepted WebSockets, active HTTP/2 streams, and long-lived SSE responses now follow state-specific timeout rules. Apps can use the bounded, generation-scoped
pounce.worker.draininghook to close streams during reload or shutdown. - Protocol and edge controls — operators can disable HTTP/2 ALPN at a Pounce-owned TLS origin; HTTP/2 active-stream and flow-control bugs are fixed; WebSocket compression requires a real client offer; and HTTP/3 moves from optional-limited to optional after real QUIC lifecycle and benchmark proof.
- Deployment identity and proof —
/_pounce/infocan report the Pounce/Python build fingerprint, GIL state, resolved worker model, and an operator-supplied build ID. The official Railway recipe and repository canary exercise GIL-off Python 3.14t, readiness, slow traffic, streaming, and graceful replacement. - Repeatable performance evidence — fixed-rate p50/p99/p999 workflows now cover Python 3.14 and 3.14t with Uvicorn, Hypercorn, and Granian comparisons plus per-process CPU/RSS telemetry. The artifacts are workload snapshots, not universal capacity claims.
Upgrade notes
- Explicit subinterpreter mode, including
workers=1, uses the supervised isolated-worker path and requires an importable application path (app_pathfor embedded callers). It remains async-only; lifespan state must be JSON-safe, and native dependencies must support subinterpreters. request_timeoutnow bounds each wait for request-body progress, whilewrite_timeoutbounds blocked HTTP/1.1, HTTP/2, and WebSocket response delivery. Expiry reportsPOUNCE_TIMEOUT_REQUEST_BODYorPOUNCE_TIMEOUT_WRITEbefore the peer is closed.keep_alive_timeoutapplies between requests, not to accepted WebSockets, active HTTP/2 streams, or active streaming/SSE responses.- WebSocket
permessage-deflateis emitted only when the client offered it. Configuration permission alone no longer negotiates compression. - Treat Pounce's configured built-in endpoint as readiness (conventionally
/readyz): it returns 503 while draining, andHEADmatchesGETstatus and headers without a body. A separate/healthzliveness endpoint remains application-owned. - HTTP/2, WebSocket, and HTTP/3 remain optional extras. WebSocket-over-HTTP/2 remains limited, and WebSocket-over-HTTP/3 remains unsupported.
Added
- Long-lived stream lifecycle events, active-stream metrics, the
pounce.worker.draininghook, and a reusable two-instance streaming test proxy. (#238) - State-specific request-body and response-write timeout enforcement with actionable
POUNCE_*diagnostics. (#242) http2_enabledand--no-http2for forcing HTTP/1.1 ALPN at Pounce-owned TLS origins. (#243)- Fail-loud worker startup policy, readiness gating, resolved-worker reporting, and runtime build identity. (#245, #246, #252)
- Official Railway release recipe, deploy/redeploy smoke proof, and a public main-branch canary. (#248, #293)
- Reproducible HTTP/3 and sustained fixed-rate benchmark evidence with per-process telemetry and release-asset automation. (#228, #240, #253)
Changed
- Explicit subinterpreter ASGI web workers are now stable within the documented import, async, state, and dependency-compatibility limits. (#239)
- OpenTelemetry request-span names, attributes, and status mapping are proven through a real OTLP/HTTP collector boundary. (#255)
- HTTP
QUERYand its request body are forwarded across HTTP/1.1, HTTP/2, and HTTP/3. Pounce does not define application semantics, caching, authorization, replay policy, orAccept-Query. (#257)
Fixed
- Active HTTP/2 responses are no longer reaped by connection-idle timeout, and blocked senders resume directly when flow-control windows open. (#231, #232)
- Subinterpreter reload and crash recovery preserve accepted connections and descriptor ownership across worker replacement. (#106, #239)
- Sync workers emit startup and shutdown hooks on their own runner loop, matching the lifecycle ownership used by other worker modes. (#244)
- Built-in endpoints implement correct
HEADbehavior, and WebSocket compression is never negotiated without a client offer. (#250, #256) - Async shutdown-503 connections now close their transports after client resets or write timeouts, preventing free-threaded loop-teardown tracebacks during SIGTERM drain. (#297)
- HTTP/3 reload gives each generation an owned duplicate UDP listener and refuses generation overlap if the retiring worker does not exit, preserving the supervisor listener across reloads. (#296)
Performance evidence
The sustained benchmark workflow records fixed-rate latency distributions, scheduler drops, CPU/RSS series, platform metadata, and raw outputs for each workload and server. The pre-release evidence is retained in GitHub Actions run 28981909028; the release workflow attaches equivalent schema-validated artifacts to the GitHub release. These results are scoped snapshots, not a universal throughput or latency target.
Install
Core HTTP/1.1 server:
uv add "bengal-pounce==0.9.0"
All optional protocol dependencies:
uv add "bengal-pounce[full]==0.9.0"