Skip to content

stream: pass ERR_STREAM_WRITE_AFTER_END to .end(cb) after end#62949

Open
maruthang wants to merge 2 commits intonodejs:mainfrom
maruthang:fix-33684-stream-end-cb-after-end
Open

stream: pass ERR_STREAM_WRITE_AFTER_END to .end(cb) after end#62949
maruthang wants to merge 2 commits intonodejs:mainfrom
maruthang:fix-33684-stream-end-cb-after-end

Conversation

@maruthang
Copy link
Copy Markdown

stream.end(cb) called on a stream that has already been ended did
not propagate ERR_STREAM_WRITE_AFTER_END to the callback. Instead,
the callback was queued for the eventual finish event and called
with null, which was inconsistent with .write(chunk, cb) and
.end(chunk, cb) after end, both of which correctly call cb with
ERR_STREAM_WRITE_AFTER_END.

In Writable.prototype.end, when the stream is already ending and
not yet finished or destroyed and a user callback was supplied,
set the error to ERR_STREAM_WRITE_AFTER_END so the existing
process.nextTick(cb, err) path delivers it. The forgiving
behavior of .end() without a callback being called multiple
times is preserved (no call to errorOrDestroy).

Fixes: #33684


Note: I was unable to run the test suite locally on this Windows host (no built out/Release/node, and system Node v20 is too old for this repo's test/common/index.js which uses getCallSites). Both files lint clean and pass node --check; logic verified by walk-through against current writable.js plus the existing test-stream-writable-end-cb-error.js (which already encodes the post-fix expectation). Looking forward to CI verification.

`stream.end(cb)` called on a stream that has already been ended did
not propagate ERR_STREAM_WRITE_AFTER_END to the callback. Instead,
the callback was queued for the eventual `finish` event and called
with `null`, which was inconsistent with `.write(chunk, cb)` and
`.end(chunk, cb)` after end, both of which correctly call `cb` with
ERR_STREAM_WRITE_AFTER_END.

In `Writable.prototype.end`, when the stream is already ending and
not yet finished or destroyed and a user callback was supplied,
set the error to ERR_STREAM_WRITE_AFTER_END so the existing
`process.nextTick(cb, err)` path delivers it. The forgiving
behavior of `.end()` without a callback being called multiple
times is preserved (no call to errorOrDestroy).

Fixes: nodejs#33684
Signed-off-by: Maruthan G <[email protected]>
@nodejs-github-bot
Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/streams

@nodejs-github-bot nodejs-github-bot added needs-ci PRs that need a full CI run. stream Issues and PRs related to the stream subsystem. labels Apr 25, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 25, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 89.65%. Comparing base (21436f0) to head (be655c5).

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #62949      +/-   ##
==========================================
- Coverage   89.66%   89.65%   -0.01%     
==========================================
  Files         706      706              
  Lines      219391   219399       +8     
  Branches    42068    42073       +5     
==========================================
  Hits       196712   196712              
- Misses      14578    14590      +12     
+ Partials     8101     8097       -4     
Files with missing lines Coverage Δ
lib/internal/streams/writable.js 96.66% <100.00%> (+0.02%) ⬆️

... and 24 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

The previous version always synthesized ERR_STREAM_WRITE_AFTER_END
when kEnding was set, which broke test-stream-writable-end-cb-error
block 1: a writable whose underlying _write errors with `_err`, and
whose `.end(cb1)` and `.end(cb2)` are both expected to receive that
underlying error via kOnFinished.

Tighten the condition to only synthesize WRITE_AFTER_END when the
stream has no in-flight write (kWriting), no buffered data
(kBuffered), no stored error (kErrored), and an empty buffer length.
In any of those cases there is real pending state that will surface a
meaningful error through the existing kOnFinished cascade, so the cb
should be queued and receive that real error instead.

Signed-off-by: Maruthan G <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-ci PRs that need a full CI run. stream Issues and PRs related to the stream subsystem.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

stream: end(callback) does not propagate write after end to callback

2 participants