Skip to content
Permalink
Browse files

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
Backport-PR-URL: #28000
Fixes: #26015
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
Reviewed-By: James M Snell <jasnell@gmail.com>
  • Loading branch information...
lpinca authored and BethGriggs committed Feb 12, 2019
1 parent cc9d005 commit 3ee076f03d95ecd661202d3ff8348d1ab6bb3048
Showing with 38 additions and 5 deletions.
  1. +12 −5 lib/internal/streams/destroy.js
  2. +26 −0 test/parallel/test-stream-writable-destroy.js
@@ -10,10 +10,15 @@ function destroy(err, cb) {
if (readableDestroyed || writableDestroyed) {
if (cb) {
cb(err);
} else if (err &&
(!this._writableState || !this._writableState.errorEmitted)) {
process.nextTick(emitErrorNT, this, err);
} else if (err) {
if (!this._writableState) {
process.nextTick(emitErrorNT, this, err);
} else if (!this._writableState.errorEmitted) {
this._writableState.errorEmitted = true;
process.nextTick(emitErrorNT, this, err);
}
}

return this;
}

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

this._destroy(err || null, (err) => {
if (!cb && err) {
process.nextTick(emitErrorNT, this, err);
if (this._writableState) {
if (!this._writableState) {
process.nextTick(emitErrorNT, this, err);
} else if (!this._writableState.errorEmitted) {
this._writableState.errorEmitted = true;
process.nextTick(emitErrorNT, this, err);
}
} else if (cb) {
cb(err);
@@ -146,6 +146,32 @@ const { inherits } = require('util');
assert.strictEqual(write.destroyed, true);
}

{
const writable = new Writable({
destroy: common.mustCall(function(err, cb) {
process.nextTick(cb, new Error('kaboom 1'));
}),
write(chunk, enc, cb) {
cb();
}
});

writable.on('close', common.mustNotCall());
writable.on('error', common.expectsError({
type: Error,
message: 'kaboom 2'
}));

writable.destroy();
assert.strictEqual(writable.destroyed, true);
assert.strictEqual(writable._writableState.errorEmitted, false);

// Test case where `writable.destroy()` is called again with an error before
// the `_destroy()` callback is called.
writable.destroy(new Error('kaboom 2'));
assert.strictEqual(writable._writableState.errorEmitted, true);
}

{
const write = new Writable({
write(chunk, enc, cb) { cb(); }

0 comments on commit 3ee076f

Please sign in to comment.
You can’t perform that action at this time.