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 — explicitworker_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-scopedpounce.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, includingworkers=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_timeout now bounds each wait for request-body progress, while write_timeout bounds blocked HTTP/1.1, HTTP/2, and WebSocket response delivery. Expiry reports POUNCE_TIMEOUT_REQUEST_BODY or POUNCE_TIMEOUT_WRITEbefore the peer is closed.
  • keep_alive_timeoutapplies between requests, not to accepted WebSockets, active HTTP/2 streams, or active streaming/SSE responses.
  • WebSocketpermessage-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, and HEAD matches GET status 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, thepounce.worker.draininghook, and a reusable two-instance streaming test proxy. (#238)
  • State-specific request-body and response-write timeout enforcement with actionablePOUNCE_*diagnostics. (#242)
  • http2_enabled and --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)
  • HTTPQUERY and its request body are forwarded across HTTP/1.1, HTTP/2, and HTTP/3. Pounce does not define application semantics, caching, authorization, replay policy, or Accept-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 correctHEADbehavior, 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"