Repository navigation
Commit 1f6cfe1
authored
fix(streaming): restore async HTTP/1.1 connection reuse (#4041)
## Changes being requested
Restore **async HTTP/1.1 streaming connection reuse** after `[DONE]`
with a 100 ms drain budget. When the HTTP body finishes within that
budget, the connection returns to the pool. A timeout or transport
failure closes it without failing completed output.
The drain reads raw transport bytes without decoding trailing SSE or
content. An AnyIO cancellation deadline handles stalled and trickling
endings, including when the request read timeout is disabled. Response
cleanup runs outside that deadline and is shielded from caller
cancellation; cancellation still propagates.
This applies only to async streams receiving `[DONE]` over HTTP/1.1.
Synchronous streaming, HTTP/2, Responses streams without `[DONE]`, early
exits, and pre-completion errors keep their existing behavior. No
watchdog threads, dependency changes, or public API changes.
Builds on the fresh transport-iterator approach in [Michał Rembalski's
proposal](mrembalski@5d615ad).
## Why async only
The synchronous approach we evaluated needed a temporary watchdog thread
per stream to enforce a total drain deadline. A per-read timeout alone
does not bound a response that keeps trickling bytes. In three synthetic
runs of 1,024 requests at concurrency 128, sync HTTPX2 median batch time
increased from **2.70 s to 3.23 s**. A diagnostic variant that retained
reuse but removed the watchdog took **2.73 s**, isolating thread
creation/joining as the main added cost.
We therefore leave synchronous streaming unchanged rather than introduce
that overhead in a performance fix. Async cleanup can enforce the
deadline through cancellation without watchdog threads. A bounded sync
implementation without the measured regression is separate work; HTTP/2
remains a connection-reuse workaround where supported.
## Validation
Three focused regression tests cover:
- Async connection reuse and recovery from silent, trickling, and
truncated endings with read timeouts disabled, across HTTPX2, legacy
HTTPX, and aiohttp.
- Draining only async HTTP/1.1 streams, leaving sync and HTTP/2
unchanged.
- Underlying-stream cleanup on caller cancellation and deadline expiry.
After narrowing the scope:
- Streaming/chat/agent suites: 217 passed, 8 skipped.
- Pydantic v1 streaming/chat suites: 81 passed, 34 skipped.
- Ruff, repository Pyright, and mypy passed.
- Structural comparison confirms sync `Stream` matches main and the
async implementation matches the version exercised by the TCP/TLS stress
tests and 120 asyncio/Trio cancellation/recovery cycles.
## Performance and tradeoffs
In three synthetic TLS comparisons of 1,024 measured requests at
concurrency 128, async HTTPX2 median batch time improved from **3.16 s
to 2.95 s** with connection reuse.
Legacy HTTPX async has a separate pooling cost at high concurrency: a
synthetic batch of 512 requests at concurrency 64 increased from 2.16 s
to 5.06 s. Consuming ordinary EOF with an unpatched SDK reproduced that
pooling cost. This PR does not change the transport's pool
implementation.
There is no fixed 100 ms sleep. Cleanup returns as soon as the HTTP body
ends; a delayed ending can add up to the drain budget, plus scheduling
and close overhead. These synthetic results are not production latency
estimates or a guarantee that every workload becomes faster.
## Additional context & links
Addresses the async streaming case of #4040. Related to #3440.1 parent c5452af commit 1f6cfe1
3 files changed
Lines changed: 203 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
13 | 13 | | |
14 | 14 | | |
15 | 15 | | |
| 16 | + | |
16 | 17 | | |
17 | 18 | | |
18 | 19 | | |
| |||
183 | 184 | | |
184 | 185 | | |
185 | 186 | | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
186 | 192 | | |
187 | 193 | | |
188 | 194 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
5 | 8 | | |
| 9 | + | |
6 | 10 | | |
7 | 11 | | |
| 12 | + | |
| 13 | + | |
8 | 14 | | |
9 | 15 | | |
10 | 16 | | |
11 | 17 | | |
12 | 18 | | |
13 | | - | |
| 19 | + | |
14 | 20 | | |
15 | 21 | | |
16 | 22 | | |
| |||
29 | 35 | | |
30 | 36 | | |
31 | 37 | | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
32 | 199 | | |
33 | 200 | | |
34 | 201 | | |
| |||
0 commit comments