From 6531b9841b036e3755120876a284739127c55d6e Mon Sep 17 00:00:00 2001 From: Robert Nagy Date: Tue, 13 Apr 2021 00:54:24 +0200 Subject: [PATCH] stream: fix multiple Writable.destroy() calls Calling Writable.destroy() multiple times in the same tick could cause an assertion error. Fixes: https://github.com/nodejs/node/issues/38189 PR-URL: https://github.com/nodejs/node/pull/38221 Reviewed-By: Matteo Collina Reviewed-By: Luigi Pinca Reviewed-By: Nitzan Uziely Reviewed-By: Rich Trott --- lib/internal/streams/writable.js | 6 ++- test/parallel/test-stream-writable-destroy.js | 54 +++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/lib/internal/streams/writable.js b/lib/internal/streams/writable.js index fbc0532681b83d..26e5ae6d25e7c8 100644 --- a/lib/internal/streams/writable.js +++ b/lib/internal/streams/writable.js @@ -770,7 +770,11 @@ ObjectDefineProperties(Writable.prototype, { const destroy = destroyImpl.destroy; Writable.prototype.destroy = function(err, cb) { const state = this._writableState; - if (!state.destroyed) { + + // Invoke pending callbacks. + if (!state.destroyed && + (state.bufferedIndex < state.buffered.length || + state[kOnFinished].length)) { process.nextTick(errorBuffer, state, new ERR_STREAM_DESTROYED('write')); } destroy.call(this, err, cb); diff --git a/test/parallel/test-stream-writable-destroy.js b/test/parallel/test-stream-writable-destroy.js index 9f6136923ee176..b63216e23ae644 100644 --- a/test/parallel/test-stream-writable-destroy.js +++ b/test/parallel/test-stream-writable-destroy.js @@ -417,3 +417,57 @@ const assert = require('assert'); })); write.write('asd'); } + +{ + const ac = new AbortController(); + const write = addAbortSignal(ac.signal, new Writable({ + write(chunk, enc, cb) { cb(); } + })); + + write.on('error', common.mustCall((e) => { + assert.strictEqual(e.name, 'AbortError'); + assert.strictEqual(write.destroyed, true); + })); + write.write('asd'); + ac.abort(); +} + +{ + const ac = new AbortController(); + const write = new Writable({ + signal: ac.signal, + write(chunk, enc, cb) { cb(); } + }); + + write.on('error', common.mustCall((e) => { + assert.strictEqual(e.name, 'AbortError'); + assert.strictEqual(write.destroyed, true); + })); + write.write('asd'); + ac.abort(); +} + +{ + const signal = AbortSignal.abort(); + + const write = new Writable({ + signal, + write(chunk, enc, cb) { cb(); } + }); + + write.on('error', common.mustCall((e) => { + assert.strictEqual(e.name, 'AbortError'); + assert.strictEqual(write.destroyed, true); + })); +} + +{ + // Destroy twice + const write = new Writable({ + write(chunk, enc, cb) { cb(); } + }); + + write.end(common.mustCall()); + write.destroy(); + write.destroy(); +}