Skip to content

Commit f9c2245

Browse files
committed
quic: refactor QuicSession close/destroy flow
PR-URL: #34160 Reviewed-By: Anna Henningsen <anna@addaleax.net>
1 parent f7510ca commit f9c2245

File tree

5 files changed

+174
-229
lines changed

5 files changed

+174
-229
lines changed

lib/internal/quic/core.js

+36-85
Original file line numberDiff line numberDiff line change
@@ -287,24 +287,10 @@ function onSessionReady(handle) {
287287
process.nextTick(emit.bind(socket, 'session', session));
288288
}
289289

290-
// Called when the session needs to be closed and destroyed.
291-
// If silent is true, then the session is going to be closed
292-
// immediately without sending any CONNECTION_CLOSE to the
293-
// connected peer. If silent is false, a CONNECTION_CLOSE
294-
// is going to be sent to the peer.
290+
// Called when the C++ QuicSession::Close() method has been called.
291+
// Synchronously cleanup and destroy the JavaScript QuicSession.
295292
function onSessionClose(code, family, silent, statelessReset) {
296-
if (this[owner_symbol]) {
297-
if (silent) {
298-
this[owner_symbol][kDestroy](statelessReset, family, code);
299-
} else {
300-
this[owner_symbol][kClose](family, code);
301-
}
302-
return;
303-
}
304-
// When there's no owner_symbol, the session was closed
305-
// before it could be fully set up. Just immediately
306-
// close everything down on the native side.
307-
this.destroy(code, family);
293+
this[owner_symbol][kDestroy](code, family, silent, statelessReset);
308294
}
309295

310296
// Called by the C++ internals when a QuicSession has been destroyed.
@@ -1654,6 +1640,7 @@ class QuicSession extends EventEmitter {
16541640
maxPacketLength: NGTCP2_DEFAULT_MAX_PKTLEN,
16551641
servername: undefined,
16561642
socket: undefined,
1643+
silentClose: false,
16571644
statelessReset: false,
16581645
stats: undefined,
16591646
pendingStreams: new Set(),
@@ -1736,46 +1723,15 @@ class QuicSession extends EventEmitter {
17361723

17371724
// Causes the QuicSession to be immediately destroyed, but with
17381725
// additional metadata set.
1739-
[kDestroy](statelessReset, family, code) {
1726+
[kDestroy](code, family, silent, statelessReset) {
17401727
const state = this[kInternalState];
1741-
state.statelessReset = statelessReset;
17421728
state.closeCode = code;
17431729
state.closeFamily = family;
1730+
state.silentClose = silent;
1731+
state.statelessReset = statelessReset;
17441732
this.destroy();
17451733
}
17461734

1747-
// Immediate close has been initiated for the session. Any
1748-
// still open QuicStreams must be abandoned and shutdown
1749-
// with RESET_STREAM and STOP_SENDING frames transmitted
1750-
// as appropriate. Once the streams have been shutdown, a
1751-
// CONNECTION_CLOSE will be generated and sent, switching
1752-
// the session into the closing period.
1753-
[kClose](family, code) {
1754-
const state = this[kInternalState];
1755-
// Do nothing if the QuicSession has already been destroyed.
1756-
if (state.destroyed)
1757-
return;
1758-
1759-
// Set the close code and family so we can keep track.
1760-
state.closeCode = code;
1761-
state.closeFamily = family;
1762-
1763-
// Shutdown all pending streams. These are Streams that
1764-
// have been created but do not yet have a handle assigned.
1765-
for (const stream of state.pendingStreams)
1766-
stream[kClose](family, code);
1767-
1768-
// Shutdown all of the remaining streams
1769-
for (const stream of state.streams.values())
1770-
stream[kClose](family, code);
1771-
1772-
// By this point, all necessary RESET_STREAM and
1773-
// STOP_SENDING frames ought to have been sent,
1774-
// so now we just trigger sending of the
1775-
// CONNECTION_CLOSE frame.
1776-
this[kHandle].close(family, code);
1777-
}
1778-
17791735
// Closes the specified stream with the given code. The
17801736
// QuicStream object will be destroyed.
17811737
[kStreamClose](id, code) {
@@ -1873,14 +1829,6 @@ class QuicSession extends EventEmitter {
18731829
this[kInternalState].streams.set(id, stream);
18741830
}
18751831

1876-
// The QuicSession will be destroyed if closing has been
1877-
// called and there are no remaining streams
1878-
[kMaybeDestroy]() {
1879-
const state = this[kInternalState];
1880-
if (state.closing && state.streams.size === 0)
1881-
this.destroy();
1882-
}
1883-
18841832
// Called when a client QuicSession has opted to use the
18851833
// server provided preferred address. This is a purely
18861834
// informationational notification. It is not called on
@@ -1890,6 +1838,17 @@ class QuicSession extends EventEmitter {
18901838
emit.bind(this, 'usePreferredAddress', address));
18911839
}
18921840

1841+
// The QuicSession will be destroyed if close() has been
1842+
// called and there are no remaining streams
1843+
[kMaybeDestroy]() {
1844+
const state = this[kInternalState];
1845+
if (state.closing && state.streams.size === 0) {
1846+
this.destroy();
1847+
return true;
1848+
}
1849+
return false;
1850+
}
1851+
18931852
// Closing allows any existing QuicStream's to complete
18941853
// normally but disallows any new QuicStreams from being
18951854
// opened. Calls to openStream() will fail, and new streams
@@ -1910,27 +1869,27 @@ class QuicSession extends EventEmitter {
19101869
// has been destroyed
19111870
if (state.closing)
19121871
return;
1913-
19141872
state.closing = true;
1915-
this[kHandle].gracefulClose();
19161873

1917-
// See if we can close immediately.
1918-
this[kMaybeDestroy]();
1874+
// If there are no active streams, we can close immediately,
1875+
// otherwise set the graceful close flag.
1876+
if (!this[kMaybeDestroy]())
1877+
this[kHandle].gracefulClose();
19191878
}
19201879

19211880
// Destroying synchronously shuts down and frees the
19221881
// QuicSession immediately, even if there are still open
19231882
// streams.
19241883
//
1925-
// A CONNECTION_CLOSE packet is sent to the
1926-
// connected peer and the session is immediately
1927-
// destroyed.
1884+
// Unless we're in the middle of a silent close, a
1885+
// CONNECTION_CLOSE packet will be sent to the connected
1886+
// peer and the session will be immediately destroyed.
19281887
//
19291888
// If destroy is called with an error argument, the
19301889
// 'error' event is emitted on next tick.
19311890
//
19321891
// Once destroyed, and after the 'error' event (if any),
1933-
// the close event is emitted on next tick.
1892+
// the 'close' event is emitted on next tick.
19341893
destroy(error) {
19351894
const state = this[kInternalState];
19361895
// Destroy can only be called once. Multiple calls will be ignored
@@ -1939,19 +1898,6 @@ class QuicSession extends EventEmitter {
19391898
state.destroyed = true;
19401899
state.closing = false;
19411900

1942-
if (typeof error === 'number' ||
1943-
(error != null &&
1944-
typeof error === 'object' &&
1945-
!(error instanceof Error))) {
1946-
const {
1947-
closeCode,
1948-
closeFamily
1949-
} = validateCloseCode(error);
1950-
state.closeCode = closeCode;
1951-
state.closeFamily = closeFamily;
1952-
error = new ERR_QUIC_ERROR(closeCode, closeFamily);
1953-
}
1954-
19551901
// Destroy any pending streams immediately. These
19561902
// are streams that have been created but have not
19571903
// yet been assigned an internal handle.
@@ -1965,16 +1911,20 @@ class QuicSession extends EventEmitter {
19651911
this.removeListener('newListener', onNewListener);
19661912
this.removeListener('removeListener', onRemoveListener);
19671913

1914+
// If we are destroying with an error, schedule the
1915+
// error to be emitted on process.nextTick.
19681916
if (error) process.nextTick(emit.bind(this, 'error', error));
19691917

19701918
const handle = this[kHandle];
1919+
this[kHandle] = undefined;
1920+
19711921
if (handle !== undefined) {
19721922
// Copy the stats for use after destruction
19731923
state.stats = new BigInt64Array(handle.stats);
1974-
state.idleTimeout = !!handle.state[IDX_QUIC_SESSION_STATE_IDLE_TIMEOUT];
1975-
// Calling destroy will cause a CONNECTION_CLOSE to be
1976-
// sent to the peer and will destroy the QuicSession
1977-
// handler immediately.
1924+
state.idleTimeout =
1925+
Boolean(handle.state[IDX_QUIC_SESSION_STATE_IDLE_TIMEOUT]);
1926+
1927+
// Destroy the underlying QuicSession handle
19781928
handle.destroy(state.closeCode, state.closeFamily);
19791929
} else {
19801930
process.nextTick(emit.bind(this, 'close'));
@@ -2108,7 +2058,8 @@ class QuicSession extends EventEmitter {
21082058
const state = this[kInternalState];
21092059
return {
21102060
code: state.closeCode,
2111-
family: state.closeFamily
2061+
family: state.closeFamily,
2062+
silent: state.silentClose,
21122063
};
21132064
}
21142065

src/quic/node_quic_session-inl.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ void QuicSession::OnIdleTimeout() {
348348
if (!is_destroyed()) {
349349
state_[IDX_QUIC_SESSION_STATE_IDLE_TIMEOUT] = 1;
350350
Debug(this, "Idle timeout");
351-
SilentClose();
351+
Close(QuicSessionListener::SESSION_CLOSE_FLAG_SILENT);
352352
}
353353
}
354354

0 commit comments

Comments
 (0)