From 3dc56c02cc3765dce053bb378fb89ea19d35d1b1 Mon Sep 17 00:00:00 2001 From: Eric Newport Date: Wed, 23 Sep 2026 09:53:41 -0400 Subject: [PATCH] stream: do not error on Duplex.from early return `0bf9e9f833` (#65963) introduced a regression. It destroys the duplex when an async function body resolves without consuming its input, so that `pipeline()` propagates destruction upstream. It destroys it through `destroyer()`, which synthesizes an `AbortError` for any stream that is not yet finished. The original behavior, as of node 26.9.0: ``` node -v v26.9.0 node -e "require('node:stream').Duplex.from(async () => {})" // no output; this is expected ``` The bug, as of node 26.10.0: ``` node -v v26.10.0 node -e "require('node:stream').Duplex.from(async () => {})" node:events:505 throw er; // Unhandled 'error' event ^ AbortError: The operation was aborted at destroyer (node:internal/streams/destroy:328:11) at node:internal/streams/duplexify:120:13 Emitted 'error' event on Duplexify instance at: at emitErrorNT (node:internal/streams/destroy:170:8) at emitErrorCloseNT (node:internal/streams/destroy:129:3) at process.processTicksAndRejections (node:internal/process/task_queues:90:21) { code: 'ABORT_ERR' } Node.js v26.10.0 ``` This PR fixes that issue and adds a new regression test. Refs: https://github.com/nodejs/node/pull/65963 Signed-off-by: Eric Newport --- lib/internal/streams/duplexify.js | 7 ++++++- test/parallel/test-stream-duplex-from.js | 12 ++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/internal/streams/duplexify.js b/lib/internal/streams/duplexify.js index 76d12fdbc449..eb9c0393df0d 100644 --- a/lib/internal/streams/duplexify.js +++ b/lib/internal/streams/duplexify.js @@ -116,8 +116,13 @@ module.exports = function duplexify(body, name) { // The async function returned without (fully) consuming the input. // Destroy the duplex so that pipeline propagates destruction // upstream. See https://github.com/nodejs/node/issues/55077. + // Destroy it directly rather than through destroyer(), which + // synthesizes an AbortError for any stream that is not finished. + // The function resolved successfully, so there is no error to + // report, and a duplex outside of a pipeline has nothing listening + // for one. if (!finalized) { - destroyer(d); + d.destroy(); } }, (err) => { diff --git a/test/parallel/test-stream-duplex-from.js b/test/parallel/test-stream-duplex-from.js index a553a90f96e3..12f4f2370585 100644 --- a/test/parallel/test-stream-duplex-from.js +++ b/test/parallel/test-stream-duplex-from.js @@ -434,3 +434,15 @@ function makeATestWritableStream(writeFunc) { }), ); } + +// Regression for the fix to https://github.com/nodejs/node/issues/55077: +// an AsyncFunction that returns without consuming its input is destroyed, but +// that destruction must not be reported as an error. Outside of a pipeline +// there is nothing listening for one, so an error here is unhandled and takes +// the process down. +{ + const duplex = Duplex.from(async function() { + // Intentionally do not consume the async iterable input. + }); + duplex.on('error', common.mustNotCall()); +}