Skip to content

fix(parse): pause req during async writeHeaders to avoid chunk loss#1093

Open
guimard wants to merge 1 commit intonode-formidable:masterfrom
guimard:fix-parse-data-race
Open

fix(parse): pause req during async writeHeaders to avoid chunk loss#1093
guimard wants to merge 1 commit intonode-formidable:masterfrom
guimard:fix-parse-data-race

Conversation

@guimard
Copy link
Copy Markdown

@guimard guimard commented Apr 17, 2026

Summary

Since parse() was made async in 3.3.2, it awaits writeHeaders() before attaching its data handling (now req.pipe(pipe) after #1017). If a caller has attached a 'data' listener on the request before calling form.parse() — a common pattern for content-length accounting or logging — the request stream is already in flowing mode. Chunks that arrive during the microtask yielded by the await are emitted and lost before Formidable pipes the stream, causing the first part(s) of a multipart body to be silently dropped.

Reproduction

See the added test test-node/standalone/parse-data-race.test.js. A server attaches req.on('data', …) for content-length accounting, then calls form.parse(req, cb) with a multipart body containing three fields. Without the fix, only the last field is parsed; with the fix all three arrive.

The regression was originally reported downstream via node-form-data's test-ranged-filestream.js, where a req.on('data') listener for content-length verification causes the first two of five parts to vanish.

Fix

Pause the request at the top of parse() so chunks are buffered in the request's internal queue until the parser is ready. req.pipe(pipe) later resumes the stream, so no explicit resume() is needed.

Test plan

  • New regression test test-node/standalone/parse-data-race.test.js passes with the fix and fails without it
  • Existing test suite still passes

Since parse() was made async in 3.3.2, it awaits writeHeaders() before
attaching its data listener (req.pipe after node-formidable#1017). If a caller has
attached a 'data' listener on the request before calling form.parse()
(e.g. for content-length accounting), the request stream is already in
flowing mode. Chunks that arrive during the await are emitted and lost
before Formidable pipes the stream, causing the first part(s) of a
multipart body to be silently dropped.

Pause the request at the top of parse() so that chunks are buffered
internally until the parser is ready. req.pipe(pipe) resumes the
stream, so no explicit resume is needed.

Fixes the regression exposed by downstream node-form-data tests
(test-ranged-filestream.js), where the caller adds a req.on('data')
listener for content-length accounting before form.parse(req).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants