Skip to content

Commit 82a68ce

Browse files
lpincaBridgeAR
authored andcommitted
stream: ensure writable.destroy() emits error once
Prevent the `'error'` event from being emitted multiple times if `writable.destroy()` is called with an error before the `_destroy()` callback is called. Emit the first error, discard all others. PR-URL: #26057 Fixes: #26015 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent 7e40ce1 commit 82a68ce

File tree

2 files changed

+40
-5
lines changed

2 files changed

+40
-5
lines changed

Diff for: lib/internal/streams/destroy.js

+14-5
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,15 @@ function destroy(err, cb) {
1010
if (readableDestroyed || writableDestroyed) {
1111
if (cb) {
1212
cb(err);
13-
} else if (err &&
14-
(!this._writableState || !this._writableState.errorEmitted)) {
15-
process.nextTick(emitErrorNT, this, err);
13+
} else if (err) {
14+
if (!this._writableState) {
15+
process.nextTick(emitErrorNT, this, err);
16+
} else if (!this._writableState.errorEmitted) {
17+
this._writableState.errorEmitted = true;
18+
process.nextTick(emitErrorNT, this, err);
19+
}
1620
}
21+
1722
return this;
1823
}
1924

@@ -31,9 +36,13 @@ function destroy(err, cb) {
3136

3237
this._destroy(err || null, (err) => {
3338
if (!cb && err) {
34-
process.nextTick(emitErrorAndCloseNT, this, err);
35-
if (this._writableState) {
39+
if (!this._writableState) {
40+
process.nextTick(emitErrorAndCloseNT, this, err);
41+
} else if (!this._writableState.errorEmitted) {
3642
this._writableState.errorEmitted = true;
43+
process.nextTick(emitErrorAndCloseNT, this, err);
44+
} else {
45+
process.nextTick(emitCloseNT, this);
3746
}
3847
} else if (cb) {
3948
process.nextTick(emitCloseNT, this);

Diff for: test/parallel/test-stream-writable-destroy.js

+26
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,32 @@ const assert = require('assert');
152152
assert.strictEqual(write.destroyed, true);
153153
}
154154

155+
{
156+
const writable = new Writable({
157+
destroy: common.mustCall(function(err, cb) {
158+
process.nextTick(cb, new Error('kaboom 1'));
159+
}),
160+
write(chunk, enc, cb) {
161+
cb();
162+
}
163+
});
164+
165+
writable.on('close', common.mustCall());
166+
writable.on('error', common.expectsError({
167+
type: Error,
168+
message: 'kaboom 2'
169+
}));
170+
171+
writable.destroy();
172+
assert.strictEqual(writable.destroyed, true);
173+
assert.strictEqual(writable._writableState.errorEmitted, false);
174+
175+
// Test case where `writable.destroy()` is called again with an error before
176+
// the `_destroy()` callback is called.
177+
writable.destroy(new Error('kaboom 2'));
178+
assert.strictEqual(writable._writableState.errorEmitted, true);
179+
}
180+
155181
{
156182
const write = new Writable({
157183
write(chunk, enc, cb) { cb(); }

0 commit comments

Comments
 (0)