-
Notifications
You must be signed in to change notification settings - Fork 30k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
quic: more implementation #56328
base: main
Are you sure you want to change the base?
quic: more implementation #56328
Conversation
Signed-off-by: James M Snell <jasnell@gmail.com>
Signed-off-by: James M Snell <jasnell@gmail.com>
Review requested:
|
This comment was marked as outdated.
This comment was marked as outdated.
4c96c67
to
bfaf60f
Compare
This comment was marked as outdated.
This comment was marked as outdated.
bfaf60f
to
c90a6d7
Compare
This comment was marked as outdated.
This comment was marked as outdated.
c90a6d7
to
0175f17
Compare
0175f17
to
2570ff3
Compare
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #56328 +/- ##
==========================================
+ Coverage 88.54% 88.67% +0.13%
==========================================
Files 657 659 +2
Lines 190393 191099 +706
Branches 36552 36475 -77
==========================================
+ Hits 168582 169457 +875
+ Misses 14998 14452 -546
- Partials 6813 7190 +377
|
This is very exciting! Nice work @jasnell, it's great to see the progress here. Some thoughts: It feels like this API is sort-of upside-down, compared to others we have. Elsewhere ( There's also no representation of a 'server' in this API. There's only the endpoint itself (not quite the same). This means there's no good place to hang APIs to manage the state tied to the listening process - e.g. there seems to be no 'stop listening' API right now other than closing the endpoint completely (which also kills client connections), and there's no way to independently listen to any server events other than the initially registered callback. I suspect as this API develops, not having a server representation in the model will become inconvenient. I think initially users will reasonably expect something with roughly the shape & semantics of the simple example above: import { connect } from 'node:quic';
const client = connect('123.123.123.123:8888'); // <-- Implicit create an endpoint
client.openUnidirectionalStream({ body: Buffer.from('hello there'); });
await client.close(); And I also think something roughly equivalent applies on the server-side too: import { createServer } from 'node:quic';
const keys = getKeysSomehow();
const certs = getCertsSomehow();
const server = createServer({ keys, certs }, (session) => {
session.onstream = (stream) => {
stream.setOutbound(Buffer.from('hello world'));
stream.closed.then(() => {
session.close();
});
readStreamSomehow(stream);
};
});
await server.listen('123.123.123.123:8888'); // <-- Implicit create an endpoint
// ...
await server.close(); Supporting advanced use cases too is good but I really think a lot of people are going to come to this expecting that kind of API to be available, and making that possible will be hugely helpful. That's not to say that the endpoint model doesn't add something, it's just that AFAICT for the most common use cases it'll be slightly confusing extra complexity initially. Do we have any good examples of when & how direct endpoint control will be useful for users? How common is the use case where sharing one endpoint between multiple client connections and/or a server is important? For cases that do need endpoint control, I'd suggest exposing that optionally but flipping the API, so that an endpoint becomes a parameter of clients/servers rather than being the source of them (similar to the last example in the description). That means for advanced use cases, this would look (very roughly) like: import { QuicEndpoint, connect, createServer } from 'node:quic';
const endpoint = new QuicEndpoint();
const client = connect('123.123.123.123:8888', { endpoint });
client.openUnidirectionalStream({ body: Buffer.from('hello there'); });
await client.close();
const server = createServer();
server.listen('123.123.123.123:8888', { endpoint }); The internal endpoint model remains the same I think, this is intended mostly as a shift in how that's exposed and where the methods go, with implicit endpoint creation whenever it's not specified. Regarding closing endpoints, I think in the simple case users definitely won't expect to close this themselves (because the endpoint is invisible) so tidy auto-closing is unavoidable. Even in the advanced case, I can see reasons to want both auto or manual endpoint closing. How about:
|
There's a lot more to do to get things fully working but this makes more incremental progress.
Several key bits:
--experimental-quic
CLI flag and thenode:quic
module behind it.test-quic-handshake
for a basic idea of the current state of the API. It's still rough but it's enough to start getting feedback.Key question for reviewers: Looking at the API documented in quic.md and demonstrated in test-quic-handshake, what API do you want to see here? This is focusing ONLY on the QUIC API at this point and does NOT touch on the http3 API.
For the server-side, the API is essentially:
For the client side it is essentially:
The API still feels rather awkward. For review, contrast with what Deno is doing here: https://github.com/denoland/deno/pull/21942/files#diff-1645ba9a2a2aac4440671da62b81ab9a723f50faffff0d0e8d016a1f991961a3
At this point there are bugs in the implementation still, yes, but let's focus on the API design at the moment. Please focus review comments on that.
A couple of bits to consider for the reivew...
In this API design, the
QuicEndpoint
represents the local binding the local UDP port. It supports being both a server and client at the same time. Use can be a bit awkward though so we need to carefully review. Specifically, a singleQuicEndpoint
can support any number ofQuicSession
s (both client and server at the same time). This gives maximum flexibility but means a more complicated API. For example, let's take a closer look at the client side example:The key question here is: does it make sense to expose
QuicEndpoint
like this? If we look at the Deno API, as an alternative, we see something like:In Deno's API, they've effectively hidden
QuicEndpoint
under their notion ofQuicConn
. While it might be the case that the same underlying UDP port is used when everconnectQuic(...)
is called, if it is that ends up being an internal implementation detail and not something that is exposed to users. Which approach is better? ExposingQuicEndpoint
the way that I am means we have more flexibility but also more complexity that may not be worth it?Alternatively, we could go with a simplified API where every call to
connect(...)
uses a separateQuicEndpoint
, so, for instance:We could still allow for direct reuse of
QuicEndpoint
in this model by allowing aQuicEndpoint
instance to be passed as an option toconnect(...)
, like:But the question here, whenever client connections are sharing a single endpoint is (a) when should the endpoint be closed, (b) should it ever be closed automatically?, (c) should it be unref'd so that if there are no active sessions but the client-side endpoint has not yet been closed, should it keep the node.js process from exiting?
Basically, looking for feedback on the API design so if you have other ideas for what color to paint the bikeshed, now is the time to discuss it.
@Qard @mcollina @anonrig @nodejs/quic