You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
On Node.js 24.22.0+, every streamed response hangs (the fake socket has no writableLength / writableHighWaterMark) #423
Starting with Node.js 24.22.0, ServerResponse.write() returns false when the adapter's fake socket is attached, and the framework then waits for a 'drain' event that is never emitted. The client receives the whole body, but res.end() is never called, so AwsStreamHandler never ends the Lambda response stream and the invocation runs until its timeout.
AWS Lambda's managed nodejs24.x runtime moved to Node 24.22.2 in runtime version nodejs:24.mainline.v115. Any function on UpdateRuntimeOn: Auto picks it up on its next deploy. That took our production API down: every request timed out at 180 s until we pinned the function to v114 (Node 24.21.0).
Cause
Node 24.22 uses the socket's writableLength and writableHighWaterMark to decide whether write() should signal backpressure. The fake sockets in src/network/response-stream.ts and src/network/response.ts define neither:
So the response reports writableLength: NaN and writableHighWaterMark: undefined, the comparison always says "wait", and write() returns false with writableNeedDrain: true. Frameworks that respect backpressure (we hit it with graphql-yoga behind Express) then wait for 'drain' forever.
Measured with the same app and AwsStreamHandler, for one small JSON response:
We've applied that as a patch to 4.4.0. On Node 24.22.2 (Lambda v115), plain responses, a 425 KB single response and a 506 KB multipart @defer stream all complete, and Node 24.21.0 behaves exactly as before.
Related: #393 (empty-body responses hanging, merged to main but not yet released). Could a release include both fixes? I'm happy to open a PR for this one.
That took our production API down: every request timed out at 180 s until we pinned the function to v114 (Node 24.21.0).
Sad to hear this :/
Could a release include both fixes? I'm happy to open a PR for this one.
Yes, would love to have a PR for this
I'm being a little lazy on this next release due to amount of frameworks to check in clouds that I don't have account anymore, but due to the severity of this issue, I might want to release this soon
As noted on #424, the change behind this is nodejs/node#64987 (http: coalesce chunked writes during auto-corking, commit 742b4bf5a6). It is the only commit in the v24.22.0 release that touched lib/_http_outgoing.js, and it replaced a comparison between two numeric fields with the public getters:
- ret = msg[kChunkedLength] < msg[kHighWaterMark];+ ret = msg.writableLength < msg.writableHighWaterMark;
kHighWaterMark always holds a number (options?.highWaterMark ?? getDefaultHighWaterMark()). Both getters switch to the socket as soon as one is assigned:
The adapter's fake socket defines neither, so the comparison evaluates NaN < undefined. That is false, write() returns false, and writableNeedDrain stays true with nothing left to emit drain.
The repository tests already catch it
On v24.22.0 and v24.22.2 the suite fails with three 5 s timeouts, while v24.21.0 passes:
test/handlers/aws-stream.handler.spec.ts: "should return the correct bytes of chunked stream" and "with eagerly flushed headers"
test/issues/issue-165/transfer-encoding-chunked-support.spec.ts: "fastify: should handle transfer-encoding: chunked"
CI showed it on 2026-10-21
Dependabot PR #420 ran build (26.x) on Node 26.12.0 and failed with exactly those three timeouts. #422 repeated it on 26.13.0. In both runs the 24.x leg was cancelled by fail-fast, and #421's 24.x failure is an unrelated ERESOLVE, so the red check that matches this issue is #420, one week ago.
That PR targets nodejs/node#66781, where a real socket counts string chunks in UTF-16 units and stays under its own highWaterMark. Its new drain check is gated on this.writableLength === 0, and here writableLength is NaN, so the branch never runs. Applied on top of a v24.22.0 tree and rebuilt, the hang remains. v24.22.0 already carries the same check in uncork(), at lib/_http_outgoing.js:344, with the same gate, and that is the check failing today.
Adding the two fields fixes it
With writableLength: 0 and writableHighWaterMark: 16384 on both fake sockets the comparison becomes 0 < 16384, write() returns true, and no framework waits for a drain that will not arrive. The suite goes from three timeouts to zero failures, on prebuilt v24.22.0, v24.22.2, and a from-source v24.22.0 build. #424 carries that change.
Also, just to let you know, LLM found few issues that can cause data loss under specific scenarios, I will likely release a 4.4.2, just need to spent more time on testing to ensure the new change is correct (and not too slow).
Activity
H4ad commentedon Oct 30, 2026
Sad to hear this :/
Yes, would love to have a PR for this
I'm being a little lazy on this next release due to amount of frameworks to check in clouds that I don't have account anymore, but due to the severity of this issue, I might want to release this soon
garfieldnate commentedon Oct 30, 2026
Hi, thank you for the super quick response! I very much appreciate it. I'll get claude to send a PR, one minute.
fix(network): give the fake sockets a writable length and high-water …
H4ad commentedon Oct 30, 2026
Root cause: nodejs/node#64987, shipped in v24.22.0
As noted on #424, the change behind this is nodejs/node#64987 (
http: coalesce chunked writes during auto-corking, commit742b4bf5a6). It is the only commit in the v24.22.0 release that touchedlib/_http_outgoing.js, and it replaced a comparison between two numeric fields with the public getters:kHighWaterMarkalways holds a number (options?.highWaterMark ?? getDefaultHighWaterMark()). Both getters switch to the socket as soon as one is assigned:The adapter's fake socket defines neither, so the comparison evaluates
NaN < undefined. That is false,write()returns false, andwritableNeedDrainstays true with nothing left to emitdrain.The repository tests already catch it
On v24.22.0 and v24.22.2 the suite fails with three 5 s timeouts, while v24.21.0 passes:
test/handlers/aws-stream.handler.spec.ts: "should return the correct bytes of chunked stream" and "with eagerly flushed headers"test/issues/issue-165/transfer-encoding-chunked-support.spec.ts: "fastify: should handle transfer-encoding: chunked"CI showed it on 2026-10-21
Dependabot PR #420 ran
build (26.x)on Node 26.12.0 and failed with exactly those three timeouts. #422 repeated it on 26.13.0. In both runs the 24.x leg was cancelled by fail-fast, and #421's 24.x failure is an unrelatedERESOLVE, so the red check that matches this issue is #420, one week ago.nodejs/node#66782 does not fix this
That PR targets nodejs/node#66781, where a real socket counts string chunks in UTF-16 units and stays under its own
highWaterMark. Its new drain check is gated onthis.writableLength === 0, and herewritableLengthisNaN, so the branch never runs. Applied on top of a v24.22.0 tree and rebuilt, the hang remains. v24.22.0 already carries the same check inuncork(), atlib/_http_outgoing.js:344, with the same gate, and that is the check failing today.Adding the two fields fixes it
With
writableLength: 0andwritableHighWaterMark: 16384on both fake sockets the comparison becomes0 < 16384,write()returns true, and no framework waits for a drain that will not arrive. The suite goes from three timeouts to zero failures, on prebuilt v24.22.0, v24.22.2, and a from-source v24.22.0 build. #424 carries that change.H4ad commentedon Oct 31, 2026
@garfieldnate deployed at https://github.com/H4ad/serverless-adapter/releases/tag/v4.4.1
Also, just to let you know, LLM found few issues that can cause data loss under specific scenarios, I will likely release a 4.4.2, just need to spent more time on testing to ensure the new change is correct (and not too slow).