mirror of
https://github.com/nodejs/node.git
synced 2026-10-28 18:25:13 +08:00
stream: propagate destruction in duplexPair
Ensure destroying one side of a duplexPair triggers destruction of the other side via process.nextTick(). Only the destruction signal is sent to avoid breaking changes. Fixes: https://github.com/nodejs/node/issues/61015 PR-URL: https://github.com/nodejs/node/pull/61098 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Gürgün Dayıoğlu <hey@gurgun.day> Reviewed-By: René <contact.9a5d6388@renegade334.me.uk>
This commit is contained in:
1 parent
511a57a179
commit
68e5f873aa
2 files changed
+57
-1
No files matched your search
@@ -50,6 +50,30 @@ class DuplexSide extends Duplex {
|
||||
this.#otherSide.on('end', callback);
|
||||
this.#otherSide.push(null);
|
||||
}
|
||||
|
||||
|
||||
_destroy(err, callback) {
|
||||
const otherSide = this.#otherSide;
|
||||
|
||||
if (otherSide !== null && !otherSide.destroyed) {
|
||||
// Use nextTick to avoid crashing the current execution stack (like HTTP parser)
|
||||
process.nextTick(() => {
|
||||
if (otherSide.destroyed) return;
|
||||
|
||||
if (err) {
|
||||
// Destroy the other side, without passing the 'err' object.
|
||||
// This closes the other side gracefully so it doesn't hang,
|
||||
// but prevents the "Unhandled error" crash.
|
||||
otherSide.destroy();
|
||||
} else {
|
||||
// Standard graceful close
|
||||
otherSide.push(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
callback(err);
|
||||
}
|
||||
}
|
||||
|
||||
function duplexPair(options) {
|
||||
@@ -57,6 +81,6 @@ function duplexPair(options) {
|
||||
const side1 = new DuplexSide(options);
|
||||
side0[kInitOtherSide](side1);
|
||||
side1[kInitOtherSide](side0);
|
||||
return [ side0, side1 ];
|
||||
return [side0, side1];
|
||||
}
|
||||
module.exports = duplexPair;
|
||||
@@ -0,0 +1,32 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const { duplexPair } = require('stream');
|
||||
|
||||
const [sideA, sideB] = duplexPair();
|
||||
|
||||
// Side A should receive the error because we called .destroy(err) on it.
|
||||
sideA.on('error', common.mustCall((err) => {
|
||||
assert.strictEqual(err.message, 'Simulated error');
|
||||
}));
|
||||
|
||||
// Side B should NOT necessarily emit an error (to avoid crashing
|
||||
// existing code), but it MUST be destroyed.
|
||||
sideB.on('error', common.mustNotCall('Side B should not emit an error event'));
|
||||
|
||||
sideB.on('close', common.mustCall(() => {
|
||||
assert.strictEqual(sideB.destroyed, true);
|
||||
}));
|
||||
|
||||
sideA.resume();
|
||||
sideB.resume();
|
||||
|
||||
// Trigger the destruction
|
||||
sideA.destroy(new Error('Simulated error'));
|
||||
|
||||
// Check the state in the next tick to allow nextTick/microtasks to run
|
||||
setImmediate(common.mustCall(() => {
|
||||
assert.strictEqual(sideA.destroyed, true);
|
||||
assert.strictEqual(sideB.destroyed, true);
|
||||
}));
|
||||
Reference in new issue
Block a user