From 02133842b8011d410eb5e162db12a615853e5df9 Mon Sep 17 00:00:00 2001 From: cjc0013 Date: Thu, 11 Jun 2026 18:47:00 -0400 Subject: [PATCH] stream: error callbacks after destroy during write/final --- lib/internal/streams/writable.js | 7 +++++++ test/parallel/test-stream-writable-destroy.js | 18 ++++++++++++++++ .../test-stream-writable-final-destroy.js | 21 +++++++++++++++++++ 3 files changed, 46 insertions(+) diff --git a/lib/internal/streams/writable.js b/lib/internal/streams/writable.js index 94913461cb6044..9ffe386a3b56db 100644 --- a/lib/internal/streams/writable.js +++ b/lib/internal/streams/writable.js @@ -628,6 +628,10 @@ function onwrite(stream, er) { state.length -= state.writelen; state.writelen = 0; + if (!er && (state[kState] & kDestroyed) !== 0) { + er = state[kErroredValue] ?? new ERR_STREAM_DESTROYED('write'); + } + if (er) { // Avoid V8 leak, https://github.com/nodejs/node/pull/34103#issuecomment-652002364 er.stack; // eslint-disable-line no-unused-expressions @@ -889,6 +893,9 @@ function onFinish(stream, state, err) { return; } state.pendingcb--; + if (!err && (state[kState] & kDestroyed) !== 0) { + err = state[kErroredValue] ?? new ERR_STREAM_DESTROYED('end'); + } if (err) { callFinishedCallbacks(state, err); errorOrDestroy(stream, err, (state[kState] & kSync) !== 0); diff --git a/test/parallel/test-stream-writable-destroy.js b/test/parallel/test-stream-writable-destroy.js index 31e9ac40664fdf..a11bc2330f0b32 100644 --- a/test/parallel/test-stream-writable-destroy.js +++ b/test/parallel/test-stream-writable-destroy.js @@ -30,6 +30,24 @@ const assert = require('assert'); assert.strictEqual(write.destroyed, true); } +{ + const write = new Writable({ + write(chunk, enc, cb) { + this.destroy(); + cb(); + } + }); + + write.on('error', common.mustNotCall()); + write.on('finish', common.mustNotCall()); + write.write('asd', common.expectsError({ + code: 'ERR_STREAM_DESTROYED', + name: 'Error', + message: 'Cannot call write after a stream was destroyed' + })); + assert.strictEqual(write.destroyed, true); +} + { const write = new Writable({ write(chunk, enc, cb) { cb(); } diff --git a/test/parallel/test-stream-writable-final-destroy.js b/test/parallel/test-stream-writable-final-destroy.js index 8d3bf72c89126f..ea4fb443271b47 100644 --- a/test/parallel/test-stream-writable-final-destroy.js +++ b/test/parallel/test-stream-writable-final-destroy.js @@ -19,3 +19,24 @@ const { Writable } = require('stream'); w.on('finish', common.mustNotCall()); w.on('close', common.mustCall()); } + +{ + const w = new Writable({ + write(chunk, encoding, callback) { + callback(null); + }, + final(callback) { + this.destroy(); + callback(); + } + }); + + w.on('error', common.mustNotCall()); + w.on('finish', common.mustNotCall()); + w.on('close', common.mustCall()); + w.end(common.expectsError({ + code: 'ERR_STREAM_DESTROYED', + name: 'Error', + message: 'Cannot call end after a stream was destroyed' + })); +}