fix(adapter): read the rest of a body its answer left unread, or close the connection
Merged
K-
2h ago+406+406+012345678901234567890123456789-3-3-0123456789
Summary
- The native server gave the runtime
Readable.toWeb(req)as the request body. When the application stopped reading a body, the request was left paused, or destroyed with its connection detached. Either way, Node.js stopped reading the connection.- This happened to a Server Action over its size limit, to a middleware's side of a split body that nothing read, and to a POST to a static page.
- The connection then answered nothing more until its keep-alive timeout.
- The body is now read from the request directly.
- Cancelling the body stops the reading and leaves the request alone.
- Once the answer has gone out, the rest is read and dropped within workerd's grace: 64 KiB and 1 s, from KJ's
HttpServerSettings. - Past either limit, the connection is ended, then dropped 500 ms later so the client can read its answer first.
- A read of the body after its answer fails, as it does in workerd.
How it showed up
In the 16.4.3 deploy-tests nightly (run 41491299214), app-action-size-limit-invalid and its node-middleware variant timed out on node, once on Node.js 24 and once on 26.
- The browser's load of
/formafter the 3 MB "exceeds the limit" action never completed. - deploy-tests runs
upwind start --keepAliveTimeout 600000(#434), so the browser's next request on that connection waited for the 10-minute timeout. - Before #434, Node.js's default closed the connection after about 6 s, and the browser retried.
Verification
-
The new
native-request-body.test.tshas 6 cases. 4 of them fail on main: the connection is not closed after the answer, a slow remainder is not bounded, a cancelled body leaves the connection stuck, and a late read succeeds. -
The upstream suite's sequence (the 3 MB plaintext action, then
/form) was driven with Playwright against a local build. All runs used a keep-alive timeout of 10 minutes:Server Page loads that hung Connections left upwind start, main1 of 6 stuck ones upwind start, this PR0 of 20 none next start0 of 8 — -
The tests ran on Node.js 24.22.1 and 26.13.0.
Not in this PR
The private bridge between the runtime and Node.js handlers leaves the same kind of connection behind.
- On the server side, those connections have never been closed since #432.
- A pooled one can hold the next call until the bridge's 30 s deadline, which answers 504.
That is #440.