fix(node): drain rejected upload bodies - #1952
Conversation
There was a problem hiding this comment.
ℹ️ The fix is correct and the drain mechanism works. Minor suggestion inline-referenced below.
Reviewed changes
limitStreamrewritten as an async generator — replacespipeThrough(new TransformStream(...))withfor await (... of stream.values({ preventCancel: true }))so the source Web stream is never cancelled (avoiding Node'sERR_INVALID_STATE), throwingPAYLOAD_TOO_LARGEpast the limit, and draining the rejected body viastream.pipeTo(new WritableStream())in afinally.- Always apply
limitStream— dropped the twofileLimit === Infinity ? stream : limitStream(...)shortcuts in the multipart and spool paths; withInfinitythe wrapper is a harmless pass-through.
ℹ️ No regression test pinned for the fixed crash
This is a subtle stream-semantics fix, and the exact scenario that originally crashed — a per-file multipart limit with an unlimited aggregate, a raw raw or event-streamed body that gets rejected mid-flight — has no repo test asserting it. A test would guard against the pipeThrough/cancel regression returning (e.g. in packages/node/tests/uploads-large-files.test.ts).
Technical details
# No regression test for rejected-upload drain/crash
## Affected sites
- /tmp-file-upload-handler.test / uploads-large-files e2e — nothing asserts the callback path this change exposes
## Required outcome
- A test that sends a per-file-limited multipart (or a spooled / streamed body) that trips the limit mid-arrival at a low rate, then asserts the request rejects with PAYLOAD_TOO_LARGE and the server completes without an unhandled ERR_INVALID_STATE (and, ideally, that the response arrives and any connection/socket is not left holding the unread body).
## Suggested approach
- Add an e2e test in `packages/node/tests/uploads-large-files.test.ts` mirroring the existing per-file-limit e2e, but where the body is still being delivered when the limit trips (no content-length fast path), so the drain path is exercised.
- Optionally assert the underlying socket is drained (server can serve an immediate follow-up request).DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
|
Thanks for the thorough diagnosis — it was right, and it led to the proper fix landing upstream: middleapi/standardserver#76 (released in 0.8.1) drops I verified against this exact scenario (per-file multipart limit, flooded 20 MB uploads): on 0.8.0 the rejection resets the connection ( |

Summary
Why
A per-file multipart limit stopped async iteration early. That canceled the Web stream created from the Node request. Node could then deliver a queued data event to the closed Web stream controller and throw ERR_INVALID_STATE.
The limiter now uses a native async generator. It prevents source cancellation and discards remaining request bytes in the background.
Tests