Skip to content

Commit be655c5

Browse files
committed
stream: pass ERR_STREAM_WRITE_AFTER_END to .end(cb) after end
`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 Signed-off-by: Maruthan G <[email protected]>
1 parent 21436f0 commit be655c5

2 files changed

Lines changed: 82 additions & 0 deletions

File tree

lib/internal/streams/writable.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -845,6 +845,14 @@ Writable.prototype.end = function(chunk, encoding, cb) {
845845
err = new ERR_STREAM_ALREADY_FINISHED('end');
846846
} else if ((state[kState] & kDestroyed) !== 0) {
847847
err = new ERR_STREAM_DESTROYED('end');
848+
} else if ((state[kState] & kEnding) !== 0 && typeof cb === 'function') {
849+
// The stream is already ending (or ended) but not yet finished or
850+
// destroyed. For consistency with `.write(chunk, cb)` after end (which
851+
// invokes cb with ERR_STREAM_WRITE_AFTER_END), surface the same error
852+
// through the user-supplied callback. Note: we do not call
853+
// errorOrDestroy here in order to preserve the pre-existing forgiving
854+
// behavior of `.end()` (no cb) called multiple times.
855+
err = new ERR_STREAM_WRITE_AFTER_END();
848856
}
849857

850858
if (typeof cb === 'function') {
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
'use strict';
2+
3+
// Regression test for https://github.com/nodejs/node/issues/33684
4+
// Calling `.end(cb)` on a stream that has already been ended must invoke
5+
// the user-supplied callback with ERR_STREAM_WRITE_AFTER_END, mirroring
6+
// the behavior of `.write(chunk, cb)` after end and `.end(chunk, cb)`
7+
// after end.
8+
9+
const common = require('../common');
10+
const assert = require('assert');
11+
const { Writable } = require('stream');
12+
13+
{
14+
// `.end()` followed by `.end(cb)` — cb must receive ERR_STREAM_WRITE_AFTER_END.
15+
const w = new Writable({
16+
write(chunk, encoding, cb) { cb(); },
17+
});
18+
19+
w.end();
20+
w.end(common.mustCall((err) => {
21+
assert.ok(err);
22+
assert.strictEqual(err.code, 'ERR_STREAM_WRITE_AFTER_END');
23+
}));
24+
}
25+
26+
{
27+
// `.end()` followed by `.end('chunk', cb)` — cb must receive
28+
// ERR_STREAM_WRITE_AFTER_END (existing behavior, included for parity).
29+
const w = new Writable({
30+
write(chunk, encoding, cb) { cb(); },
31+
});
32+
33+
w.on('error', common.mustCall((err) => {
34+
assert.strictEqual(err.code, 'ERR_STREAM_WRITE_AFTER_END');
35+
}));
36+
37+
w.end();
38+
w.end('chunk', common.mustCall((err) => {
39+
assert.ok(err);
40+
assert.strictEqual(err.code, 'ERR_STREAM_WRITE_AFTER_END');
41+
}));
42+
}
43+
44+
{
45+
// `.write(chunk, cb)` after end calls cb with ERR_STREAM_WRITE_AFTER_END —
46+
// sanity check confirming that `.end(cb)` is now consistent with this path.
47+
const w = new Writable({
48+
write(chunk, encoding, cb) { cb(); },
49+
});
50+
51+
w.on('error', common.mustCall((err) => {
52+
assert.strictEqual(err.code, 'ERR_STREAM_WRITE_AFTER_END');
53+
}));
54+
55+
w.end();
56+
w.write('chunk', common.mustCall((err) => {
57+
assert.ok(err);
58+
assert.strictEqual(err.code, 'ERR_STREAM_WRITE_AFTER_END');
59+
}));
60+
}
61+
62+
{
63+
// `.end(null, encoding, cb)` after end — cb must receive
64+
// ERR_STREAM_WRITE_AFTER_END.
65+
const w = new Writable({
66+
write(chunk, encoding, cb) { cb(); },
67+
});
68+
69+
w.end();
70+
w.end(null, null, common.mustCall((err) => {
71+
assert.ok(err);
72+
assert.strictEqual(err.code, 'ERR_STREAM_WRITE_AFTER_END');
73+
}));
74+
}

0 commit comments

Comments
 (0)