From 413eb7d1c3849ba79020e3331fe57b57fcdbc75f Mon Sep 17 00:00:00 2001 From: Christian Aurich Zanettini Martins Date: Thu, 24 Sep 2026 10:59:16 -0300 Subject: [PATCH] quic: destroy stream on invalid promise body When `setBody()` receives a promise that resolves to an unsupported body type, `configureOutbound()` throws inside the fulfillment handler. The rejection handler only covered the original promise, so the error rejected a discarded promise and crashed the process as an unhandled rejection. Catch errors thrown by `configureOutbound()` in the fulfillment handler and destroy the stream with them, as the rejection handler already does. For an unsupported resolved value, `stream.closed` now rejects with the same `ERR_INVALID_ARG_TYPE` that `setBody()` throws synchronously. Signed-off-by: Christian Aurich Zanettini Martins --- lib/internal/quic/quic.js | 15 +++++-- .../test-quic-stream-body-promise-invalid.mjs | 39 +++++++++++++++++++ 2 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 test/parallel/test-quic-stream-body-promise-invalid.mjs diff --git a/lib/internal/quic/quic.js b/lib/internal/quic/quic.js index 632369fc908..b670606e71f 100644 --- a/lib/internal/quic/quic.js +++ b/lib/internal/quic/quic.js @@ -1375,14 +1375,21 @@ function configureOutbound(handle, stream, body) { // Handle Promise - await and recurse. Native promises auto-flatten, // so the resolved value will never itself be a promise. if (isPromise(body)) { + const onError = (err) => { + if (!stream.destroyed) { + stream.destroy(err); + } + }; PromisePrototypeThen( body, - (resolved) => configureOutbound(handle, stream, resolved), - (err) => { - if (!stream.destroyed) { - stream.destroy(err); + (resolved) => { + try { + configureOutbound(handle, stream, resolved); + } catch (err) { + onError(err); } }, + onError, ); return; } diff --git a/test/parallel/test-quic-stream-body-promise-invalid.mjs b/test/parallel/test-quic-stream-body-promise-invalid.mjs new file mode 100644 index 00000000000..65f4dd27bb3 --- /dev/null +++ b/test/parallel/test-quic-stream-body-promise-invalid.mjs @@ -0,0 +1,39 @@ +// Flags: --experimental-quic --no-warnings + +// Test: body: Promise resolving to an invalid body destroys the stream. +// When the body is a Promise that resolves to an unsupported type, the +// stream should be destroyed with the same ERR_INVALID_ARG_TYPE that +// setBody() throws synchronously for that type. + +import { hasQuic, skip, mustCall } from '../common/index.mjs'; +import assert from 'node:assert'; + +if (!hasQuic) { + skip('QUIC is not enabled'); +} + +const { listen, connect } = await import('../common/quic.mjs'); + +const serverEndpoint = await listen(mustCall(async (serverSession) => { + await serverSession.closed; +}), { transportParams: { maxIdleTimeout: 5 } }); + +const clientSession = await connect(serverEndpoint.address, { + transportParams: { maxIdleTimeout: 5 }, +}); +await clientSession.opened; + +const stream = await clientSession.createBidirectionalStream(); + +assert.throws(() => stream.setBody(42), { code: 'ERR_INVALID_ARG_TYPE' }); + +const stream2 = await clientSession.createBidirectionalStream(); + +const closedPromise = assert.rejects(stream2.closed, { + code: 'ERR_INVALID_ARG_TYPE', +}); + +stream2.setBody(Promise.resolve(42)); + +await Promise.all([closedPromise, clientSession.closed]); +await serverEndpoint.close();