fix(runtime): drop a bridge connection whose server left its request unread
Summary
- A Node.js handler can stop reading its request before the end. Next.js does this with a Server Action over its size limit: the pipeline's failure destroys the request, and Node.js reads that connection no further.
- If the whole upload fitted in the loopback's buffers, the bridge's client had sent all of it, so the Agent pooled the connection. The next call sent on it waited for the bridge's headers deadline (30 s), then answered 504.
- Since #432 the server keeps idle connections, so the server's end of each such connection was never closed either. Each one held its unread bytes in kernel buffers, in
CLOSE-WAITonce the client side had gone. - Now, a connection whose server has not read the request to its end is destroyed at both ends. That happens once the answer has been read whole or the call has ended without it, and before the Agent can pool the connection.
How it showed up
While I triaged the 16.4.3 nightly (run 41491299214), a local upwind start build of app-dir/actions showed the problem:
- After a few 3 MB actions over the 2 MB limit, a GET to a dynamic Node.js page answered 504 after 28.9 s.
- The process held about 20 bridge connections with up to 800 KB unread each.
The outer server's side of the same failure is #439.
Verification
-
2 new bridge tests use a handler that reads its body as Next.js's action handler does (a pipeline through a size limit). Both fail on main:
- The next call reuses the connection and hits the headers deadline.
- The server's end of the connection never closes.
-
test/native/(187 tests) passes on Node.js 24.22.1 and 26.13.0. -
With this PR and #439, after 20 oversize actions in the same local build:
Check main this PR and #439 Connections left, in the process about 20, in the bridge none The dynamic page 504 after 28.9 s 200 in 0.3–0.6 s
Summary by cubic
Fixes a bridge connection leak that made calls time out with a 504 after a handler stopped reading its request body, as Next.js does with a Server Action over its size limit.
Previously, the Agent pooled connections whose server had left part of the request unread, so the next call sent on one waited for the headers deadline and answered 504. The server's end of such a connection also stayed open, since idle connections are kept.
Now a connection whose server has not read its request to the end is destroyed at both ends, once the answer has been read whole or the call has ended without it, and before the Agent can pool it.
Bug Fixes
- Two new bridge tests use a handler that reads its body like Next.js's action handler; both failed on
main. test/native/passes on Node.js 24.22.1 and 26.13.0.