Skip to content

quic: add option waitUntilAvailable to creating streams - #65331

Open
martenrichter wants to merge 8 commits into
nodejs:mainfrom
martenrichter:waituntilavailable
Open

quic: add option waitUntilAvailable to creating streams#65331
martenrichter wants to merge 8 commits into
nodejs:mainfrom
martenrichter:waituntilavailable

Conversation

@martenrichter

Copy link
Copy Markdown
Contributor

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

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 nodejs#65321

Signed-off-by: Marten Richter <marten.richter@freenet.de>
@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/quic

@nodejs-github-bot nodejs-github-bot added c++ Issues and PRs that require attention from people who are familiar with C++. lib / src Issues and PRs involving general changes in the lib/ or src/ directories. needs-ci PRs that need a full CI run. labels Aug 16, 2026
@codecov

codecov Bot commented Aug 16, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.05%. Comparing base (4551732) to head (4684e2a).
⚠️ Report is 461 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #65331      +/-   ##
==========================================
- Coverage   90.30%   90.05%   -0.26%     
==========================================
  Files         751      754       +3     
  Lines      250235   255723    +5488     
  Branches    47305    48314    +1009     
==========================================
+ Hits       225987   230299    +4312     
- Misses      15619    16557     +938     
- Partials     8629     8867     +238     
Files with missing lines Coverage Δ
lib/internal/quic/quic.js 100.00% <100.00%> (ø)

... and 210 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

});

// Second stream is created but queued as pending because the
try {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use assert.rejects(...)?

body: encoder.encode('stream 2'),
const s3 = await clientSession.createBidirectionalStream({
body: encoder.encode('stream 3'),
waitUntilAvailable: true

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the default right?

waitUntilAvailable: false
});
await s4.closed;
await allDone.promise;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

await Promise.all([s4.closed, allDone.promise]);

@@ -42,6 +42,7 @@ const s1 = await clientSession.createUnidirectionalStream({
// Second uni stream is pending (limit = 1).
const s2 = await clientSession.createUnidirectionalStream({
body: encoder.encode('uni 2'),
waitUntilAvailable: true,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If waitUntilAvailable: true is the default we don't need to change all these, right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No you suggested to default it to false.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which matches W3C

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, that was a mistake on my part. Since it returns a promise for the stream, I think it's more ergonomic to wait by default. The Web Transport API impl can easily pass false but I think what most users would likely typically expect is that the promise resolves with the stream when the stream is actually available.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I will change this.

Comment thread doc/api/quic.md Outdated
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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These methods return a promise. While it may fail synchronously internally, it should return a rejected promise and not throw synchronously.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have the C++ command in mind?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or will it be automatically like this through the js side and I do not have to change anything on the c++ side?

Comment thread doc/api/quic.md Outdated
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`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Default should be true I think... just from an ergonomics point of view.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your initial suggestion was in the other direction. W3C WT uses also false.
P.S: I have to leave my dev machine for a while, so I might not update this for a while.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have tried to fix the remaining stuff using the github web ui....

@martenrichter

Copy link
Copy Markdown
Contributor Author

@jasnell I just ping this, was not on the dashboard in the call. It should be almost ready, I will test it at the weekend. (Btw. where is the quic dashboard, I do not find it in this repo)

@jasnell

jasnell commented Sep 5, 2026

Copy link
Copy Markdown
Member

Added to the project board! will try to do a follow up review on monday

@martenrichter

Copy link
Copy Markdown
Contributor Author

@jasnell and where can I find the project board ?

@pimterry

pimterry commented Sep 5, 2026

Copy link
Copy Markdown
Member

It's at https://github.com/orgs/nodejs/projects/17/ @martenrichter, I think it's marked as private so you won't be able to see it though, that's probably the issue. @jasnell is that intentional? Could just be public. I can't change it, it says org owners only.

@martenrichter

Copy link
Copy Markdown
Contributor Author

Yes, I get a 404 .

@martenrichter

Copy link
Copy Markdown
Contributor Author

I am currently testing some tests, that verify, if stream limits are respected. But it does not seem to be the case. I will investigate and report back if this PR is affected or if the problem is with another part of the code.

@martenrichter

Copy link
Copy Markdown
Contributor Author

Well, interesting #createStream in QuicSession is async but has no async operation in it.

If a stream, due to stream limits, is not immediately created, the #createStream promise will return nevertheless.
One can only synchronously poll the pending property or use the session's onstream callback.
I wonder if this is a good api, or if we should instead:
(i) not return immediately for a pending stream and settle the promise once it arrive. Though one could not manipulate the stream then, or
(ii) offer a promise or other callback on the QuicStream to wait asynchronously for materilization of the pending stream.

@pimterry @jasnell what do you think?

@martenrichter

Copy link
Copy Markdown
Contributor Author

Correction: you do not seem to get a pending stream through onstream .

@jasnell

jasnell commented Sep 6, 2026

Copy link
Copy Markdown
Member

The design intent here was to return the QuicStream object as quickly as possible even if the stream itself is still pending to be opened... to be able to set things like error handlers or start queuing up outbound data optimistically. I think maybe the best approach would be ii... offering a promise on the QuicStream that is resolved when the stream is materialized.

@martenrichter

Copy link
Copy Markdown
Contributor Author

Ok, I agree, a separate PR is almost ready, just the last bugs needs to be found.

@martenrichter

Copy link
Copy Markdown
Contributor Author

Added a separate PR #65862 for the other issue.
My webtransport tests for stream limits work fine after these to PRs.

@pimterry

pimterry commented Sep 7, 2026

Copy link
Copy Markdown
Member

Ok, so we're going to make createStream sync once #65862 is in place then? That makes total sense to me, seems cleaner and it's nice to be able to drop a promise step completely.

If we do that, waitUntilAvailable as is doesn't make much sense though, since nothing ever waits at all.

Maybe we should just expose the remaining budget directly, so cases who care about the budget before they open the stream can precisely check it instead? More flexible, and easy to do: just exposing ngtcp2_conn_get_streams_bidi_left2 and ngtcp2_conn_get_streams_uni_left2 to JS, or zero if the session isn't yet open or already closed.

@martenrichter

Copy link
Copy Markdown
Contributor Author

Well the name waitUntilAvailable comes from Webtransport, and yes I assumed, that we were waiting for the stream.

Exposing the budget would be an alternative. But we can't put it in state, as we would also have a caching issue.
I do not have much experinence with speed, would such a call into C++ be expensive? As in this scenario, a caller would have to cross the js to c++ barrier twice.

May be we just rename it to failIfNotAvailable? (Or anything similar, may be a better idea from native speakers.

@pimterry

pimterry commented Sep 7, 2026

Copy link
Copy Markdown
Member

I think the performance cost is roughly the same in either case (querying the budget or passing failIfNotAvailable): either we go into C++ to create the stream and then back to JS to fail, or we go into C++ to query the budget and then back to JS to report it. In theory we could cache the budget though, if we need to, by updating it whenever a stream opens or MAX_STREAMS frames arrive (extend_max_local_streams_bidi/uni callbacks).

Different is mostly API UX, so I guess it depends how people will use this. Exposing the budget gives you strictly more information (maybe you want to create 2 streams, and know that you have enough budget for both beforehand) but it's slightly less convenient to check it and throw yourself, instead of just passing failIfNotAvailable: true for the very simple case. I'm not sure how people would actually want to use this though, any idea?

@martenrichter

Copy link
Copy Markdown
Contributor Author

I am also not sure. These last two issues just came up, when I tried to implement W3C webtransport.
Though I know many people use my lib for games and open constantly streams and close them. (I do audio/video transmission, but I reuse streams, as early UA implementation had issues with properly closing streams). Though waitUntilAvailable is a newer option. (I think it was established, if I remember correctly, as Chromium and Firefox behaved differently on creating streams, and in the end they added an option for both behaviors).

@trivikr trivikr added the quic Issues and PRs related to the QUIC transport implementation. label Sep 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c++ Issues and PRs that require attention from people who are familiar with C++. lib / src Issues and PRs involving general changes in the lib/ or src/ directories. needs-ci PRs that need a full CI run. quic Issues and PRs related to the QUIC transport implementation.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

quic: Add mechanism similar to waitUntilAvailable to create(Bi|Uni)directionalStream

5 participants