Skip to content

Commit f0af79e

Browse files
committed
quic: apply multiple quality improvements to js side
1 parent 988cc03 commit f0af79e

File tree

5 files changed

+81
-118
lines changed

5 files changed

+81
-118
lines changed

doc/api/quic.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -815,7 +815,7 @@ added: v23.8.0
815815

816816
* Type: {bigint}
817817

818-
### `sessionStats.maxBytesInFlights`
818+
### `sessionStats.maxBytesInFlight`
819819

820820
<!-- YAML
821821
added: v23.8.0

lib/internal/quic/quic.js

Lines changed: 69 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -637,6 +637,51 @@ function validateBody(body) {
637637
], body);
638638
}
639639

640+
/**
641+
* Parses an alternating [name, value, name, value, ...] array from C++
642+
* into a plain header object. Multi-value headers become arrays.
643+
* @param {string[]} pairs
644+
* @returns {object}
645+
*/
646+
function parseHeaderPairs(pairs) {
647+
assert(ArrayIsArray(pairs));
648+
assert(pairs.length % 2 === 0);
649+
const block = { __proto__: null };
650+
for (let n = 0; n + 1 < pairs.length; n += 2) {
651+
if (block[pairs[n]] !== undefined) {
652+
block[pairs[n]] = [block[pairs[n]], pairs[n + 1]];
653+
} else {
654+
block[pairs[n]] = pairs[n + 1];
655+
}
656+
}
657+
return block;
658+
}
659+
660+
/**
661+
* Applies session and stream callbacks from an options object to a session.
662+
* @param {QuicSession} session
663+
* @param {object} cbs
664+
*/
665+
function applyCallbacks(session, cbs) {
666+
if (cbs.onstream) session.onstream = cbs.onstream;
667+
if (cbs.ondatagram) session.ondatagram = cbs.ondatagram;
668+
if (cbs.ondatagramstatus) session.ondatagramstatus = cbs.ondatagramstatus;
669+
if (cbs.onpathvalidation) session.onpathvalidation = cbs.onpathvalidation;
670+
if (cbs.onsessionticket) session.onsessionticket = cbs.onsessionticket;
671+
if (cbs.onversionnegotiation) session.onversionnegotiation = cbs.onversionnegotiation;
672+
if (cbs.onhandshake) session.onhandshake = cbs.onhandshake;
673+
if (cbs.onnewtoken) session.onnewtoken = cbs.onnewtoken;
674+
if (cbs.onorigin) session.onorigin = cbs.onorigin;
675+
if (cbs.onheaders || cbs.ontrailers || cbs.onwanttrailers) {
676+
session[kStreamCallbacks] = {
677+
__proto__: null,
678+
onheaders: cbs.onheaders,
679+
ontrailers: cbs.ontrailers,
680+
onwanttrailers: cbs.onwanttrailers,
681+
};
682+
}
683+
}
684+
640685
// Functions used specifically for internal or assertion purposes only.
641686
let getQuicStreamState;
642687
let getQuicSessionState;
@@ -713,6 +758,13 @@ class QuicStream {
713758
}
714759
}
715760

761+
#assertHeadersSupported() {
762+
if (getQuicSessionState(this.#session).headersSupported === 2) {
763+
throw new ERR_INVALID_STATE(
764+
'The negotiated QUIC application protocol does not support headers');
765+
}
766+
}
767+
716768
/**
717769
* @param {symbol} privateSymbol
718770
* @param {object} handle
@@ -810,10 +862,7 @@ class QuicStream {
810862
this.#onheaders = undefined;
811863
this.#state[kWantsHeaders] = false;
812864
} else {
813-
if (getQuicSessionState(this.#session).headersSupported === 2) {
814-
throw new ERR_INVALID_STATE(
815-
'The negotiated QUIC application protocol does not support headers');
816-
}
865+
this.#assertHeadersSupported();
817866
validateFunction(fn, 'onheaders');
818867
this.#onheaders = FunctionPrototypeBind(fn, this);
819868
this.#state[kWantsHeaders] = true;
@@ -832,10 +881,7 @@ class QuicStream {
832881
this.#ontrailers = undefined;
833882
this.#state[kWantsTrailers] = false;
834883
} else {
835-
if (getQuicSessionState(this.#session).headersSupported === 2) {
836-
throw new ERR_INVALID_STATE(
837-
'The negotiated QUIC application protocol does not support headers');
838-
}
884+
this.#assertHeadersSupported();
839885
validateFunction(fn, 'ontrailers');
840886
this.#ontrailers = FunctionPrototypeBind(fn, this);
841887
this.#state[kWantsTrailers] = true;
@@ -853,10 +899,7 @@ class QuicStream {
853899
if (fn === undefined) {
854900
this.#onwanttrailers = undefined;
855901
} else {
856-
if (getQuicSessionState(this.#session).headersSupported === 2) {
857-
throw new ERR_INVALID_STATE(
858-
'The negotiated QUIC application protocol does not support headers');
859-
}
902+
this.#assertHeadersSupported();
860903
validateFunction(fn, 'onwanttrailers');
861904
this.#onwanttrailers = FunctionPrototypeBind(fn, this);
862905
}
@@ -940,7 +983,7 @@ class QuicStream {
940983

941984
/**
942985
* True if the stream has been destroyed.
943-
* @returns {boolean}
986+
* @type {boolean}
944987
*/
945988
get destroyed() {
946989
QuicStream.#assertIsQuicStream(this);
@@ -1186,18 +1229,7 @@ class QuicStream {
11861229

11871230
[kHeaders](headers, kind) {
11881231
assert(this.#onheaders, 'Unexpected stream headers event');
1189-
assert(ArrayIsArray(headers));
1190-
assert(headers.length % 2 === 0);
1191-
const block = {
1192-
__proto__: null,
1193-
};
1194-
for (let n = 0; n + 1 < headers.length; n += 2) {
1195-
if (block[headers[n]] !== undefined) {
1196-
block[headers[n]] = [block[headers[n]], headers[n + 1]];
1197-
} else {
1198-
block[headers[n]] = headers[n + 1];
1199-
}
1200-
}
1232+
const block = parseHeaderPairs(headers);
12011233

12021234
// Buffer initial headers so stream.headers returns them.
12031235
if (kind === 'initial' && this.#headers === undefined) {
@@ -1213,17 +1245,7 @@ class QuicStream {
12131245
// If we received trailers from the peer, dispatch them.
12141246
if (headers !== undefined) {
12151247
if (this.#ontrailers) {
1216-
assert(ArrayIsArray(headers));
1217-
assert(headers.length % 2 === 0);
1218-
const block = { __proto__: null };
1219-
for (let n = 0; n + 1 < headers.length; n += 2) {
1220-
if (block[headers[n]] !== undefined) {
1221-
block[headers[n]] = [block[headers[n]], headers[n + 1]];
1222-
} else {
1223-
block[headers[n]] = headers[n + 1];
1224-
}
1225-
}
1226-
this.#ontrailers(block);
1248+
this.#ontrailers(parseHeaderPairs(headers));
12271249
}
12281250
return;
12291251
}
@@ -1248,7 +1270,7 @@ class QuicStream {
12481270
depth: options.depth == null ? null : options.depth - 1,
12491271
};
12501272

1251-
return `Stream ${inspect({
1273+
return `QuicStream ${inspect({
12521274
__proto__: null,
12531275
id: this.id,
12541276
direction: this.direction,
@@ -1792,7 +1814,7 @@ class QuicSession {
17921814

17931815
debug('gracefully closing the session');
17941816

1795-
this.#handle?.gracefulClose();
1817+
this.#handle.gracefulClose();
17961818
if (onSessionClosingChannel.hasSubscribers) {
17971819
onSessionClosingChannel.publish({
17981820
session: this,
@@ -1890,7 +1912,6 @@ class QuicSession {
18901912
this.#peerCertificate = undefined;
18911913
this.#ephemeralKeyInfo = undefined;
18921914

1893-
18941915
// Destroy the underlying C++ handle
18951916
this.#handle.destroy();
18961917
this.#handle = undefined;
@@ -1920,29 +1941,20 @@ class QuicSession {
19201941
debug('finishing closing the session with an error', errorType, code, reason);
19211942
// Otherwise, errorType indicates the type of error that occurred, code indicates
19221943
// the specific error, and reason is an optional string describing the error.
1944+
// code !== 0n here (the early return above handles code === 0n)
19231945
switch (errorType) {
19241946
case 0: /* Transport Error */
1925-
if (code === 0n) {
1926-
this.destroy();
1927-
} else {
1928-
this.destroy(new ERR_QUIC_TRANSPORT_ERROR(code, reason));
1929-
}
1947+
this.destroy(new ERR_QUIC_TRANSPORT_ERROR(code, reason));
19301948
break;
19311949
case 1: /* Application Error */
1932-
if (code === 0n) {
1933-
this.destroy();
1934-
} else {
1935-
this.destroy(new ERR_QUIC_APPLICATION_ERROR(code, reason));
1936-
}
1950+
this.destroy(new ERR_QUIC_APPLICATION_ERROR(code, reason));
19371951
break;
19381952
case 2: /* Version Negotiation Error */
19391953
this.destroy(new ERR_QUIC_VERSION_NEGOTIATION_ERROR());
19401954
break;
1941-
case 3: /* Idle close */ {
1942-
// An idle close is not really an error. We can just destroy.
1955+
case 3: /* Idle close */
19431956
this.destroy();
19441957
break;
1945-
}
19461958
}
19471959
}
19481960

@@ -2069,9 +2081,6 @@ class QuicSession {
20692081
if (this.#onversionnegotiation) {
20702082
this.#onversionnegotiation(version, requestedVersions, supportedVersions);
20712083
}
2072-
// Version negotiation is always a fatal event - the session must be
2073-
// destroyed regardless of whether the callback is set.
2074-
this.destroy(new ERR_QUIC_VERSION_NEGOTIATION_ERROR());
20752084
if (onSessionVersionNegotiationChannel.hasSubscribers) {
20762085
onSessionVersionNegotiationChannel.publish({
20772086
__proto__: null,
@@ -2081,6 +2090,9 @@ class QuicSession {
20812090
session: this,
20822091
});
20832092
}
2093+
// Version negotiation is always a fatal event - the session must be
2094+
// destroyed regardless of whether the callback is set.
2095+
this.destroy(new ERR_QUIC_VERSION_NEGOTIATION_ERROR());
20842096
}
20852097

20862098
/**
@@ -2515,19 +2527,6 @@ class QuicEndpoint {
25152527
validateObject(options, 'options');
25162528
const {
25172529
sessionTicket,
2518-
onstream,
2519-
ondatagram,
2520-
ondatagramstatus,
2521-
onpathvalidation,
2522-
onsessionticket,
2523-
onversionnegotiation,
2524-
onhandshake,
2525-
onnewtoken,
2526-
onorigin,
2527-
// Stream-level callbacks.
2528-
onheaders,
2529-
ontrailers,
2530-
onwanttrailers,
25312530
...rest
25322531
} = options;
25332532

@@ -2539,24 +2538,7 @@ class QuicEndpoint {
25392538
const session = this.#newSession(handle);
25402539
// Set callbacks before any async work to avoid missing events
25412540
// that fire during or immediately after the handshake.
2542-
if (onstream) session.onstream = onstream;
2543-
if (ondatagram) session.ondatagram = ondatagram;
2544-
if (ondatagramstatus) session.ondatagramstatus = ondatagramstatus;
2545-
if (onpathvalidation) session.onpathvalidation = onpathvalidation;
2546-
if (onsessionticket) session.onsessionticket = onsessionticket;
2547-
if (onversionnegotiation) session.onversionnegotiation = onversionnegotiation;
2548-
if (onhandshake) session.onhandshake = onhandshake;
2549-
if (onnewtoken) session.onnewtoken = onnewtoken;
2550-
if (onorigin) session.onorigin = onorigin;
2551-
// Store stream-level callbacks for application to client-created streams.
2552-
if (onheaders || ontrailers || onwanttrailers) {
2553-
session[kStreamCallbacks] = {
2554-
__proto__: null,
2555-
onheaders,
2556-
ontrailers,
2557-
onwanttrailers,
2558-
};
2559-
}
2541+
applyCallbacks(session, options);
25602542
return session;
25612543
}
25622544

@@ -2735,26 +2717,7 @@ class QuicEndpoint {
27352717
// the onsession callback, to avoid missing events that fire
27362718
// during or immediately after the handshake.
27372719
if (this.#sessionCallbacks) {
2738-
const cbs = this.#sessionCallbacks;
2739-
if (cbs.onstream) session.onstream = cbs.onstream;
2740-
if (cbs.ondatagram) session.ondatagram = cbs.ondatagram;
2741-
if (cbs.ondatagramstatus) session.ondatagramstatus = cbs.ondatagramstatus;
2742-
if (cbs.onpathvalidation) session.onpathvalidation = cbs.onpathvalidation;
2743-
if (cbs.onsessionticket) session.onsessionticket = cbs.onsessionticket;
2744-
if (cbs.onversionnegotiation) session.onversionnegotiation = cbs.onversionnegotiation;
2745-
if (cbs.onhandshake) session.onhandshake = cbs.onhandshake;
2746-
if (cbs.onnewtoken) session.onnewtoken = cbs.onnewtoken;
2747-
if (cbs.onorigin) session.onorigin = cbs.onorigin;
2748-
// Store stream-level callbacks on the session for application
2749-
// to each new incoming stream.
2750-
if (cbs.onheaders || cbs.ontrailers || cbs.onwanttrailers) {
2751-
session[kStreamCallbacks] = {
2752-
__proto__: null,
2753-
onheaders: cbs.onheaders,
2754-
ontrailers: cbs.ontrailers,
2755-
onwanttrailers: cbs.onwanttrailers,
2756-
};
2757-
}
2720+
applyCallbacks(session, this.#sessionCallbacks);
27582721
}
27592722
if (onEndpointServerSessionChannel.hasSubscribers) {
27602723
onEndpointServerSessionChannel.publish({

lib/internal/quic/state.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,8 +185,8 @@ class QuicEndpointState {
185185
}
186186

187187
/**
188-
* The number of underlying callbacks that are pending. If the session
189-
* is closing, these are the number of callbacks that the session is
188+
* The number of underlying callbacks that are pending. If the endpoint
189+
* is closing, these are the number of callbacks that the endpoint is
190190
* waiting on before it can be closed.
191191
* @type {bigint}
192192
*/

lib/internal/quic/stats.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ class QuicEndpointStats {
302302
* True if this QuicEndpointStats object is still connected to the underlying
303303
* Endpoint stats source. If this returns false, then the stats object is
304304
* no longer being updated and should be considered stale.
305-
* @returns {boolean}
305+
* @type {boolean}
306306
*/
307307
get isConnected() {
308308
return !this.#disconnected;
@@ -324,7 +324,7 @@ class QuicSessionStats {
324324

325325
/**
326326
* @param {symbol} privateSymbol
327-
* @param {BigUint64Array} buffer
327+
* @param {ArrayBuffer} buffer
328328
*/
329329
constructor(privateSymbol, buffer) {
330330
// We use the kPrivateConstructor symbol to restrict the ability to
@@ -384,7 +384,7 @@ class QuicSessionStats {
384384
}
385385

386386
/** @type {bigint} */
387-
get maxBytesInFlights() {
387+
get maxBytesInFlight() {
388388
return this.#handle[IDX_STATS_SESSION_MAX_BYTES_IN_FLIGHT];
389389
}
390390

@@ -499,7 +499,7 @@ class QuicSessionStats {
499499
bidiOutStreamCount: `${this.bidiOutStreamCount}`,
500500
uniInStreamCount: `${this.uniInStreamCount}`,
501501
uniOutStreamCount: `${this.uniOutStreamCount}`,
502-
maxBytesInFlights: `${this.maxBytesInFlights}`,
502+
maxBytesInFlight: `${this.maxBytesInFlight}`,
503503
bytesInFlight: `${this.bytesInFlight}`,
504504
blockCount: `${this.blockCount}`,
505505
cwnd: `${this.cwnd}`,
@@ -544,7 +544,7 @@ class QuicSessionStats {
544544
bidiOutStreamCount: this.bidiOutStreamCount,
545545
uniInStreamCount: this.uniInStreamCount,
546546
uniOutStreamCount: this.uniOutStreamCount,
547-
maxBytesInFlights: this.maxBytesInFlights,
547+
maxBytesInFlight: this.maxBytesInFlight,
548548
bytesInFlight: this.bytesInFlight,
549549
blockCount: this.blockCount,
550550
cwnd: this.cwnd,
@@ -572,7 +572,7 @@ class QuicSessionStats {
572572
* True if this QuicSessionStats object is still connected to the underlying
573573
* Session stats source. If this returns false, then the stats object is
574574
* no longer being updated and should be considered stale.
575-
* @returns {boolean}
575+
* @type {boolean}
576576
*/
577577
get isConnected() {
578578
return !this.#disconnected;
@@ -697,7 +697,7 @@ class QuicStreamStats {
697697
depth: options.depth == null ? null : options.depth - 1,
698698
};
699699

700-
return `StreamStats ${inspect({
700+
return `QuicStreamStats ${inspect({
701701
connected: this.isConnected,
702702
createdAt: this.createdAt,
703703
openedAt: this.openedAt,
@@ -717,7 +717,7 @@ class QuicStreamStats {
717717
* True if this QuicStreamStats object is still connected to the underlying
718718
* Stream stats source. If this returns false, then the stats object is
719719
* no longer being updated and should be considered stale.
720-
* @returns {boolean}
720+
* @type {boolean}
721721
*/
722722
get isConnected() {
723723
return !this.#disconnected;

0 commit comments

Comments
 (0)