From 3ee076f03d95ecd661202d3ff8348d1ab6bb3048 Mon Sep 17 00:00:00 2001 From: Luigi Pinca Date: Tue, 12 Feb 2019 19:24:39 +0100 Subject: [PATCH] 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 Backport-PR-URL: https://github.com/nodejs/node/pull/28000 Fixes: https://github.com/nodejs/node/issues/26015 Reviewed-By: Matteo Collina Reviewed-By: Ruben Bridgewater Reviewed-By: James M Snell --- lib/internal/streams/destroy.js | 17 ++++++++---- test/parallel/test-stream-writable-destroy.js | 26 +++++++++++++++++++ 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/lib/internal/streams/destroy.js b/lib/internal/streams/destroy.js index 985332ac4607a8..a418a49523900d 100644 --- a/lib/internal/streams/destroy.js +++ b/lib/internal/streams/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); diff --git a/test/parallel/test-stream-writable-destroy.js b/test/parallel/test-stream-writable-destroy.js index 46c48511177813..2d5ad8e6b9e189 100644 --- a/test/parallel/test-stream-writable-destroy.js +++ b/test/parallel/test-stream-writable-destroy.js @@ -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(); }