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: https://github.com/nodejs/node/pull/26057
Fixes: https://github.com/nodejs/node/issues/26015
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
Luigi Pinca 2019-02-12 19:24:39 +01:00 committed by Ruben Bridgewater
parent 60aaf2c214
commit 49f1bb9b93
No known key found for this signature in database
GPG Key ID: F07496B3EB3C1762
2 changed files with 40 additions and 5 deletions

View File

@ -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,13 @@ function destroy(err, cb) {
this._destroy(err || null, (err) => {
if (!cb && err) {
process.nextTick(emitErrorAndCloseNT, this, err);
if (this._writableState) {
if (!this._writableState) {
process.nextTick(emitErrorAndCloseNT, this, err);
} else if (!this._writableState.errorEmitted) {
this._writableState.errorEmitted = true;
process.nextTick(emitErrorAndCloseNT, this, err);
} else {
process.nextTick(emitCloseNT, this);
}
} else if (cb) {
process.nextTick(emitCloseNT, this);

View File

@ -152,6 +152,32 @@ const assert = require('assert');
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.mustCall());
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(); }