quic: cleanup QuicSocketFlags, used shared state struct

Some of the flags were no longer being used.

Switched to use an AliasedStruct for shared state to avoid
extraneous expensive JS=>C++ calls.

Removed unused QuicSocket option

PR-URL: https://github.com/nodejs/node/pull/34247
Reviewed-By: Anna Henningsen <anna@addaleax.net>
This commit is contained in:
James M Snell 2020-07-08 08:53:25 -07:00
parent d08e99de24
commit fe11f6bf7c
8 changed files with 137 additions and 159 deletions

View File

@ -1327,17 +1327,17 @@ added: REPLACEME
-->
Emitted when the server busy state has been toggled using
`quicSocket.serverBusy = true | false`. The callback is invoked with a
single boolean argument indicating `true` if busy status is enabled,
`false` otherwise. This event is strictly informational.
`quicSocket.serverBusy = true | false`. The callback is invoked with no
arguments. Use the `quicsocket.serverBusy` property to determine the
current status. This event is strictly informational.
```js
const { createQuicSocket } = require('net');
const socket = createQuicSocket();
socket.on('busy', (busy) => {
if (busy)
socket.on('busy', () => {
if (socket.serverBusy)
console.log('Server is busy');
else
console.log('Server is not busy');

View File

@ -12,6 +12,7 @@ assertCrypto();
const {
Array,
BigInt64Array,
Boolean,
Error,
Map,
Number,
@ -38,6 +39,7 @@ const {
validateQuicEndpointOptions,
validateCreateSecureContextOptions,
validateQuicSocketConnectOptions,
QuicSocketSharedState,
QuicSessionSharedState,
QLogStream,
} = require('internal/quic/util');
@ -269,8 +271,8 @@ function onSocketClose(err) {
// Called by the C++ internals when the server busy state of
// the QuicSocket has been changed.
function onSocketServerBusy(on) {
this[owner_symbol][kServerBusy](on);
function onSocketServerBusy() {
this[owner_symbol][kServerBusy]();
}
// Called by the C++ internals when a new server QuicSession has been created.
@ -845,19 +847,17 @@ class QuicEndpoint {
class QuicSocket extends EventEmitter {
[kInternalState] = {
alpn: undefined,
autoClose: undefined,
client: undefined,
defaultEncoding: undefined,
endpoints: new Set(),
highWaterMark: undefined,
listenPending: false,
lookup: undefined,
server: undefined,
serverBusy: false,
serverListening: false,
serverSecureContext: undefined,
sessions: new Set(),
state: kSocketUnbound,
statelessResetEnabled: true,
sharedState: undefined,
stats: undefined,
};
@ -865,11 +865,6 @@ class QuicSocket extends EventEmitter {
const {
endpoint,
// True if the QuicSocket should automatically enter a graceful shutdown
// if it is not listening as a server and the last QuicClientSession
// closes
autoClose,
// Default configuration for QuicClientSessions
client,
@ -913,7 +908,6 @@ class QuicSocket extends EventEmitter {
const state = this[kInternalState];
state.autoClose = autoClose;
state.client = client;
state.lookup = lookup || (type === AF_INET6 ? lookup6 : lookup4);
state.server = server;
@ -976,6 +970,10 @@ class QuicSocket extends EventEmitter {
if (handle !== undefined) {
handle[owner_symbol] = this;
this[async_id_symbol] = handle.getAsyncId();
this[kInternalState].sharedState =
new QuicSocketSharedState(handle.state);
} else {
this[kInternalState].sharedState = undefined;
}
}
@ -1081,16 +1079,13 @@ class QuicSocket extends EventEmitter {
}
// Called by the C++ internals to notify when server busy status is toggled.
[kServerBusy](on) {
this[kInternalState].serverBusy = on;
// In a nextTick because the event ends up being
// emitted synchronously when quicSocket.serverBusy
// is called.
[kServerBusy]() {
const busy = this.serverBusy;
process.nextTick(() => {
try {
this.emit('busy', on);
this.emit('busy', busy);
} catch (error) {
this[kRejections](error, 'busy', on);
this[kRejections](error, 'busy', busy);
}
});
}
@ -1161,6 +1156,7 @@ class QuicSocket extends EventEmitter {
// server and will emit session events whenever a new QuicServerSession
// is created.
const state = this[kInternalState];
state.listenPending = false;
this[kHandle].listen(
state.serverSecureContext.context,
address,
@ -1225,14 +1221,12 @@ class QuicSocket extends EventEmitter {
// function.
listen(options, callback) {
const state = this[kInternalState];
if (state.serverListening)
throw new ERR_QUICSOCKET_LISTENING();
if (state.state === kSocketDestroyed ||
state.state === kSocketClosing) {
throw new ERR_QUICSOCKET_DESTROYED('listen');
}
if (this.listening || state.listenPending)
throw new ERR_QUICSOCKET_LISTENING();
if (typeof options === 'function') {
callback = options;
options = {};
@ -1265,8 +1259,8 @@ class QuicSocket extends EventEmitter {
state.highWaterMark = highWaterMark;
state.defaultEncoding = defaultEncoding;
state.serverListening = true;
state.alpn = alpn;
state.listenPending = true;
// If the callback function is provided, it is registered as a
// handler for the on('session') event and will be called whenever
@ -1403,10 +1397,8 @@ class QuicSocket extends EventEmitter {
// listening for new QuicServerSession connections.
// New initial connection packets for currently unknown
// DCID's will be ignored.
if (this[kHandle]) {
this[kHandle].stopListening();
}
state.serverListening = false;
if (this[kHandle])
this[kInternalState].sharedState.serverListening = false;
// If there are no sessions, calling maybeDestroy
// will immediately and synchronously destroy the
@ -1502,7 +1494,7 @@ class QuicSocket extends EventEmitter {
// True if listen() has been called successfully
get listening() {
return this[kInternalState].serverListening;
return Boolean(this[kInternalState].sharedState?.serverListening);
}
// True if the QuicSocket is currently waiting on at least one
@ -1518,12 +1510,27 @@ class QuicSocket extends EventEmitter {
if (state.state === kSocketDestroyed)
throw new ERR_QUICSOCKET_DESTROYED('serverBusy');
validateBoolean(on, 'on');
if (state.serverBusy !== on)
this[kHandle].setServerBusy(on);
if (state.sharedState.serverBusy !== on) {
state.sharedState.serverBusy = on;
this[kServerBusy]();
}
}
get serverBusy() {
return this[kInternalState].serverBusy;
return Boolean(this[kInternalState].sharedState?.serverBusy);
}
set statelessResetDisabled(on) {
const state = this[kInternalState];
if (state.state === kSocketDestroyed)
throw new ERR_QUICSOCKET_DESTROYED('statelessResetDisabled');
validateBoolean(on, 'on');
if (state.sharedState.statelessResetDisabled !== on)
state.sharedState.statelessResetDisabled = on;
}
get statelessResetDisabled() {
return Boolean(this[kInternalState].sharedState?.statelessResetDisabled);
}
get duration() {
@ -1613,21 +1620,6 @@ class QuicSocket extends EventEmitter {
}
this[kHandle].setDiagnosticPacketLoss(rx, tx);
}
get statelessResetEnabled() {
return this[kInternalState].statelessResetEnabled;
}
set statelessResetEnabled(on) {
const state = this[kInternalState];
if (state.state === kSocketDestroyed)
throw new ERR_QUICSOCKET_DESTROYED('serverBusy');
validateBoolean(on, 'on');
if (state.statelessResetEnabled !== on) {
this[kHandle].enableStatelessReset(on);
state.statelessResetEnabled = on;
}
}
}
class QuicSession extends EventEmitter {

View File

@ -82,6 +82,10 @@ const {
IDX_QUICSESSION_STATE_MAX_DATA_LEFT,
IDX_QUICSESSION_STATE_BYTES_IN_FLIGHT,
IDX_QUICSOCKET_STATE_SERVER_LISTENING,
IDX_QUICSOCKET_STATE_SERVER_BUSY,
IDX_QUICSOCKET_STATE_STATELESS_RESET_DISABLED,
IDX_HTTP3_QPACK_MAX_TABLE_CAPACITY,
IDX_HTTP3_QPACK_BLOCKED_STREAMS,
IDX_HTTP3_MAX_HEADER_LIST_SIZE,
@ -514,7 +518,6 @@ function validateQuicSocketOptions(options = {}) {
validateObject(options, 'options');
const {
autoClose = false,
client = {},
disableStatelessReset = false,
endpoint = { port: 0, type: 'udp4' },
@ -538,7 +541,6 @@ function validateQuicSocketOptions(options = {}) {
validateLookup(lookup);
validateBoolean(validateAddress, 'options.validateAddress');
validateBoolean(validateAddressLRU, 'options.validateAddressLRU');
validateBoolean(autoClose, 'options.autoClose');
validateBoolean(qlog, 'options.qlog');
validateBoolean(disableStatelessReset, 'options.disableStatelessReset');
@ -576,7 +578,6 @@ function validateQuicSocketOptions(options = {}) {
return {
endpoint,
autoClose,
client,
lookup,
maxConnections,
@ -803,6 +804,39 @@ function toggleListeners(state, event, on) {
}
}
class QuicSocketSharedState {
constructor(state) {
this[kHandle] = Buffer.from(state);
}
get serverListening() {
return Boolean(this[kHandle]
.readUInt8(IDX_QUICSOCKET_STATE_SERVER_LISTENING));
}
set serverListening(on) {
this[kHandle].writeUInt8(on ? 1 : 0, IDX_QUICSOCKET_STATE_SERVER_LISTENING);
}
get serverBusy() {
return Boolean(this[kHandle].readUInt8(IDX_QUICSOCKET_STATE_SERVER_BUSY));
}
set serverBusy(on) {
this[kHandle].writeUInt8(on ? 1 : 0, IDX_QUICSOCKET_STATE_SERVER_BUSY);
}
get statelessResetDisabled() {
return Boolean(this[kHandle]
.readUInt8(IDX_QUICSOCKET_STATE_STATELESS_RESET_DISABLED));
}
set statelessResetDisabled(on) {
this[kHandle].writeUInt8(on ? 1 : 0,
IDX_QUICSOCKET_STATE_STATELESS_RESET_DISABLED);
}
}
// A utility class used to handle reading / modifying shared JS/C++
// state associated with a QuicSession
class QuicSessionSharedState {
@ -938,6 +972,7 @@ module.exports = {
validateQuicEndpointOptions,
validateCreateSecureContextOptions,
validateQuicSocketConnectOptions,
QuicSocketSharedState,
QuicSessionSharedState,
QLogStream,
};

View File

@ -205,6 +205,11 @@ void Initialize(Local<Object> target,
QUICSESSION_SHARED_STATE(V)
#undef V
#define V(id, _, __) \
NODE_DEFINE_CONSTANT(constants, IDX_QUICSOCKET_STATE_##id);
QUICSOCKET_SHARED_STATE(V)
#undef V
#define V(name, _, __) \
NODE_DEFINE_CONSTANT(constants, IDX_QUIC_SESSION_STATS_##name);
SESSION_STATS(V)

View File

@ -90,20 +90,6 @@ void QuicSocket::DisassociateStatelessResetToken(
token_map_.erase(token);
}
// StopListening is called when the QuicSocket is no longer
// accepting new server connections. Typically, this is called
// when the QuicSocket enters a graceful closing state where
// existing sessions are allowed to close naturally but new
// sessions are rejected.
void QuicSocket::StopListening() {
if (is_server_listening()) {
Debug(this, "Stop listening");
set_server_listening(false);
// It is important to not call ReceiveStop here as there
// is ongoing traffic being exchanged by the peers.
}
}
void QuicSocket::ReceiveStart() {
for (const auto& endpoint : endpoints_)
CHECK_EQ(endpoint->ReceiveStart(), 0);
@ -157,8 +143,8 @@ size_t QuicSocket::GetCurrentStatelessResetCounter(const SocketAddress& addr) {
void QuicSocket::ServerBusy(bool on) {
Debug(this, "Turning Server Busy Response %s", on ? "on" : "off");
set_server_busy(on);
listener_->OnServerBusy(on);
state_->server_busy = on ? 1 : 0;
listener_->OnServerBusy();
}
bool QuicSocket::is_diagnostic_packet_loss(double prob) const {
@ -173,11 +159,6 @@ void QuicSocket::set_diagnostic_packet_loss(double rx, double tx) {
tx_loss_ = tx;
}
bool QuicSocket::EnableStatelessReset(bool on) {
set_stateless_reset_disabled(!on);
return !is_stateless_reset_disabled();
}
void QuicSocket::set_validated_address(const SocketAddress& addr) {
if (has_option_validate_address_lru()) {
// Remove the oldest item if we've hit the LRU limit
@ -215,7 +196,7 @@ void QuicSocket::AddEndpoint(
if (preferred || endpoints_.empty())
preferred_endpoint_ = endpoint_;
endpoints_.emplace_back(endpoint_);
if (is_server_listening())
if (state_->server_listening)
endpoint_->ReceiveStart();
}

View File

@ -1,4 +1,5 @@
#include "node_quic_socket-inl.h" // NOLINT(build/include)
#include "aliased_struct-inl.h"
#include "allocated_buffer-inl.h"
#include "async_wrap-inl.h"
#include "debug_utils-inl.h"
@ -38,6 +39,7 @@ using v8::Local;
using v8::Number;
using v8::Object;
using v8::ObjectTemplate;
using v8::PropertyAttribute;
using v8::String;
using v8::Value;
@ -115,9 +117,9 @@ void QuicSocketListener::OnSessionReady(BaseObjectPtr<QuicSession> session) {
previous_listener_->OnSessionReady(session);
}
void QuicSocketListener::OnServerBusy(bool busy) {
void QuicSocketListener::OnServerBusy() {
if (previous_listener_ != nullptr)
previous_listener_->OnServerBusy(busy);
previous_listener_->OnServerBusy();
}
void QuicSocketListener::OnEndpointDone(QuicEndpoint* endpoint) {
@ -145,12 +147,12 @@ void JSQuicSocketListener::OnSessionReady(BaseObjectPtr<QuicSession> session) {
socket()->MakeCallback(env->quic_on_session_ready_function(), 1, &arg);
}
void JSQuicSocketListener::OnServerBusy(bool busy) {
void JSQuicSocketListener::OnServerBusy() {
Environment* env = socket()->env();
HandleScope handle_scope(env->isolate());
Context::Scope context_scope(env->context());
Local<Value> arg = Boolean::New(env->isolate(), busy);
socket()->MakeCallback(env->quic_on_socket_server_busy_function(), 1, &arg);
socket()->MakeCallback(
env->quic_on_socket_server_busy_function(), 0, nullptr);
}
void JSQuicSocketListener::OnEndpointDone(QuicEndpoint* endpoint) {
@ -252,6 +254,7 @@ QuicSocket::QuicSocket(
StatsBase(quic_state->env(), wrap),
alloc_info_(MakeAllocator()),
options_(options),
state_(quic_state->env()->isolate()),
max_connections_(max_connections),
max_connections_per_host_(max_connections_per_host),
max_stateless_resets_per_host_(max_stateless_resets_per_host),
@ -266,8 +269,14 @@ QuicSocket::QuicSocket(
EntropySource(token_secret_, kTokenSecretLen);
wrap->DefineOwnProperty(
env()->context(),
env()->state_string(),
state_.GetArrayBuffer(),
PropertyAttribute::ReadOnly).Check();
if (disable_stateless_reset)
set_stateless_reset_disabled();
state_->stateless_reset_disabled = 1;
// Set the session reset secret to the one provided or random.
// Note that a random secret is going to make it exceedingly
@ -316,13 +325,13 @@ void QuicSocket::Listen(
const std::string& alpn,
uint32_t options) {
CHECK(sc);
CHECK(!is_server_listening());
CHECK_NE(state_->server_listening, 1);
Debug(this, "Starting to listen");
server_session_config_.Set(quic_state(), preferred_address);
server_secure_context_ = sc;
server_alpn_ = alpn;
server_options_ = options;
set_server_listening();
state_->server_listening = 1;
RecordTimestamp(&QuicSocketStats::listen_at);
ReceiveStart();
}
@ -391,7 +400,7 @@ bool QuicSocket::MaybeStatelessReset(
const SocketAddress& local_addr,
const SocketAddress& remote_addr,
unsigned int flags) {
if (UNLIKELY(is_stateless_reset_disabled() || nread < 16))
if (UNLIKELY(state_->stateless_reset_disabled || nread < 16))
return false;
StatelessResetToken possible_token(
data + nread - NGTCP2_STATELESS_RESET_TOKENLEN);
@ -620,7 +629,7 @@ bool QuicSocket::SendStatelessReset(
const SocketAddress& local_addr,
const SocketAddress& remote_addr,
size_t source_len) {
if (UNLIKELY(is_stateless_reset_disabled()))
if (UNLIKELY(state_->stateless_reset_disabled))
return false;
constexpr static size_t kRandlen = NGTCP2_MIN_STATELESS_RESET_RANDLEN * 5;
constexpr static size_t kMinStatelessResetLen = 41;
@ -737,7 +746,7 @@ BaseObjectPtr<QuicSession> QuicSocket::AcceptInitialPacket(
QuicCID ocid;
// If the QuicSocket is not listening, the paket will be ignored.
if (!is_server_listening()) {
if (!state_->server_listening) {
Debug(this, "QuicSocket is not listening");
return {};
}
@ -762,7 +771,7 @@ BaseObjectPtr<QuicSession> QuicSocket::AcceptInitialPacket(
// Else, check to see if the number of connections total for this QuicSocket
// has been exceeded. If the count has been exceeded, shutdown the connection
// immediately after the initial keys are installed.
if (UNLIKELY(is_server_busy()) ||
if (UNLIKELY(state_->server_busy == 1) ||
sessions_.size() >= max_connections_ ||
GetCurrentSocketAddressCounter(remote_addr) >=
max_connections_per_host_) {
@ -1115,26 +1124,6 @@ void QuicSocketListen(const FunctionCallbackInfo<Value>& args) {
options);
}
void QuicSocketStopListening(const FunctionCallbackInfo<Value>& args) {
QuicSocket* socket;
ASSIGN_OR_RETURN_UNWRAP(&socket, args.Holder());
socket->StopListening();
}
void QuicSocketSetServerBusy(const FunctionCallbackInfo<Value>& args) {
QuicSocket* socket;
ASSIGN_OR_RETURN_UNWRAP(&socket, args.Holder());
CHECK_EQ(args.Length(), 1);
socket->ServerBusy(args[0]->IsTrue());
}
void QuicSocketEnableStatelessReset(const FunctionCallbackInfo<Value>& args) {
QuicSocket* socket;
ASSIGN_OR_RETURN_UNWRAP(&socket, args.Holder());
CHECK_EQ(args.Length(), 1);
args.GetReturnValue().Set(socket->EnableStatelessReset(args[0]->IsTrue()));
}
void QuicEndpointWaitForPendingCallbacks(
const FunctionCallbackInfo<Value>& args) {
QuicEndpoint* endpoint;
@ -1191,15 +1180,6 @@ void QuicSocket::Initialize(
env->SetProtoMethod(socket,
"setDiagnosticPacketLoss",
QuicSocketSetDiagnosticPacketLoss);
env->SetProtoMethod(socket,
"setServerBusy",
QuicSocketSetServerBusy);
env->SetProtoMethod(socket,
"stopListening",
QuicSocketStopListening);
env->SetProtoMethod(socket,
"enableStatelessReset",
QuicSocketEnableStatelessReset);
socket->Inherit(HandleWrap::GetConstructorTemplate(env));
target->Set(context, class_name,
socket->GetFunction(env->context()).ToLocalChecked()).FromJust();

View File

@ -3,6 +3,7 @@
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "aliased_struct.h"
#include "base_object.h"
#include "node.h"
#include "node_crypto.h"
@ -47,12 +48,24 @@ enum QuicSocketOptions : uint32_t {
};
#undef V
#define QUICSOCKET_FLAGS(V) \
V(GRACEFUL_CLOSE, graceful_closing) \
V(WAITING_FOR_CALLBACKS, waiting_for_callbacks) \
V(SERVER_LISTENING, server_listening) \
V(SERVER_BUSY, server_busy) \
V(DISABLE_STATELESS_RESET, stateless_reset_disabled)
#define QUICSOCKET_SHARED_STATE(V) \
V(SERVER_LISTENING, server_listening, uint8_t) \
V(SERVER_BUSY, server_busy, uint8_t) \
V(STATELESS_RESET_DISABLED, stateless_reset_disabled, uint8_t)
#define V(_, name, type) type name;
struct QuicSocketState {
QUICSOCKET_SHARED_STATE(V)
};
#undef V
#define V(id, name, _) \
IDX_QUICSOCKET_STATE_##id = offsetof(QuicSocketState, name),
enum QuicSocketStateFields {
QUICSOCKET_SHARED_STATE(V)
IDX_QUICSOCKET_STATE_END
};
#undef V
#define SOCKET_STATS(V) \
V(CREATED_AT, created_at, "Created At") \
@ -98,7 +111,7 @@ class QuicSocketListener {
virtual void OnError(ssize_t code);
virtual void OnSessionReady(BaseObjectPtr<QuicSession> session);
virtual void OnServerBusy(bool busy);
virtual void OnServerBusy();
virtual void OnEndpointDone(QuicEndpoint* endpoint);
virtual void OnDestroy();
@ -114,7 +127,7 @@ class JSQuicSocketListener final : public QuicSocketListener {
public:
void OnError(ssize_t code) override;
void OnSessionReady(BaseObjectPtr<QuicSession> session) override;
void OnServerBusy(bool busy) override;
void OnServerBusy() override;
void OnEndpointDone(QuicEndpoint* endpoint) override;
void OnDestroy() override;
};
@ -357,35 +370,19 @@ class QuicSocket : public AsyncWrap,
std::unique_ptr<QuicPacket> packet,
BaseObjectPtr<QuicSession> session = BaseObjectPtr<QuicSession>());
#define V(id, name) \
inline bool is_##name() const { \
return flags_ & (1 << QUICSOCKET_FLAG_##id); } \
inline void set_##name(bool on = true) { \
if (on) \
flags_ |= (1 << QUICSOCKET_FLAG_##id); \
else \
flags_ &= ~(1 << QUICSOCKET_FLAG_##id); \
}
QUICSOCKET_FLAGS(V)
#undef V
#define V(id, name) \
bool has_option_##name() const { \
return options_ & (1 << QUICSOCKET_OPTIONS_##id); }
QUICSOCKET_OPTIONS(V)
#undef V
// Allows the server busy status to be enabled from C++. A notification
// will be sent to the JavaScript side informing that the status has
// changed.
inline void ServerBusy(bool on);
inline void set_diagnostic_packet_loss(double rx = 0.0, double tx = 0.0);
inline void StopListening();
// Toggles whether or not stateless reset is enabled or not.
// Returns true if stateless reset is enabled, false if it
// is not.
inline bool EnableStatelessReset(bool on = true);
BaseObjectPtr<crypto::SecureContext> server_secure_context() const {
return server_secure_context_;
}
@ -513,13 +510,6 @@ class QuicSocket : public AsyncWrap,
// and the current packet should be artificially considered lost.
inline bool is_diagnostic_packet_loss(double prob) const;
#define V(id, _) QUICSOCKET_FLAG_##id,
enum QuicSocketFlags : uint32_t {
QUICSOCKET_FLAGS(V)
QUICSOCKET_FLAG_COUNT
};
#undef V
ngtcp2_mem alloc_info_;
std::vector<BaseObjectPtr<QuicEndpoint>> endpoints_;
@ -530,6 +520,8 @@ class QuicSocket : public AsyncWrap,
uint32_t options_ = 0;
uint32_t server_options_;
AliasedStruct<QuicSocketState> state_;
size_t max_connections_ = DEFAULT_MAX_CONNECTIONS;
size_t max_connections_per_host_ = DEFAULT_MAX_CONNECTIONS_PER_HOST;
size_t current_ngtcp2_memory_ = 0;

View File

@ -82,13 +82,6 @@ const { createQuicSocket } = require('net');
});
});
// Test invalid QuicSocket autoClose argument option
[1, NaN, 1n, null, {}, []].forEach((autoClose) => {
assert.throws(() => createQuicSocket({ autoClose }), {
code: 'ERR_INVALID_ARG_TYPE'
});
});
// Test invalid QuicSocket qlog argument option
[1, NaN, 1n, null, {}, []].forEach((qlog) => {
assert.throws(() => createQuicSocket({ qlog }), {