From da1ab904cde81139248d3cdcce1c161d3ad45b6d Mon Sep 17 00:00:00 2001 From: yashshinde8585 Date: Sun, 5 Jul 2026 15:29:23 +0530 Subject: [PATCH] quic: handle stream writer reset error --- lib/internal/quic/quic.js | 4 ++ ...est-quic-stream-writer-unhandled-error.mjs | 54 +++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 test/parallel/test-quic-stream-writer-unhandled-error.mjs diff --git a/lib/internal/quic/quic.js b/lib/internal/quic/quic.js index 99eeccc3c51d23..68740ad59c2656 100644 --- a/lib/internal/quic/quic.js +++ b/lib/internal/quic/quic.js @@ -2313,6 +2313,7 @@ class QuicStream { getQuicSessionState(stream.#inner.session).internalErrorCode; handle.resetStream(code); if (drainWakeup != null) { + markPromiseAsHandled(drainWakeup.promise); drainWakeup.reject(error); drainWakeup = null; } @@ -2508,6 +2509,9 @@ class QuicStream { } if (error !== undefined) { inner.pendingClose.reject(error); + if (!inner.destroying && typeof inner.onerror === 'function') { + invokeOnerror(inner.onerror, error); + } } else { inner.pendingClose.resolve(); } diff --git a/test/parallel/test-quic-stream-writer-unhandled-error.mjs b/test/parallel/test-quic-stream-writer-unhandled-error.mjs new file mode 100644 index 00000000000000..1cc027381c11b5 --- /dev/null +++ b/test/parallel/test-quic-stream-writer-unhandled-error.mjs @@ -0,0 +1,54 @@ +// Flags: --experimental-quic --no-warnings + +// Test: Verify that when a peer resets a stream after writer.endSync() +// is called, the stream.onerror handler fires and no unhandled rejection occurs. + +import { hasQuic, skip, mustCall } from '../common/index.mjs'; +import * as assert from 'node:assert'; + +const { rejects, strictEqual } = assert; + +if (!hasQuic) { + skip('QUIC is not enabled'); +} + +const { listen, connect } = await import('../common/quic.mjs'); + +const serverDone = Promise.withResolvers(); + +// Listen for unhandled rejections to catch regressions. +process.on('unhandledRejection', (reason) => { + assert.fail(`Unhandled rejection: ${reason}`); +}); + +const serverEndpoint = await listen(mustCall((serverSession) => { + serverSession.onstream = mustCall(async (stream) => { + // Set up onerror to verify it fires on remote reset + const errorFired = Promise.withResolvers(); + stream.onerror = mustCall((error) => { + strictEqual(error.code, 'ERR_QUIC_APPLICATION_ERROR'); + strictEqual(error.errorCode, 256n); + errorFired.resolve(); + }); + + stream.sendHeaders({ ':status': '200' }); + stream.writer.endSync(); + + await errorFired.promise; + await rejects(stream.closed); + + serverSession.close(); + serverDone.resolve(); + }); +})); + +const clientSession = await connect(serverEndpoint.address); +await clientSession.opened; + +const stream = await clientSession.createBidirectionalStream(); +stream.resetStream(256n); +await rejects(stream.closed); + +await serverDone.promise; +await clientSession.close(); +await serverEndpoint.close();