Skip to content

Commit 02c47ad

Browse files
committed
stream: forward errors correctly for duplexPair endpoints
fix the duplexPair implementation so that when one side is destroyed with an error, the other side also receives the error or a close event as appropriate. previous behavior caused sideA to never emit an 'error' or 'close' when sideB errored, which prevented users from observing or handling the paired stream failure. Fixes: #61015
1 parent 05f8772 commit 02c47ad

File tree

2 files changed

+34
-1
lines changed

2 files changed

+34
-1
lines changed

lib/internal/streams/duplexpair.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,26 @@ class DuplexSide extends Duplex {
5050
this.#otherSide.on('end', callback);
5151
this.#otherSide.push(null);
5252
}
53+
54+
55+
_destroy(err, callback) {
56+
if (err) {
57+
// Error case: tell the other side to also destroy with that error.
58+
this.#otherSide.destroy(err);
59+
} else if (this.#otherSide && !this.#otherSide.destroyed) {
60+
// Graceful close case (destroy() without error):
61+
// send an EOF to the other side's readable end if it hasn't already closed.
62+
this.#otherSide.push(null);
63+
}
64+
callback(err);
65+
}
5366
}
5467

5568
function duplexPair(options) {
5669
const side0 = new DuplexSide(options);
5770
const side1 = new DuplexSide(options);
5871
side0[kInitOtherSide](side1);
5972
side1[kInitOtherSide](side0);
60-
return [ side0, side1 ];
73+
return [side0, side1];
6174
}
6275
module.exports = duplexPair;

test/parallel/test-duplex-error.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const { duplexPair } = require('stream');
6+
7+
const [sideA, sideB] = duplexPair();
8+
9+
sideA.on('error', common.mustCall((err) => {
10+
assert.strictEqual(err.message, 'Simulated error');
11+
}));
12+
13+
sideB.on('error', common.mustCall((err) => {
14+
assert.strictEqual(err.message, 'Simulated error');
15+
}));
16+
17+
sideA.resume();
18+
sideB.resume();
19+
20+
sideB.destroy(new Error('Simulated error'));

0 commit comments

Comments
 (0)