From 462c3534476c3d0f34315422361d9820b842c776 Mon Sep 17 00:00:00 2001 From: Marten Richter Date: Sun, 16 Aug 2026 20:11:11 +0200 Subject: [PATCH 1/9] quic: add option waitUntilAvailable to creating streams We add an option to createBidirectionalStream and createUnidirectionalStream to fail immediately, if the flow control's stream budget does not allow stream creation. The behavior matches W3C webtransport's behavior. Fixes #65321 Signed-off-by: Marten Richter --- doc/api/quic.md | 10 ++++ lib/internal/quic/quic.js | 3 +- src/quic/session.cc | 24 ++++++-- src/quic/session.h | 3 + .../test-quic-stream-limits-pending.mjs | 59 +++++++++++++------ 5 files changed, 75 insertions(+), 24 deletions(-) diff --git a/doc/api/quic.md b/doc/api/quic.md index 5bdac8189fac..89772c2cceff 100644 --- a/doc/api/quic.md +++ b/doc/api/quic.md @@ -1356,6 +1356,11 @@ added: v23.8.0 will buffer before `writeSync()` returns `false`. When the buffered data exceeds this limit, the caller should wait for drain before writing more. **Default:** `65536` (64 KB). + * `waitUntilAvailable` {boolean} When true the promise will wait until flow + control will allow to open the stream. If set to false, the function + will fail synchronously, if flow control will not allow to open the stream + immediately. + **Default:** `false` * `onheaders` {Function} Callback for received initial response headers. Called with `(headers)`. * `ontrailers` {Function} Callback for received trailing headers. @@ -1397,6 +1402,11 @@ added: v23.8.0 will buffer before `writeSync()` returns `false`. When the buffered data exceeds this limit, the caller should wait for drain before writing more. **Default:** `65536` (64 KB). + * `waitUntilAvailable` {boolean} When true the promise will wait until flow + control will allow to open the stream. If set to false, the function + will fail synchronously, if flow control will not allow to open the stream + immediately. + **Default:** `false` * `onheaders` {Function} Callback for received initial response headers. Called with `(headers)`. * `ontrailers` {Function} Callback for received trailing headers. diff --git a/lib/internal/quic/quic.js b/lib/internal/quic/quic.js index 4922ce562751..9805c0028020 100644 --- a/lib/internal/quic/quic.js +++ b/lib/internal/quic/quic.js @@ -3509,6 +3509,7 @@ class QuicSession { incremental = false, budget = kDefaultBudget, headers, + waitUntilAvailable = false, onheaders, ontrailers, oninfo, @@ -3520,7 +3521,7 @@ class QuicSession { const validatedBody = validateBody(body); - const handle = this.#handle.openStream(direction, validatedBody); + const handle = this.#handle.openStream(direction, waitUntilAvailable, validatedBody); if (handle === undefined) { throw new ERR_QUIC_OPEN_STREAM_FAILED(); } diff --git a/src/quic/session.cc b/src/quic/session.cc index 90d59487a2a7..07c9e741ab98 100644 --- a/src/quic/session.cc +++ b/src/quic/session.cc @@ -1168,17 +1168,24 @@ struct Session::Impl final : public MemoryRetainer { } DCHECK(args[0]->IsUint32()); + DCHECK(args[1]->IsBoolean()); + + auto direction = FromV8Value(args[0]); + if (!args[1].As()->Value() && false) { // This is waitUntilAvailable + if (!session->CanImmediatelyOpenStream(direction)) { + return THROW_ERR_INVALID_STATE(env, "No new stream available within flow control"); + } + } // GetDataQueueFromSource handles type validation. std::shared_ptr data_source; - if (!Stream::GetDataQueueFromSource(env, args[1]).To(&data_source)) + if (!Stream::GetDataQueueFromSource(env, args[2]).To(&data_source)) [[unlikely]] { return THROW_ERR_INVALID_ARG_VALUE(env, "Invalid data source"); } session->impl_->handshake_deferred_ = false; SendPendingDataScope send_scope(session); - auto direction = FromV8Value(args[0]); Local stream; if (session->OpenStream(direction, std::move(data_source)).ToLocal(&stream)) [[likely]] { @@ -3203,6 +3210,14 @@ BaseObjectPtr Session::CreateStream( return {}; } +bool Session::CanImmediatelyOpenStream(Direction direction) { + if (direction == Direction::BIDIRECTIONAL) { + return max_local_streams_bidi() > 0; + } else { + return max_local_streams_uni() > 0; + } +} + MaybeLocal Session::OpenStream(Direction direction, std::shared_ptr data_source) { // If can_create_streams() returns false, we are not able to open a stream @@ -3508,13 +3523,12 @@ void Session::SetApplicationError(error_code app_error_code) { uint64_t Session::max_local_streams_uni() const { DCHECK(!is_destroyed()); - return ngtcp2_conn_get_streams_uni_left(*this); + return ngtcp2_conn_get_streams_uni_left2(*this); } uint64_t Session::max_local_streams_bidi() const { DCHECK(!is_destroyed()); - return ngtcp2_conn_get_local_transport_params(*this) - ->initial_max_streams_bidi; + return ngtcp2_conn_get_streams_bidi_left2(*this); } void Session::set_wrapped() { diff --git a/src/quic/session.h b/src/quic/session.h index 9834aa7ec130..cc67f0ce3a67 100644 --- a/src/quic/session.h +++ b/src/quic/session.h @@ -535,6 +535,9 @@ class Session final : public AsyncWrap, private SessionTicket::AppData::Source { size_t max_packet_size() const; void set_priority_supported(bool on = true); + // Check whether flow control permits opening another stream + bool CanImmediatelyOpenStream(Direction direction); + // Open a new locally-initialized stream with the specified directionality. // If the session is not yet in a state where the stream can be opened -- // such as when the handshake is not yet sufficiently far along and ORTT diff --git a/test/parallel/test-quic-stream-limits-pending.mjs b/test/parallel/test-quic-stream-limits-pending.mjs index 2d81b0d79096..609f85d42687 100644 --- a/test/parallel/test-quic-stream-limits-pending.mjs +++ b/test/parallel/test-quic-stream-limits-pending.mjs @@ -15,22 +15,30 @@ if (!hasQuic) { const { listen, connect } = await import('../common/quic.mjs'); const { bytes } = await import('stream/iter'); +const { setTimeout: sleep } = await import('timers/promises'); const encoder = new TextEncoder(); const allDone = Promise.withResolvers(); +const twoDone = Promise.withResolvers(); let serverStreamCount = 0; // Server allows only 1 bidi stream at a time. const serverEndpoint = await listen(mustCall((serverSession) => { serverSession.onstream = mustCall(async (stream) => { - await bytes(stream); + const streambytes = await bytes(stream); stream.writer.endSync(); await stream.closed; - if (++serverStreamCount === 2) { - serverSession.close(); + ++serverStreamCount; + if (serverStreamCount === 2) { + twoDone.resolve(); + } + if (serverStreamCount === 3) { allDone.resolve(); } - }, 2); + if (serverStreamCount === 4) { + serverSession.close(); + } + }, 3); }), { transportParams: { initialMaxStreamsBidi: 1 }, }); @@ -43,6 +51,7 @@ let opened = 0; // First stream opens immediately (within the limit). const s1 = await clientSession.createBidirectionalStream({ body: encoder.encode('stream 1'), + waitUntilAvailable: true }); // eslint-disable-next-line node-core/must-call-assert @@ -50,16 +59,21 @@ s1.opened.then(() => { opened++; }); +let s2 // Second stream is created but queued as pending because the -// server only allows 1 concurrent bidi stream. -const s2 = await clientSession.createBidirectionalStream({ - body: encoder.encode('stream 2'), -}); - -// eslint-disable-next-line node-core/must-call-assert -s2.opened.then(() => { - opened++; -}); +try { + // Second stream should not open, but throw. + s2 = await clientSession.createBidirectionalStream({ + body: encoder.encode('stream 2'), + waitUntilAvailable: false + }); + // eslint-disable-next-line node-core/must-call-assert + s2.opened.then(() => { + opened++; + }); +} catch (error) { + assert.strictEqual(error.code, 'ERR_INVALID_STATE'); +} // Third stream is created but queued as pending because the // server only allows 1 concurrent bidi stream. @@ -72,6 +86,9 @@ const s3 = await clientSession.createBidirectionalStream({ // more stream credits. assert.strictEqual(s2.pending, true); assert.strictEqual(opened, 1); +// s3 should be pending until s1 closes and the server grants +// more stream credits. +assert.strictEqual(s3.pending, true); // Drain and close the first stream. for await (const _ of s1) { /* drain */ } // eslint-disable-line no-unused-vars @@ -82,15 +99,21 @@ s3.destroy(err); await Promise.all([assert.rejects(s3.opened, err), assert.rejects(s3.closed, err)]); - -// After s1 closes, the server sends MAX_STREAMS which opens s2. +// After s1 closes, the server sends MAX_STREAMS which opens s3. // Wait for the server to receive both streams. -await allDone.promise; -assert.strictEqual(opened, 2); - +await twoDone.promise; // s2 should no longer be pending. for await (const _ of s2) { /* drain */ } // eslint-disable-line no-unused-vars await s2.closed; +await sleep(10); // we wait a bit, as we do not have a callback exposed to js +// fourth stream should open immediately and not throw +const s4 = await clientSession.createBidirectionalStream({ + body: encoder.encode('stream 4'), + waitUntilAvailable: false +}); +await s4.closed; +await allDone.promise; + await clientSession.close(); await serverEndpoint.close(); From de676ca52a45e2a7045d67c5e8ab390164ba737b Mon Sep 17 00:00:00 2001 From: Marten Richter Date: Mon, 17 Aug 2026 05:01:26 +0200 Subject: [PATCH 2/9] quic: fix lint and bugfixes --- src/quic/session.cc | 6 ++++-- test/parallel/test-quic-h3-pending-stream.mjs | 1 + test/parallel/test-quic-stream-limits-pending.mjs | 8 ++++---- test/parallel/test-quic-stream-limits-uni.mjs | 1 + test/parallel/test-quic-stream-pending.mjs | 1 + 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/quic/session.cc b/src/quic/session.cc index 07c9e741ab98..4671f199f9c4 100644 --- a/src/quic/session.cc +++ b/src/quic/session.cc @@ -1171,9 +1171,11 @@ struct Session::Impl final : public MemoryRetainer { DCHECK(args[1]->IsBoolean()); auto direction = FromV8Value(args[0]); - if (!args[1].As()->Value() && false) { // This is waitUntilAvailable + if (!args[1].As()->Value()) { + // This is waitUntilAvailable if (!session->CanImmediatelyOpenStream(direction)) { - return THROW_ERR_INVALID_STATE(env, "No new stream available within flow control"); + return THROW_ERR_INVALID_STATE( + env, "No new stream available within flow control"); } } diff --git a/test/parallel/test-quic-h3-pending-stream.mjs b/test/parallel/test-quic-h3-pending-stream.mjs index a6e9c8cfd912..d4c822066a1b 100644 --- a/test/parallel/test-quic-h3-pending-stream.mjs +++ b/test/parallel/test-quic-h3-pending-stream.mjs @@ -63,6 +63,7 @@ const decoder = new TextDecoder(); // Priority set at creation time. priority: 'high', incremental: true, + waitUntilAvailable: true, onheaders: mustCall(function(headers) { assert.strictEqual(headers[':status'], 200); }), diff --git a/test/parallel/test-quic-stream-limits-pending.mjs b/test/parallel/test-quic-stream-limits-pending.mjs index 609f85d42687..973f1527365a 100644 --- a/test/parallel/test-quic-stream-limits-pending.mjs +++ b/test/parallel/test-quic-stream-limits-pending.mjs @@ -25,7 +25,7 @@ let serverStreamCount = 0; // Server allows only 1 bidi stream at a time. const serverEndpoint = await listen(mustCall((serverSession) => { serverSession.onstream = mustCall(async (stream) => { - const streambytes = await bytes(stream); + await bytes(stream); stream.writer.endSync(); await stream.closed; ++serverStreamCount; @@ -63,9 +63,9 @@ let s2 // Second stream is created but queued as pending because the try { // Second stream should not open, but throw. - s2 = await clientSession.createBidirectionalStream({ + await clientSession.createBidirectionalStream({ body: encoder.encode('stream 2'), - waitUntilAvailable: false + waitUntilAvailable: false, }); // eslint-disable-next-line node-core/must-call-assert s2.opened.then(() => { @@ -106,7 +106,7 @@ await twoDone.promise; for await (const _ of s2) { /* drain */ } // eslint-disable-line no-unused-vars await s2.closed; -await sleep(10); // we wait a bit, as we do not have a callback exposed to js +await sleep(10); // We wait a bit, as we do not have a callback exposed to js // fourth stream should open immediately and not throw const s4 = await clientSession.createBidirectionalStream({ body: encoder.encode('stream 4'), diff --git a/test/parallel/test-quic-stream-limits-uni.mjs b/test/parallel/test-quic-stream-limits-uni.mjs index f3686ca45e3c..1b26adc306ee 100644 --- a/test/parallel/test-quic-stream-limits-uni.mjs +++ b/test/parallel/test-quic-stream-limits-uni.mjs @@ -49,6 +49,7 @@ s1.opened.then(() => { // Second uni stream is pending (limit = 1). const s2 = await clientSession.createUnidirectionalStream({ body: encoder.encode('uni 2'), + waitUntilAvailable: true, }); // eslint-disable-next-line node-core/must-call-assert diff --git a/test/parallel/test-quic-stream-pending.mjs b/test/parallel/test-quic-stream-pending.mjs index a51bc711585e..673c94b3f873 100644 --- a/test/parallel/test-quic-stream-pending.mjs +++ b/test/parallel/test-quic-stream-pending.mjs @@ -35,6 +35,7 @@ const clientSession = await connect(serverEndpoint.address); // completed yet. The stream should be created in a pending state. const stream = await clientSession.createBidirectionalStream({ body: encoder.encode('pending stream'), + waitUntilAvailable: true, }); // The stream should initially be pending (no ID assigned yet). From 0f70c998fe04d8124006f8a915b0320553a7aed4 Mon Sep 17 00:00:00 2001 From: Marten Richter Date: Mon, 17 Aug 2026 05:26:11 +0200 Subject: [PATCH 3/9] quic: lint and fixup --- doc/api/quic.md | 4 +- src/quic/session.cc | 4 +- .../test-quic-stream-limits-pending.mjs | 39 +++++++++---------- 3 files changed, 22 insertions(+), 25 deletions(-) diff --git a/doc/api/quic.md b/doc/api/quic.md index 89772c2cceff..501c97a58eb1 100644 --- a/doc/api/quic.md +++ b/doc/api/quic.md @@ -1358,8 +1358,8 @@ added: v23.8.0 writing more. **Default:** `65536` (64 KB). * `waitUntilAvailable` {boolean} When true the promise will wait until flow control will allow to open the stream. If set to false, the function - will fail synchronously, if flow control will not allow to open the stream - immediately. + will return a rejected promise, if flow control will not allow to + open the stream immediately. **Default:** `false` * `onheaders` {Function} Callback for received initial response headers. Called with `(headers)`. diff --git a/src/quic/session.cc b/src/quic/session.cc index 4671f199f9c4..4985efd4cf5b 100644 --- a/src/quic/session.cc +++ b/src/quic/session.cc @@ -1175,7 +1175,7 @@ struct Session::Impl final : public MemoryRetainer { // This is waitUntilAvailable if (!session->CanImmediatelyOpenStream(direction)) { return THROW_ERR_INVALID_STATE( - env, "No new stream available within flow control"); + env, "No new stream available within flow control"); } } @@ -3530,7 +3530,7 @@ uint64_t Session::max_local_streams_uni() const { uint64_t Session::max_local_streams_bidi() const { DCHECK(!is_destroyed()); - return ngtcp2_conn_get_streams_bidi_left2(*this); + return ngtcp2_conn_get_streams_bidi_left2(*this); } void Session::set_wrapped() { diff --git a/test/parallel/test-quic-stream-limits-pending.mjs b/test/parallel/test-quic-stream-limits-pending.mjs index 973f1527365a..7cbd00546adb 100644 --- a/test/parallel/test-quic-stream-limits-pending.mjs +++ b/test/parallel/test-quic-stream-limits-pending.mjs @@ -54,26 +54,24 @@ const s1 = await clientSession.createBidirectionalStream({ waitUntilAvailable: true }); -// eslint-disable-next-line node-core/must-call-assert -s1.opened.then(() => { - opened++; -}); - let s2 -// Second stream is created but queued as pending because the -try { - // Second stream should not open, but throw. - await clientSession.createBidirectionalStream({ - body: encoder.encode('stream 2'), - waitUntilAvailable: false, - }); - // eslint-disable-next-line node-core/must-call-assert - s2.opened.then(() => { - opened++; - }); -} catch (error) { - assert.strictEqual(error.code, 'ERR_INVALID_STATE'); -} +await assert.rejects( + async () => { + // Second stream should not open, but throw. + s2 = await clientSession.createBidirectionalStream({ + body: encoder.encode('stream 2'), + waitUntilAvailable: false, + }); + // eslint-disable-next-line node-core/must-call-assert + s2.opened.then(() => { + opened++; + }); + }, + { + name: 'Error', + message: 'No new stream available within flow control', + }, +); // Third stream is created but queued as pending because the // server only allows 1 concurrent bidi stream. @@ -112,8 +110,7 @@ const s4 = await clientSession.createBidirectionalStream({ body: encoder.encode('stream 4'), waitUntilAvailable: false }); -await s4.closed; -await allDone.promise; +await Promise.all([s4.closed, allDone.promise]); await clientSession.close(); await serverEndpoint.close(); From 61dcbff2da708a45b10ca31d7c194d91a176eaaf Mon Sep 17 00:00:00 2001 From: Marten Richter Date: Mon, 17 Aug 2026 05:36:34 +0200 Subject: [PATCH 4/9] quic: waitUntilAvailable change default behavor and fixes --- doc/api/quic.md | 2 +- lib/internal/quic/quic.js | 2 +- test/parallel/test-quic-h3-pending-stream.mjs | 1 - test/parallel/test-quic-stream-limits-pending.mjs | 1 - 4 files changed, 2 insertions(+), 4 deletions(-) diff --git a/doc/api/quic.md b/doc/api/quic.md index 501c97a58eb1..90e90ac6f4e7 100644 --- a/doc/api/quic.md +++ b/doc/api/quic.md @@ -1360,7 +1360,7 @@ added: v23.8.0 control will allow to open the stream. If set to false, the function will return a rejected promise, if flow control will not allow to open the stream immediately. - **Default:** `false` + **Default:** `true` * `onheaders` {Function} Callback for received initial response headers. Called with `(headers)`. * `ontrailers` {Function} Callback for received trailing headers. diff --git a/lib/internal/quic/quic.js b/lib/internal/quic/quic.js index 9805c0028020..c5dbb7484c08 100644 --- a/lib/internal/quic/quic.js +++ b/lib/internal/quic/quic.js @@ -3509,7 +3509,7 @@ class QuicSession { incremental = false, budget = kDefaultBudget, headers, - waitUntilAvailable = false, + waitUntilAvailable = true, onheaders, ontrailers, oninfo, diff --git a/test/parallel/test-quic-h3-pending-stream.mjs b/test/parallel/test-quic-h3-pending-stream.mjs index d4c822066a1b..a6e9c8cfd912 100644 --- a/test/parallel/test-quic-h3-pending-stream.mjs +++ b/test/parallel/test-quic-h3-pending-stream.mjs @@ -63,7 +63,6 @@ const decoder = new TextDecoder(); // Priority set at creation time. priority: 'high', incremental: true, - waitUntilAvailable: true, onheaders: mustCall(function(headers) { assert.strictEqual(headers[':status'], 200); }), diff --git a/test/parallel/test-quic-stream-limits-pending.mjs b/test/parallel/test-quic-stream-limits-pending.mjs index 7cbd00546adb..e86272d37553 100644 --- a/test/parallel/test-quic-stream-limits-pending.mjs +++ b/test/parallel/test-quic-stream-limits-pending.mjs @@ -51,7 +51,6 @@ let opened = 0; // First stream opens immediately (within the limit). const s1 = await clientSession.createBidirectionalStream({ body: encoder.encode('stream 1'), - waitUntilAvailable: true }); let s2 From 5d0981df8720d9464346de66676cc90cb30974e1 Mon Sep 17 00:00:00 2001 From: Marten Richter Date: Sat, 29 Aug 2026 03:51:24 -0600 Subject: [PATCH 5/9] doc: Update quic.md remove trailing whitespace --- doc/api/quic.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/quic.md b/doc/api/quic.md index 90e90ac6f4e7..0689b4aca883 100644 --- a/doc/api/quic.md +++ b/doc/api/quic.md @@ -1358,7 +1358,7 @@ added: v23.8.0 writing more. **Default:** `65536` (64 KB). * `waitUntilAvailable` {boolean} When true the promise will wait until flow control will allow to open the stream. If set to false, the function - will return a rejected promise, if flow control will not allow to + will return a rejected promise, if flow control will not allow to open the stream immediately. **Default:** `true` * `onheaders` {Function} Callback for received initial response headers. From 01c9c58cb1935fbcc13984c6ba6056eb1022644b Mon Sep 17 00:00:00 2001 From: Marten Richter Date: Sat, 29 Aug 2026 04:04:59 -0600 Subject: [PATCH 6/9] Update quic.md that the promise is rejected --- doc/api/quic.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/api/quic.md b/doc/api/quic.md index 0689b4aca883..b163ffc1fd5e 100644 --- a/doc/api/quic.md +++ b/doc/api/quic.md @@ -1404,8 +1404,8 @@ added: v23.8.0 writing more. **Default:** `65536` (64 KB). * `waitUntilAvailable` {boolean} When true the promise will wait until flow control will allow to open the stream. If set to false, the function - will fail synchronously, if flow control will not allow to open the stream - immediately. + will return a rejected promise, if flow control will not allow + to open the stream immediately. **Default:** `false` * `onheaders` {Function} Callback for received initial response headers. Called with `(headers)`. From f5cc0a1f82e8e5a5d26251e28d3b8ee3eec7156e Mon Sep 17 00:00:00 2001 From: Marten Richter Date: Sat, 29 Aug 2026 04:08:38 -0600 Subject: [PATCH 7/9] Update test-quic-stream-limits-uni.mjs for new default vals --- test/parallel/test-quic-stream-limits-uni.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/parallel/test-quic-stream-limits-uni.mjs b/test/parallel/test-quic-stream-limits-uni.mjs index 1b26adc306ee..1c7fbd974386 100644 --- a/test/parallel/test-quic-stream-limits-uni.mjs +++ b/test/parallel/test-quic-stream-limits-uni.mjs @@ -39,6 +39,7 @@ let opened = 0; // First uni stream opens immediately. const s1 = await clientSession.createUnidirectionalStream({ body: encoder.encode('uni 1'), + waitUntilAvailable: false, }); // eslint-disable-next-line node-core/must-call-assert @@ -49,7 +50,6 @@ s1.opened.then(() => { // Second uni stream is pending (limit = 1). const s2 = await clientSession.createUnidirectionalStream({ body: encoder.encode('uni 2'), - waitUntilAvailable: true, }); // eslint-disable-next-line node-core/must-call-assert From 4354ef32e1c8a9332e59161b5bc00da65b9845b1 Mon Sep 17 00:00:00 2001 From: Marten Richter Date: Sat, 29 Aug 2026 04:11:33 -0600 Subject: [PATCH 8/9] Update test-quic-stream-pending.mjs for new waitUntilAvail... --- test/parallel/test-quic-stream-pending.mjs | 1 - 1 file changed, 1 deletion(-) diff --git a/test/parallel/test-quic-stream-pending.mjs b/test/parallel/test-quic-stream-pending.mjs index 673c94b3f873..a51bc711585e 100644 --- a/test/parallel/test-quic-stream-pending.mjs +++ b/test/parallel/test-quic-stream-pending.mjs @@ -35,7 +35,6 @@ const clientSession = await connect(serverEndpoint.address); // completed yet. The stream should be created in a pending state. const stream = await clientSession.createBidirectionalStream({ body: encoder.encode('pending stream'), - waitUntilAvailable: true, }); // The stream should initially be pending (no ID assigned yet). From f450014f9d4b409a3697d5e833d0a31fc0ae2da3 Mon Sep 17 00:00:00 2001 From: Marten Richter Date: Sun, 27 Sep 2026 09:37:17 +0200 Subject: [PATCH 9/9] quic: fix test after rebase --- .../test-quic-stream-limits-pending.mjs | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/test/parallel/test-quic-stream-limits-pending.mjs b/test/parallel/test-quic-stream-limits-pending.mjs index e86272d37553..76220df8ad5b 100644 --- a/test/parallel/test-quic-stream-limits-pending.mjs +++ b/test/parallel/test-quic-stream-limits-pending.mjs @@ -53,12 +53,17 @@ const s1 = await clientSession.createBidirectionalStream({ body: encoder.encode('stream 1'), }); -let s2 +// eslint-disable-next-line node-core/must-call-assert +s1.opened.then(() => { + opened++; +}); + +let s2; await assert.rejects( async () => { // Second stream should not open, but throw. s2 = await clientSession.createBidirectionalStream({ - body: encoder.encode('stream 2'), + body: encoder.encode('stream 2a'), waitUntilAvailable: false, }); // eslint-disable-next-line node-core/must-call-assert @@ -71,6 +76,15 @@ await assert.rejects( message: 'No new stream available within flow control', }, ); +// Ok try again a second second stream, that patiently waits +s2 = await clientSession.createBidirectionalStream({ + body: encoder.encode('stream 2b') +}); +// eslint-disable-next-line node-core/must-call-assert +s2.opened.then(() => { + opened++; +}); + // Third stream is created but queued as pending because the // server only allows 1 concurrent bidi stream. @@ -79,13 +93,11 @@ const s3 = await clientSession.createBidirectionalStream({ }); -// s2 should be pending until s1 closes and the server grants +// s2 and s3 should be pending until s1 closes and the server grants // more stream credits. assert.strictEqual(s2.pending, true); -assert.strictEqual(opened, 1); -// s3 should be pending until s1 closes and the server grants -// more stream credits. assert.strictEqual(s3.pending, true); +assert.strictEqual(opened, 1); // Drain and close the first stream. for await (const _ of s1) { /* drain */ } // eslint-disable-line no-unused-vars