Skip to content

Commit 7b062ca

Browse files
committed
quic: refactor qlog handling
Because of the timing of qlog events emitted by ngtcp2, it becomes difficult to handle those as events on the QuicSession object because the final qlog entry is not emitted until the ngtcp2_conn is freed, which can occur when the object is being garbage collected (meaning, we a: can't call out to javascript and b: don't have an object we can use to emit the event). This refactors it into a QLogStream object that allows the qlog data to be piped out using a separate Readable stream. PR-URL: #34160 Reviewed-By: Anna Henningsen <anna@addaleax.net>
1 parent e4d369e commit 7b062ca

File tree

9 files changed

+257
-64
lines changed

9 files changed

+257
-64
lines changed

doc/api/quic.md

+28-17
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ added: REPLACEME
274274
* `maxStatelessResetsPerHost` {number} The maximum number of stateless
275275
resets that the `QuicSocket` is permitted to send per remote host.
276276
Default: `10`.
277-
* `qlog` {boolean} Whether to emit ['qlog'][] events for incoming sessions.
277+
* `qlog` {boolean} Whether to enable ['qlog'][] for incoming sessions.
278278
(For outgoing client sessions, set `client.qlog`.) Default: `false`.
279279
* `retryTokenTimeout` {number} The maximum number of *seconds* for retry token
280280
validation. Default: `10` seconds.
@@ -633,20 +633,6 @@ The callback will be invoked with three arguments:
633633

634634
The `'pathValidation'` event will be emitted multiple times.
635635

636-
#### Event: `'qlog'`
637-
<!-- YAML
638-
added: REPLACEME
639-
-->
640-
641-
* `jsonChunk` {string} A JSON fragment.
642-
643-
Emitted if the `qlog: true` option was passed to `quicsocket.connect()` or
644-
`net.createQuicSocket()` functions.
645-
646-
The argument is a JSON fragment according to the [qlog standard][].
647-
648-
The `'qlog'` event will be emitted multiple times.
649-
650636
#### Event: `'secure'`
651637
<!-- YAML
652638
added: REPLACEME
@@ -1044,6 +1030,19 @@ added: REPLACEME
10441030
A `BigInt` representing the total number of `QuicStreams` initiated by the
10451031
connected peer.
10461032

1033+
#### quicsession.qlog
1034+
<!-- YAML
1035+
added: REPLACEME
1036+
-->
1037+
1038+
* Type: {stream.Readable}
1039+
1040+
If `qlog` support is enabled for `QuicSession`, the `quicsession.qlog` property
1041+
provides a [`stream.Readable`][] that may be used to access the `qlog` event
1042+
data according to the [qlog standard][]. For client `QuicSessions`, the
1043+
`quicsession.qlog` property will be `undefined` untilt the `'qlog'` event
1044+
is emitted.
1045+
10471046
#### quicsession.remoteAddress
10481047
<!-- YAML
10491048
added: REPLACEME
@@ -1183,6 +1182,17 @@ The `sessionTicket` and `remoteTransportParams` are useful when creating a new
11831182

11841183
The `'sessionTicket'` event may be emitted multiple times.
11851184

1185+
#### Event: `'qlog'`
1186+
<!-- YAML
1187+
added: REPLACEME
1188+
-->
1189+
1190+
The `'qlog'` event is emitted when the `QuicClientSession` is ready to begin
1191+
providing `qlog` event data. The callback is invoked with a single argument:
1192+
1193+
* `qlog` {stream.Readable} A [`stream.Readable`][] that is also available using
1194+
the `quicsession.qlog` property.
1195+
11861196
#### Event: `'usePreferredAddress'`
11871197
<!-- YAML
11881198
added: REPLACEME
@@ -1573,7 +1583,7 @@ added: REPLACEME
15731583
transport parameters from a previously established session. These would
15741584
have been provided as part of the `'sessionTicket'` event on a previous
15751585
`QuicClientSession` object.
1576-
* `qlog` {boolean} Whether to emit ['qlog'][] events for this session.
1586+
* `qlog` {boolean} Whether to enable ['qlog'][] for this session.
15771587
Default: `false`.
15781588
* `requestOCSP` {boolean} If `true`, specifies that the OCSP status request
15791589
extension will be added to the client hello and an `'OCSPResponse'` event
@@ -2278,6 +2288,7 @@ added: REPLACEME
22782288
Set to `true` if the `QuicStream` is unidirectional.
22792289

22802290
[`crypto.getCurves()`]: crypto.html#crypto_crypto_getcurves
2291+
[`stream.Readable`]: #stream_class_stream_readable
22812292
[`tls.DEFAULT_ECDH_CURVE`]: #tls_tls_default_ecdh_curve
22822293
[`tls.getCiphers()`]: tls.html#tls_tls_getciphers
22832294
[ALPN]: https://tools.ietf.org/html/rfc7301
@@ -2286,5 +2297,5 @@ Set to `true` if the `QuicStream` is unidirectional.
22862297
[modifying the default cipher suite]: tls.html#tls_modifying_the_default_tls_cipher_suite
22872298
[OpenSSL Options]: crypto.html#crypto_openssl_options
22882299
[Perfect Forward Secrecy]: #tls_perfect_forward_secrecy
2289-
['qlog']: #quic_event_qlog
2300+
['qlog']: #quic_quicsession_qlog
22902301
[qlog standard]: https://tools.ietf.org/id/draft-marx-qlog-event-definitions-quic-h3-00.html

lib/internal/quic/core.js

+29-30
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ const {
4040
validateCreateSecureContextOptions,
4141
validateQuicSocketConnectOptions,
4242
QuicSessionSharedState,
43+
QLogStream,
4344
} = require('internal/quic/util');
4445
const util = require('util');
4546
const assert = require('internal/assert');
@@ -236,6 +237,7 @@ const kRemoveSession = Symbol('kRemove');
236237
const kRemoveStream = Symbol('kRemoveStream');
237238
const kServerBusy = Symbol('kServerBusy');
238239
const kSetHandle = Symbol('kSetHandle');
240+
const kSetQLogStream = Symbol('kSetQLogStream');
239241
const kSetSocket = Symbol('kSetSocket');
240242
const kSetSocketAfterBind = Symbol('kSetSocketAfterBind');
241243
const kStartFilePipe = Symbol('kStartFilePipe');
@@ -423,37 +425,17 @@ function onSessionUsePreferredAddress(address, port, family) {
423425
});
424426
}
425427

426-
// Called by the C++ internals to emit a QLog record.
427-
function onSessionQlog(str) {
428-
if (this.qlogBuffer === undefined) this.qlogBuffer = '';
429-
428+
// Called by the C++ internals to emit a QLog record. This can
429+
// be called before the QuicSession has been fully initialized,
430+
// in which case we store a reference and defer emitting the
431+
// qlog event until after we're initialized.
432+
function onSessionQlog(handle) {
430433
const session = this[owner_symbol];
431-
432-
if (session && session.listenerCount('qlog') > 0) {
433-
// Emit this chunk along with any previously buffered data.
434-
str = this.qlogBuffer + str;
435-
this.qlogBuffer = '';
436-
if (str === '') return;
437-
session.emit('qlog', str);
438-
} else {
439-
// Buffer the data until both the JS session object and a listener
440-
// become available.
441-
this.qlogBuffer += str;
442-
443-
if (!session || this.waitingForQlogListener) return;
444-
this.waitingForQlogListener = true;
445-
446-
function onNewListener(ev) {
447-
if (ev === 'qlog') {
448-
session.removeListener('newListener', onNewListener);
449-
process.nextTick(() => {
450-
onSessionQlog.call(this, '');
451-
});
452-
}
453-
}
454-
455-
session.on('newListener', onNewListener);
456-
}
434+
const stream = new QLogStream(handle);
435+
if (session)
436+
session[kSetQLogStream](stream);
437+
else
438+
this.qlogStream = stream;
457439
}
458440

459441
// Called by the C++ internals when a client QuicSession receives
@@ -1631,6 +1613,7 @@ class QuicSession extends EventEmitter {
16311613
highWaterMark: undefined,
16321614
defaultEncoding: undefined,
16331615
state: undefined,
1616+
qlogStream: undefined,
16341617
};
16351618

16361619
constructor(socket, options) {
@@ -1662,6 +1645,14 @@ class QuicSession extends EventEmitter {
16621645
};
16631646
}
16641647

1648+
[kSetQLogStream](stream) {
1649+
const state = this[kInternalState];
1650+
state.qlogStream = stream;
1651+
process.nextTick(() => {
1652+
this.emit('qlog', state.qlogStream);
1653+
});
1654+
}
1655+
16651656
// Sets the internal handle for the QuicSession instance. For
16661657
// server QuicSessions, this is called immediately as the
16671658
// handle is created before the QuicServerSession JS object.
@@ -1676,6 +1667,10 @@ class QuicSession extends EventEmitter {
16761667
state.state = new QuicSessionSharedState(handle.state);
16771668
state.handshakeAckHistogram = new Histogram(handle.ack);
16781669
state.handshakeContinuationHistogram = new Histogram(handle.rate);
1670+
if (handle.qlogStream !== undefined) {
1671+
this[kSetQLogStream](handle.qlogStream);
1672+
handle.qlogStream = undefined;
1673+
}
16791674
} else {
16801675
if (state.handshakeAckHistogram)
16811676
state.handshakeAckHistogram[kDestroyHistogram]();
@@ -1934,6 +1929,10 @@ class QuicSession extends EventEmitter {
19341929
return { bidi, uni };
19351930
}
19361931

1932+
get qlog() {
1933+
return this[kInternalState].qlogStream;
1934+
}
1935+
19371936
get address() {
19381937
return this[kInternalState].socket?.address || {};
19391938
}

lib/internal/quic/util.js

+38
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,24 @@ const {
1515
},
1616
} = require('internal/errors');
1717

18+
const {
19+
symbols: {
20+
owner_symbol,
21+
},
22+
} = require('internal/async_hooks');
23+
1824
const {
1925
kHandle,
2026
} = require('internal/stream_base_commons');
2127

2228
const endianness = require('os').endianness();
2329

30+
const { Readable } = require('stream');
31+
const {
32+
kUpdateTimer,
33+
onStreamRead,
34+
} = require('internal/stream_base_commons');
35+
2436
const assert = require('internal/assert');
2537
assert(process.versions.ngtcp2 !== undefined);
2638

@@ -885,6 +897,31 @@ class QuicSessionSharedState {
885897
}
886898
}
887899

900+
class QLogStream extends Readable {
901+
constructor(handle) {
902+
super({ autoDestroy: true });
903+
this[kHandle] = handle;
904+
handle[owner_symbol] = this;
905+
handle.onread = onStreamRead;
906+
}
907+
908+
_read() {
909+
if (this[kHandle])
910+
this[kHandle].readStart();
911+
}
912+
913+
_destroy() {
914+
// Release the references on the handle so that
915+
// it can be garbage collected.
916+
this[kHandle][owner_symbol] = undefined;
917+
this[kHandle] = undefined;
918+
}
919+
920+
[kUpdateTimer]() {
921+
// Does nothing
922+
}
923+
}
924+
888925
module.exports = {
889926
getAllowUnauthorized,
890927
getSocketType,
@@ -903,4 +940,5 @@ module.exports = {
903940
validateCreateSecureContextOptions,
904941
validateQuicSocketConnectOptions,
905942
QuicSessionSharedState,
943+
QLogStream,
906944
};

src/async_wrap.h

+1
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ namespace node {
5959
V(PROCESSWRAP) \
6060
V(PROMISE) \
6161
V(QUERYWRAP) \
62+
V(QLOGSTREAM) \
6263
V(QUICCLIENTSESSION) \
6364
V(QUICSERVERSESSION) \
6465
V(QUICSENDWRAP) \

src/env.h

+1
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,7 @@ constexpr size_t kFsStatsBufferLength =
441441
V(secure_context_constructor_template, v8::FunctionTemplate) \
442442
V(shutdown_wrap_template, v8::ObjectTemplate) \
443443
V(streambaseoutputstream_constructor_template, v8::ObjectTemplate) \
444+
V(qlogoutputstream_constructor_template, v8::ObjectTemplate) \
444445
V(tcp_constructor_template, v8::FunctionTemplate) \
445446
V(tty_constructor_template, v8::FunctionTemplate) \
446447
V(write_wrap_template, v8::ObjectTemplate) \

0 commit comments

Comments
 (0)