# 0.9.0

URL: /pounce/releases/0.9.0/
Section: releases
Description: Minor release for stable subinterpreter web workers, bounded stream and timeout lifecycle, protocol hardening, and deployment-backed evidence

---

> For a complete page index, fetch /pounce/llms.txt.

## 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.draining` hook 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/info` can 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_path` for 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_WRITE` before the peer is closed.
- `keep_alive_timeout` applies between requests, not to accepted WebSockets, active HTTP/2 streams, or active streaming/SSE responses.
- WebSocket `permessage-deflate` is 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 `/healthz` liveness 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.draining` hook, and a reusable two-instance streaming test proxy. ([#238](https://github.com/lbliii/pounce/issues/238))
- State-specific request-body and response-write timeout enforcement with actionable `POUNCE_*` diagnostics. ([#242](https://github.com/lbliii/pounce/issues/242))
- `http2_enabled` and `--no-http2` for forcing HTTP/1.1 ALPN at Pounce-owned TLS origins. ([#243](https://github.com/lbliii/pounce/issues/243))
- Fail-loud worker startup policy, readiness gating, resolved-worker reporting, and runtime build identity. ([#245](https://github.com/lbliii/pounce/issues/245), [#246](https://github.com/lbliii/pounce/issues/246), [#252](https://github.com/lbliii/pounce/issues/252))
- Official Railway release recipe, deploy/redeploy smoke proof, and a public main-branch canary. ([#248](https://github.com/lbliii/pounce/issues/248), [#293](https://github.com/lbliii/pounce/pull/293))
- Reproducible HTTP/3 and sustained fixed-rate benchmark evidence with per-process telemetry and release-asset automation. ([#228](https://github.com/lbliii/pounce/issues/228), [#240](https://github.com/lbliii/pounce/issues/240), [#253](https://github.com/lbliii/pounce/issues/253))

## Changed

- Explicit subinterpreter ASGI web workers are now stable within the documented import, async, state, and dependency-compatibility limits. ([#239](https://github.com/lbliii/pounce/issues/239))
- OpenTelemetry request-span names, attributes, and status mapping are proven through a real OTLP/HTTP collector boundary. ([#255](https://github.com/lbliii/pounce/issues/255))
- HTTP `QUERY` 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](https://github.com/lbliii/pounce/issues/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](https://github.com/lbliii/pounce/issues/231), [#232](https://github.com/lbliii/pounce/issues/232))
- Subinterpreter reload and crash recovery preserve accepted connections and descriptor ownership across worker replacement. ([#106](https://github.com/lbliii/pounce/issues/106), [#239](https://github.com/lbliii/pounce/issues/239))
- Sync workers emit startup and shutdown hooks on their own runner loop, matching the lifecycle ownership used by other worker modes. ([#244](https://github.com/lbliii/pounce/issues/244))
- Built-in endpoints implement correct `HEAD` behavior, and WebSocket compression is never negotiated without a client offer. ([#250](https://github.com/lbliii/pounce/issues/250), [#256](https://github.com/lbliii/pounce/issues/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](https://github.com/lbliii/pounce/issues/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](https://github.com/lbliii/pounce/pull/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](https://github.com/lbliii/pounce/actions/runs/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:

```bash
uv add "bengal-pounce==0.9.0"
```

All optional protocol dependencies:

```bash
uv add "bengal-pounce[full]==0.9.0"
```
