Commit 83269a5
authored
fix(client): cancel the SSE stream on teardown to avoid leaking connections (#580)
# Description
`readFrom` — used by `parseSseStream` for both the JSON-RPC and REST
client
transports — only calls `reader.releaseLock()` in its `finally`:
```ts
async function* readFrom(stream: ReadableStream<string>) {
const reader = stream.getReader();
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
yield value;
}
} finally {
reader.releaseLock(); // detaches the reader, but never cancels the stream
}
}
```
When a consumer stops iterating early — a `break`, a `throw`, or the
REST
transport throwing on an `error` event — the async generator's
`return()`/`throw()` runs that `finally`. `releaseLock()` detaches the
reader
but does **not** cancel the underlying `ReadableStream`, so the fetch
body (and
its socket) is left open. Repeated early terminations leak connections.
## Fix
Cancel the reader on teardown so cancellation propagates to the response
body:
```ts
} finally {
await reader.cancel().catch(() => {});
reader.releaseLock();
}
```
Semantics (verified against the WHATWG Streams behaviour):
- **early break / throw** → `cancel()` propagates to the source, closing
the
connection (the fix).
- **normal completion** → the stream is already closed; `cancel()` is a
no-op.
- **errored source** → `cancel()` rejects with the stored error; it is
ignored
so the original error still surfaces to the caller.
## Tests
Adds regression tests asserting the underlying stream is canceled when
the
consumer breaks early and when it throws mid-iteration. Both fail
without the
fix (the source `cancel` callback is never invoked) and pass with it.
Full suite green (1370 tests).
- [x] Follows the `CONTRIBUTING` guide
- [x] PR title uses Conventional Commits (`fix:`)
- [x] Tests and linter pass
- [ ] Docs updated (not necessary)1 parent 5833652 commit 83269a5
2 files changed
Lines changed: 91 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
144 | 144 | | |
145 | 145 | | |
146 | 146 | | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
147 | 151 | | |
148 | 152 | | |
149 | 153 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
51 | 51 | | |
52 | 52 | | |
53 | 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 | + | |
54 | 81 | | |
55 | 82 | | |
56 | 83 | | |
| |||
177 | 204 | | |
178 | 205 | | |
179 | 206 | | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
180 | 267 | | |
181 | 268 | | |
182 | 269 | | |
| |||
0 commit comments