From c7ecaf234fc3345faa149b64833e7701b54f4cbb Mon Sep 17 00:00:00 2001 From: Tim Perry Date: Fri, 24 Jul 2026 14:48:01 +0200 Subject: [PATCH] quic: fix infinite loop if STOP_SENDING received on a buffering stream Signed-off-by: Tim Perry --- src/quic/application.cc | 6 +++ ...test-quic-stream-stop-sending-buffered.mjs | 49 +++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 test/parallel/test-quic-stream-stop-sending-buffered.mjs diff --git a/src/quic/application.cc b/src/quic/application.cc index 688a1309dea552..79a3263b8537a9 100644 --- a/src/quic/application.cc +++ b/src/quic/application.cc @@ -410,6 +410,12 @@ class DefaultApplication final : public Session::Application { void ResumeStream(stream_id id) override { ScheduleStream(id); } + void StreamWriteShut(stream_id id) override { + if (auto stream = session().FindStream(id)) [[likely]] { + stream->Unschedule(); + } + } + void BlockStream(stream_id id) override { if (auto stream = session().FindStream(id)) [[likely]] { // Remove the stream from the send queue. It will be re-scheduled diff --git a/test/parallel/test-quic-stream-stop-sending-buffered.mjs b/test/parallel/test-quic-stream-stop-sending-buffered.mjs new file mode 100644 index 00000000000000..1d41635b4575d3 --- /dev/null +++ b/test/parallel/test-quic-stream-stop-sending-buffered.mjs @@ -0,0 +1,49 @@ +// Flags: --experimental-quic --no-warnings + +// A peer STOP_SENDING must unschedule buffered outbound data. + +import { hasQuic, mustCall, skip } from '../common/index.mjs'; +import assert from 'node:assert'; + +if (!hasQuic) { + skip('QUIC is not enabled'); +} + +const { connect, listen } = await import('../common/quic.mjs'); + +const serverStreamReady = Promise.withResolvers(); +const clientBuffered = Promise.withResolvers(); +const serverReset = Promise.withResolvers(); + +const serverEndpoint = await listen(mustCall((serverSession) => { + serverSession.onstream = mustCall(async (stream) => { + serverStreamReady.resolve(); + await clientBuffered.promise; + + const closed = assert.rejects(stream.closed, { + code: 'ERR_QUIC_APPLICATION_ERROR', + }); + stream.stopSending(1n); + stream.writer.endSync(); + await closed; + serverSession.close(); + serverReset.resolve(); + }); +})); + +const clientSession = await connect(serverEndpoint.address); +await clientSession.opened; + +const stream = await clientSession.createBidirectionalStream(); +const clientClosed = stream.closed.catch(() => {}); +const writer = stream.writer; +writer.writeSync(new Uint8Array([1])); +await serverStreamReady.promise; + +writer.writeSync(new Uint8Array(64 * 1024)); +clientBuffered.resolve(); + +await serverReset.promise; + +await Promise.all([clientClosed, clientSession.closed]); +await serverEndpoint.close();