Commit 098e3d7

Browse files
jasnelladuh95
authored andcommitted
quic: eliminate per-received datagram allocation
Signed-off-by: James M Snell <jasnell@gmail.com> Assisted-by: Opencode:Opus 4.6 PR-URL: #63267 Backport-PR-URL: #64675 Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent 53b05e2 commit 098e3d7

5 files changed

Lines changed: 68 additions & 68 deletions

File tree

‎src/quic/application.cc‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -448,9 +448,13 @@ void Session::Application::SendPendingData() {
448448

449449
// Awesome, let's write our packet!
450450
PacketInfo pi;
451-
ssize_t nwrite = WriteVStream(
452-
&path, &pi, packet->data(), &ndatalen, packet->length(),
453-
stream_data, ts);
451+
ssize_t nwrite = WriteVStream(&path,
452+
&pi,
453+
packet->data(),
454+
&ndatalen,
455+
packet->length(),
456+
stream_data,
457+
ts);
454458

455459
// When ndatalen is > 0, that's our indication that stream data was accepted
456460
// in to the packet. Yay!

‎src/quic/endpoint.cc‎

Lines changed: 43 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -311,10 +311,18 @@ class Endpoint::UDP::Impl final : public HandleWrap {
311311
SET_SELF_SIZE(Impl)
312312

313313
private:
314+
// Pre-allocated receive buffer. Reused across all datagrams because
315+
// ngtcp2_conn_read_pkt is synchronous — it copies what it needs and
316+
// does not retain a reference to the buffer after returning. This
317+
// eliminates a malloc(64KB)/free(64KB) cycle per received datagram.
318+
static constexpr size_t kRecvBufferSize = 65536; // UV__UDP_DGRAM_MAXSIZE
319+
char recv_buf_[kRecvBufferSize];
320+
314321
staticvoidOnAlloc(uv_handle_t* handle,
315322
size_t suggested_size,
316323
uv_buf_t* buf) {
317-
*buf = From(handle)->env()->allocate_managed_buffer(suggested_size);
324+
auto* impl = From(handle);
325+
*buf = uv_buf_init(impl->recv_buf_, kRecvBufferSize);
318326
}
319327

320328
staticvoidOnReceive(uv_udp_t* handle,
@@ -326,26 +334,22 @@ class Endpoint::UDP::Impl final : public HandleWrap {
326334
DCHECK_NOT_NULL(impl);
327335
DCHECK_NOT_NULL(impl->endpoint_);
328336

329-
auto release_buf = [&]() {
330-
if (buf->base != nullptr) impl->env()->release_managed_buffer(*buf);
331-
};
332-
333337
// Nothing to do in these cases. Specifically, if the nread
334338
// is zero or we have received a partial packet, we are just
335-
// going to ignore it.
339+
// going to ignore it. No buffer release needed — recv_buf_
340+
// is pre-allocated and reused.
336341
if (nread == 0 || flags & UV_UDP_PARTIAL) {
337-
release_buf();
338342
return;
339343
}
340344

341345
if (nread < 0) {
342-
release_buf();
343346
impl->endpoint_->Destroy(CloseContext::RECEIVE_FAILURE,
344347
static_cast<int>(nread));
345348
return;
346349
}
347350

348-
impl->endpoint_->Receive(uv_buf_init(buf->base, static_cast<size_t>(nread)),
351+
impl->endpoint_->Receive(reinterpret_cast<constuint8_t*>(buf->base),
352+
static_cast<size_t>(nread),
349353
SocketAddress(addr));
350354
}
351355

@@ -1264,24 +1268,25 @@ void Endpoint::CloseGracefully() {
12641268
MaybeDestroy();
12651269
}
12661270

1267-
voidEndpoint::Receive(constuv_buf_t& buf,
1271+
voidEndpoint::Receive(constuint8_t* data,
1272+
size_t len,
12681273
const SocketAddress& remote_address) {
12691274
constauto receive = [&](Session* session,
1270-
Store&& store,
1275+
constuint8_t* pkt_data,
1276+
size_t pkt_len,
12711277
const SocketAddress& local_address,
12721278
const SocketAddress& remote_address,
12731279
constCID& dcid,
12741280
constCID& scid) {
12751281
DCHECK_NOT_NULL(session);
12761282
if (session->is_destroyed()) return;
1277-
size_t len = store.length();
12781283
// Use ReadPacket (no SendPendingDataScope) so that multiple packets
12791284
// received in the same I/O burst are processed before any responses
12801285
// are generated. The deferred flush via BindingData's uv_check
12811286
// callback calls SendPendingData once per dirty session after all
12821287
// packets in the burst have been read.
1283-
if (session->ReadPacket(std::move(store), local_address, remote_address)) {
1284-
STAT_INCREMENT_N(Stats, bytes_received, len);
1288+
if (session->ReadPacket(pkt_data, pkt_len, local_address, remote_address)) {
1289+
STAT_INCREMENT_N(Stats, bytes_received, pkt_len);
12851290
STAT_INCREMENT(Stats, packets_received);
12861291
}
12871292
// Schedule the session for deferred SendPendingData if it hasn't
@@ -1293,7 +1298,9 @@ void Endpoint::Receive(const uv_buf_t& buf,
12931298
}
12941299
};
12951300

1296-
constauto accept = [&](const Session::Config& config, Store&& store) {
1301+
constauto accept = [&](const Session::Config& config,
1302+
constuint8_t* pkt_data,
1303+
size_t pkt_len) {
12971304
// One final check. If the endpoint is closed, closing, or is not listening
12981305
// as a server, then we cannot accept the initial packet.
12991306
if (is_closed() || is_closing() || !is_listening()) return;
@@ -1323,7 +1330,8 @@ void Endpoint::Receive(const uv_buf_t& buf,
13231330
return;
13241331

13251332
receive(session.get(),
1326-
std::move(store),
1333+
pkt_data,
1334+
pkt_len,
13271335
config.local_address,
13281336
config.remote_address,
13291337
config.dcid,
@@ -1333,7 +1341,8 @@ void Endpoint::Receive(const uv_buf_t& buf,
13331341
constauto acceptInitialPacket = [&](constuint32_t version,
13341342
constCID& dcid,
13351343
constCID& scid,
1336-
Store&& store,
1344+
constuint8_t* pkt_data,
1345+
size_t pkt_len,
13371346
const SocketAddress& local_address,
13381347
const SocketAddress& remote_address) {
13391348
// If we're not listening as a server, do not accept an initial packet.
@@ -1343,8 +1352,7 @@ void Endpoint::Receive(const uv_buf_t& buf,
13431352

13441353
// This is our first condition check... A minimal check to see if ngtcp2 can
13451354
// even recognize this packet as a quic packet.
1346-
ngtcp2_vec vec = store;
1347-
if (ngtcp2_accept(&hd, vec.base, vec.len) != NGTCP2_SUCCESS) {
1355+
if (ngtcp2_accept(&hd, pkt_data, pkt_len) != NGTCP2_SUCCESS) {
13481356
// Per the ngtcp2 docs, ngtcp2_accept returns 0 if the check was
13491357
// successful, or an error code if it was not. Currently there's only one
13501358
// documented error code (NGTCP2_ERR_INVALID_ARGUMENT) but we'll handle
@@ -1582,7 +1590,7 @@ void Endpoint::Receive(const uv_buf_t& buf,
15821590
}
15831591
}
15841592

1585-
accept(config, std::move(store));
1593+
accept(config, pkt_data, pkt_len);
15861594
};
15871595

15881596
// When a received packet contains a QUIC short header but cannot be matched
@@ -1598,35 +1606,37 @@ void Endpoint::Receive(const uv_buf_t& buf,
15981606
// possible to avoid a DOS vector.
15991607
constauto maybeStatelessReset = [&](constCID& dcid,
16001608
constCID& scid,
1601-
Store& store,
1609+
constuint8_t* pkt_data,
1610+
size_t pkt_len,
16021611
const SocketAddress& local_address,
16031612
const SocketAddress& remote_address) {
16041613
// Support for stateless resets can be disabled by the application. If that
16051614
// case, or if the packet is too short to contain a reset token, then we
16061615
// skip the remaining checks.
16071616
if (options_.disable_stateless_reset ||
1608-
store.length() < NGTCP2_STATELESS_RESET_TOKENLEN) {
1617+
pkt_len < NGTCP2_STATELESS_RESET_TOKENLEN) {
16091618
returnfalse;
16101619
}
16111620

16121621
// The stateless reset token itself is the *final*
16131622
// NGTCP2_STATELESS_RESET_TOKENLEN bytes in the received packet. If it is a
16141623
// stateless reset then then rest of the bytes in the packet are garbage
16151624
// that we'll ignore.
1616-
ngtcp2_vec vec = store;
1617-
vec.base += (vec.len - NGTCP2_STATELESS_RESET_TOKENLEN);
1625+
constuint8_t* token_pos =
1626+
pkt_data + (pkt_len - NGTCP2_STATELESS_RESET_TOKENLEN);
16181627

16191628
// If a Session has been associated with the token, then it is a valid
16201629
// stateless reset token. We need to dispatch it to the session to be
16211630
// processed.
16221631
auto* session = session_manager().FindSessionByStatelessResetToken(
1623-
StatelessResetToken(vec.base));
1632+
StatelessResetToken(token_pos));
16241633
if (session != nullptr) {
16251634
// If the session happens to have been destroyed already, we'll
16261635
// just ignore the packet.
16271636
if (!session->is_destroyed()) [[likely]] {
16281637
receive(session,
1629-
std::move(store),
1638+
pkt_data,
1639+
pkt_len,
16301640
local_address,
16311641
remote_address,
16321642
dcid,
@@ -1654,22 +1664,8 @@ void Endpoint::Receive(const uv_buf_t& buf,
16541664
// return;
16551665
// }
16561666

1657-
Debug(this, "Received %zu-byte packet from %s", buf.len, remote_address);
1658-
1659-
// The managed buffer here contains the received packet. We do not yet know
1660-
// at this point if it is a valid QUIC packet. We need to do some basic
1661-
// checks. It is critical at this point that we do as little work as possible
1662-
// to avoid a DOS vector.
1663-
std::shared_ptr<BackingStore> backing = env()->release_managed_buffer(buf);
1664-
if (!backing) [[unlikely]] {
1665-
// At this point something bad happened and we need to treat this as a fatal
1666-
// case. There's likely no way to test this specific condition reliably.
1667-
returnDestroy(CloseContext::RECEIVE_FAILURE, UV_ENOMEM);
1668-
}
1669-
1670-
Store store(std::move(backing), buf.len, 0);
1667+
Debug(this, "Received %zu-byte packet from %s", len, remote_address);
16711668

1672-
ngtcp2_vec vec = store;
16731669
ngtcp2_version_cid pversion_cid;
16741670

16751671
// This is our first check to see if the received data can be processed as a
@@ -1678,7 +1674,7 @@ void Endpoint::Receive(const uv_buf_t& buf,
16781674
// valid QUIC header but there is still no guarantee that the packet can be
16791675
// successfully processed.
16801676
switch (ngtcp2_pkt_decode_version_cid(
1681-
&pversion_cid, vec.base, vec.len, NGTCP2_MAX_CIDLEN)) {
1677+
&pversion_cid, data, len, NGTCP2_MAX_CIDLEN)) {
16821678
case0:
16831679
break; // Supported version, continue processing.
16841680
caseNGTCP2_ERR_VERSION_NEGOTIATION: {
@@ -1756,7 +1752,7 @@ void Endpoint::Receive(const uv_buf_t& buf,
17561752
// necessary here. We want to return immediately without committing any
17571753
// further resources.
17581754
if (pversion_cid.version == 0 &&
1759-
maybeStatelessReset(dcid, scid, store, addr, remote_address)) {
1755+
maybeStatelessReset(dcid, scid, data, len, addr, remote_address)) {
17601756
Debug(this, "Packet was a stateless reset");
17611757
return; // Stateless reset! Don't do any further processing.
17621758
}
@@ -1771,17 +1767,13 @@ void Endpoint::Receive(const uv_buf_t& buf,
17711767
SendStatelessReset(
17721768
PathDescriptor{
17731769
pversion_cid.version, dcid, scid, addr, remote_address},
1774-
store.length());
1770+
len);
17751771
return;
17761772
}
17771773

17781774
// Process the packet as an initial packet...
1779-
returnacceptInitialPacket(pversion_cid.version,
1780-
dcid,
1781-
scid,
1782-
std::move(store),
1783-
addr,
1784-
remote_address);
1775+
returnacceptInitialPacket(
1776+
pversion_cid.version, dcid, scid, data, len, addr, remote_address);
17851777
}
17861778

17871779
if (session->is_destroyed()) [[unlikely]] {
@@ -1793,7 +1785,7 @@ void Endpoint::Receive(const uv_buf_t& buf,
17931785
// If we got here, the dcid matched the scid of a known local session. Yay!
17941786
// The session will take over any further processing of the packet.
17951787
Debug(this, "Dispatching packet to known session");
1796-
receive(session.get(), std::move(store), addr, remote_address, dcid, scid);
1788+
receive(session.get(), data, len, addr, remote_address, dcid, scid);
17971789

17981790
// It is important to note that the session may have been destroyed during
17991791
// the call to receive(...). If that's the case, the session object still

‎src/quic/endpoint.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,7 @@ class Endpoint final : public AsyncWrap, public Packet::Listener {
429429
// Ref() causes a listening Endpoint to keep the event loop active.
430430
JS_METHOD(Ref);
431431

432-
voidReceive(constuv_buf_t& buf, const SocketAddress& from);
432+
voidReceive(constuint8_t* data, size_t len, const SocketAddress& from);
433433

434434
AliasedStruct<Stats> stats_;
435435
AliasedStruct<State> state_;

‎src/quic/session.cc‎

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2109,7 +2109,8 @@ void Session::SetLastError(QuicError&& error) {
21092109
impl_->last_error_ = std::move(error);
21102110
}
21112111

2112-
boolSession::Receive(Store&& store,
2112+
boolSession::Receive(constuint8_t* data,
2113+
size_t len,
21132114
const SocketAddress& local_address,
21142115
const SocketAddress& remote_address,
21152116
const PacketInfo& pkt_info,
@@ -2120,24 +2121,23 @@ bool Session::Receive(Store&& store,
21202121
// The hot receive path uses ReadPacket() directly with deferred
21212122
// flush via BindingData's uv_check callback.
21222123
SendPendingDataScope send_scope(this);
2123-
returnReadPacket(
2124-
std::move(store), local_address, remote_address, pkt_info, ts);
2124+
returnReadPacket(data, len, local_address, remote_address, pkt_info, ts);
21252125
}
21262126

2127-
boolSession::ReadPacket(Store&& store,
2127+
boolSession::ReadPacket(constuint8_t* data,
2128+
size_t len,
21282129
const SocketAddress& local_address,
21292130
const SocketAddress& remote_address,
21302131
const PacketInfo& pkt_info,
21312132
uint64_t ts) {
21322133
DCHECK(!is_destroyed());
21332134
impl_->remote_address_ = remote_address;
21342135

2135-
ngtcp2_vec vec = store;
21362136
Path path(local_address, remote_address);
21372137

21382138
Debug(this,
21392139
"Session is receiving %zu-byte packet received along path %s",
2140-
vec.len,
2140+
len,
21412141
path);
21422142

21432143
// It is important to understand that reading the packet will cause
@@ -2158,19 +2158,18 @@ bool Session::ReadPacket(Store&& store,
21582158
// receive path caches a timestamp and passes it to all ReadPacket()
21592159
// calls in the same I/O burst.
21602160
if (ts == 0) ts = uv_hrtime();
2161-
err = ngtcp2_conn_read_pkt(
2162-
*this, &path, pkt_info, vec.base, vec.len, ts);
2161+
err = ngtcp2_conn_read_pkt(*this, &path, pkt_info, data, len, ts);
21632162
}
21642163
if (is_destroyed()) returnfalse;
21652164

2166-
Debug(this, "Session receiving %zu-byte packet with result %d", vec.len, err);
2165+
Debug(this, "Session receiving %zu-byte packet with result %d", len, err);
21672166

21682167
switch (err) {
21692168
case0: {
2170-
Debug(this, "Session successfully received %zu-byte packet", vec.len);
2169+
Debug(this, "Session successfully received %zu-byte packet", len);
21712170
if (!is_destroyed()) [[likely]] {
21722171
auto& stats_ = impl_->stats_;
2173-
STAT_INCREMENT_N(Stats, bytes_received, vec.len);
2172+
STAT_INCREMENT_N(Stats, bytes_received, len);
21742173
// Process deferred operations that couldn't run inside callback
21752174
// scopes (e.g., HTTP/3 GOAWAY handling that calls into JS).
21762175
application().PostReceive();

‎src/quic/session.h‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,8 @@ class Session final : public AsyncWrap, private SessionTicket::AppData::Source {
353353
bool early = false;
354354
};
355355

356-
boolReceive(Store&& store,
356+
boolReceive(constuint8_t* data,
357+
size_t len,
357358
const SocketAddress& local_address,
358359
const SocketAddress& remote_address,
359360
const PacketInfo& pkt_info = PacketInfo(),
@@ -367,10 +368,14 @@ class Session final : public AsyncWrap, private SessionTicket::AppData::Source {
367368
// Receive() is kept as a convenience wrapper that calls ReadPacket()
368369
// then triggers SendPendingData (for paths like Connect that need
369370
// immediate response).
371+
// The data pointer is used synchronously — ngtcp2_conn_read_pkt does
372+
// not retain a reference after returning, so the caller's buffer can
373+
// be reused immediately.
370374
// When ts is 0 (the default), uv_hrtime() is called internally.
371375
// The batched receive path caches a timestamp and passes it to all
372376
// ReadPacket() calls in the same I/O burst.
373-
boolReadPacket(Store&& store,
377+
boolReadPacket(constuint8_t* data,
378+
size_t len,
374379
const SocketAddress& local_address,
375380
const SocketAddress& remote_address,
376381
const PacketInfo& pkt_info = PacketInfo(),

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Commit 098e3d7

Browse files
jasnelladuh95
authored andcommitted
quic: eliminate per-received datagram allocation
Signed-off-by: James M Snell <jasnell@gmail.com> Assisted-by: Opencode:Opus 4.6 PR-URL: #63267 Backport-PR-URL: #64675 Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent 53b05e2 commit 098e3d7

5 files changed

Lines changed: 68 additions & 68 deletions

File tree

‎src/quic/application.cc‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -448,9 +448,13 @@ void Session::Application::SendPendingData() {
448448

449449
// Awesome, let's write our packet!
450450
PacketInfo pi;
451-
ssize_t nwrite = WriteVStream(
452-
&path, &pi, packet->data(), &ndatalen, packet->length(),
453-
stream_data, ts);
451+
ssize_t nwrite = WriteVStream(&path,
452+
&pi,
453+
packet->data(),
454+
&ndatalen,
455+
packet->length(),
456+
stream_data,
457+
ts);
454458

455459
// When ndatalen is > 0, that's our indication that stream data was accepted
456460
// in to the packet. Yay!

‎src/quic/endpoint.cc‎

Lines changed: 43 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -311,10 +311,18 @@ class Endpoint::UDP::Impl final : public HandleWrap {
311311
SET_SELF_SIZE(Impl)
312312

313313
private:
314+
// Pre-allocated receive buffer. Reused across all datagrams because
315+
// ngtcp2_conn_read_pkt is synchronous — it copies what it needs and
316+
// does not retain a reference to the buffer after returning. This
317+
// eliminates a malloc(64KB)/free(64KB) cycle per received datagram.
318+
static constexpr size_t kRecvBufferSize = 65536; // UV__UDP_DGRAM_MAXSIZE
319+
char recv_buf_[kRecvBufferSize];
320+
314321
staticvoidOnAlloc(uv_handle_t* handle,
315322
size_t suggested_size,
316323
uv_buf_t* buf) {
317-
*buf = From(handle)->env()->allocate_managed_buffer(suggested_size);
324+
auto* impl = From(handle);
325+
*buf = uv_buf_init(impl->recv_buf_, kRecvBufferSize);
318326
}
319327

320328
staticvoidOnReceive(uv_udp_t* handle,
@@ -326,26 +334,22 @@ class Endpoint::UDP::Impl final : public HandleWrap {
326334
DCHECK_NOT_NULL(impl);
327335
DCHECK_NOT_NULL(impl->endpoint_);
328336

329-
auto release_buf = [&]() {
330-
if (buf->base != nullptr) impl->env()->release_managed_buffer(*buf);
331-
};
332-
333337
// Nothing to do in these cases. Specifically, if the nread
334338
// is zero or we have received a partial packet, we are just
335-
// going to ignore it.
339+
// going to ignore it. No buffer release needed — recv_buf_
340+
// is pre-allocated and reused.
336341
if (nread == 0 || flags & UV_UDP_PARTIAL) {
337-
release_buf();
338342
return;
339343
}
340344

341345
if (nread < 0) {
342-
release_buf();
343346
impl->endpoint_->Destroy(CloseContext::RECEIVE_FAILURE,
344347
static_cast<int>(nread));
345348
return;
346349
}
347350

348-
impl->endpoint_->Receive(uv_buf_init(buf->base, static_cast<size_t>(nread)),
351+
impl->endpoint_->Receive(reinterpret_cast<constuint8_t*>(buf->base),
352+
static_cast<size_t>(nread),
349353
SocketAddress(addr));
350354
}
351355

@@ -1264,24 +1268,25 @@ void Endpoint::CloseGracefully() {
12641268
MaybeDestroy();
12651269
}
12661270

1267-
voidEndpoint::Receive(constuv_buf_t& buf,
1271+
voidEndpoint::Receive(constuint8_t* data,
1272+
size_t len,
12681273
const SocketAddress& remote_address) {
12691274
constauto receive = [&](Session* session,
1270-
Store&& store,
1275+
constuint8_t* pkt_data,
1276+
size_t pkt_len,
12711277
const SocketAddress& local_address,
12721278
const SocketAddress& remote_address,
12731279
constCID& dcid,
12741280
constCID& scid) {
12751281
DCHECK_NOT_NULL(session);
12761282
if (session->is_destroyed()) return;
1277-
size_t len = store.length();
12781283
// Use ReadPacket (no SendPendingDataScope) so that multiple packets
12791284
// received in the same I/O burst are processed before any responses
12801285
// are generated. The deferred flush via BindingData's uv_check
12811286
// callback calls SendPendingData once per dirty session after all
12821287
// packets in the burst have been read.
1283-
if (session->ReadPacket(std::move(store), local_address, remote_address)) {
1284-
STAT_INCREMENT_N(Stats, bytes_received, len);
1288+
if (session->ReadPacket(pkt_data, pkt_len, local_address, remote_address)) {
1289+
STAT_INCREMENT_N(Stats, bytes_received, pkt_len);
12851290
STAT_INCREMENT(Stats, packets_received);
12861291
}
12871292
// Schedule the session for deferred SendPendingData if it hasn't
@@ -1293,7 +1298,9 @@ void Endpoint::Receive(const uv_buf_t& buf,
12931298
}
12941299
};
12951300

1296-
constauto accept = [&](const Session::Config& config, Store&& store) {
1301+
constauto accept = [&](const Session::Config& config,
1302+
constuint8_t* pkt_data,
1303+
size_t pkt_len) {
12971304
// One final check. If the endpoint is closed, closing, or is not listening
12981305
// as a server, then we cannot accept the initial packet.
12991306
if (is_closed() || is_closing() || !is_listening()) return;
@@ -1323,7 +1330,8 @@ void Endpoint::Receive(const uv_buf_t& buf,
13231330
return;
13241331

13251332
receive(session.get(),
1326-
std::move(store),
1333+
pkt_data,
1334+
pkt_len,
13271335
config.local_address,
13281336
config.remote_address,
13291337
config.dcid,
@@ -1333,7 +1341,8 @@ void Endpoint::Receive(const uv_buf_t& buf,
13331341
constauto acceptInitialPacket = [&](constuint32_t version,
13341342
constCID& dcid,
13351343
constCID& scid,
1336-
Store&& store,
1344+
constuint8_t* pkt_data,
1345+
size_t pkt_len,
13371346
const SocketAddress& local_address,
13381347
const SocketAddress& remote_address) {
13391348
// If we're not listening as a server, do not accept an initial packet.
@@ -1343,8 +1352,7 @@ void Endpoint::Receive(const uv_buf_t& buf,
13431352

13441353
// This is our first condition check... A minimal check to see if ngtcp2 can
13451354
// even recognize this packet as a quic packet.
1346-
ngtcp2_vec vec = store;
1347-
if (ngtcp2_accept(&hd, vec.base, vec.len) != NGTCP2_SUCCESS) {
1355+
if (ngtcp2_accept(&hd, pkt_data, pkt_len) != NGTCP2_SUCCESS) {
13481356
// Per the ngtcp2 docs, ngtcp2_accept returns 0 if the check was
13491357
// successful, or an error code if it was not. Currently there's only one
13501358
// documented error code (NGTCP2_ERR_INVALID_ARGUMENT) but we'll handle
@@ -1582,7 +1590,7 @@ void Endpoint::Receive(const uv_buf_t& buf,
15821590
}
15831591
}
15841592

1585-
accept(config, std::move(store));
1593+
accept(config, pkt_data, pkt_len);
15861594
};
15871595

15881596
// When a received packet contains a QUIC short header but cannot be matched
@@ -1598,35 +1606,37 @@ void Endpoint::Receive(const uv_buf_t& buf,
15981606
// possible to avoid a DOS vector.
15991607
constauto maybeStatelessReset = [&](constCID& dcid,
16001608
constCID& scid,
1601-
Store& store,
1609+
constuint8_t* pkt_data,
1610+
size_t pkt_len,
16021611
const SocketAddress& local_address,
16031612
const SocketAddress& remote_address) {
16041613
// Support for stateless resets can be disabled by the application. If that
16051614
// case, or if the packet is too short to contain a reset token, then we
16061615
// skip the remaining checks.
16071616
if (options_.disable_stateless_reset ||
1608-
store.length() < NGTCP2_STATELESS_RESET_TOKENLEN) {
1617+
pkt_len < NGTCP2_STATELESS_RESET_TOKENLEN) {
16091618
returnfalse;
16101619
}
16111620

16121621
// The stateless reset token itself is the *final*
16131622
// NGTCP2_STATELESS_RESET_TOKENLEN bytes in the received packet. If it is a
16141623
// stateless reset then then rest of the bytes in the packet are garbage
16151624
// that we'll ignore.
1616-
ngtcp2_vec vec = store;
1617-
vec.base += (vec.len - NGTCP2_STATELESS_RESET_TOKENLEN);
1625+
constuint8_t* token_pos =
1626+
pkt_data + (pkt_len - NGTCP2_STATELESS_RESET_TOKENLEN);
16181627

16191628
// If a Session has been associated with the token, then it is a valid
16201629
// stateless reset token. We need to dispatch it to the session to be
16211630
// processed.
16221631
auto* session = session_manager().FindSessionByStatelessResetToken(
1623-
StatelessResetToken(vec.base));
1632+
StatelessResetToken(token_pos));
16241633
if (session != nullptr) {
16251634
// If the session happens to have been destroyed already, we'll
16261635
// just ignore the packet.
16271636
if (!session->is_destroyed()) [[likely]] {
16281637
receive(session,
1629-
std::move(store),
1638+
pkt_data,
1639+
pkt_len,
16301640
local_address,
16311641
remote_address,
16321642
dcid,
@@ -1654,22 +1664,8 @@ void Endpoint::Receive(const uv_buf_t& buf,
16541664
// return;
16551665
// }
16561666

1657-
Debug(this, "Received %zu-byte packet from %s", buf.len, remote_address);
1658-
1659-
// The managed buffer here contains the received packet. We do not yet know
1660-
// at this point if it is a valid QUIC packet. We need to do some basic
1661-
// checks. It is critical at this point that we do as little work as possible
1662-
// to avoid a DOS vector.
1663-
std::shared_ptr<BackingStore> backing = env()->release_managed_buffer(buf);
1664-
if (!backing) [[unlikely]] {
1665-
// At this point something bad happened and we need to treat this as a fatal
1666-
// case. There's likely no way to test this specific condition reliably.
1667-
returnDestroy(CloseContext::RECEIVE_FAILURE, UV_ENOMEM);
1668-
}
1669-
1670-
Store store(std::move(backing), buf.len, 0);
1667+
Debug(this, "Received %zu-byte packet from %s", len, remote_address);
16711668

1672-
ngtcp2_vec vec = store;
16731669
ngtcp2_version_cid pversion_cid;
16741670

16751671
// This is our first check to see if the received data can be processed as a
@@ -1678,7 +1674,7 @@ void Endpoint::Receive(const uv_buf_t& buf,
16781674
// valid QUIC header but there is still no guarantee that the packet can be
16791675
// successfully processed.
16801676
switch (ngtcp2_pkt_decode_version_cid(
1681-
&pversion_cid, vec.base, vec.len, NGTCP2_MAX_CIDLEN)) {
1677+
&pversion_cid, data, len, NGTCP2_MAX_CIDLEN)) {
16821678
case0:
16831679
break; // Supported version, continue processing.
16841680
caseNGTCP2_ERR_VERSION_NEGOTIATION: {
@@ -1756,7 +1752,7 @@ void Endpoint::Receive(const uv_buf_t& buf,
17561752
// necessary here. We want to return immediately without committing any
17571753
// further resources.
17581754
if (pversion_cid.version == 0 &&
1759-
maybeStatelessReset(dcid, scid, store, addr, remote_address)) {
1755+
maybeStatelessReset(dcid, scid, data, len, addr, remote_address)) {
17601756
Debug(this, "Packet was a stateless reset");
17611757
return; // Stateless reset! Don't do any further processing.
17621758
}
@@ -1771,17 +1767,13 @@ void Endpoint::Receive(const uv_buf_t& buf,
17711767
SendStatelessReset(
17721768
PathDescriptor{
17731769
pversion_cid.version, dcid, scid, addr, remote_address},
1774-
store.length());
1770+
len);
17751771
return;
17761772
}
17771773

17781774
// Process the packet as an initial packet...
1779-
returnacceptInitialPacket(pversion_cid.version,
1780-
dcid,
1781-
scid,
1782-
std::move(store),
1783-
addr,
1784-
remote_address);
1775+
returnacceptInitialPacket(
1776+
pversion_cid.version, dcid, scid, data, len, addr, remote_address);
17851777
}
17861778

17871779
if (session->is_destroyed()) [[unlikely]] {
@@ -1793,7 +1785,7 @@ void Endpoint::Receive(const uv_buf_t& buf,
17931785
// If we got here, the dcid matched the scid of a known local session. Yay!
17941786
// The session will take over any further processing of the packet.
17951787
Debug(this, "Dispatching packet to known session");
1796-
receive(session.get(), std::move(store), addr, remote_address, dcid, scid);
1788+
receive(session.get(), data, len, addr, remote_address, dcid, scid);
17971789

17981790
// It is important to note that the session may have been destroyed during
17991791
// the call to receive(...). If that's the case, the session object still

‎src/quic/endpoint.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,7 @@ class Endpoint final : public AsyncWrap, public Packet::Listener {
429429
// Ref() causes a listening Endpoint to keep the event loop active.
430430
JS_METHOD(Ref);
431431

432-
voidReceive(constuv_buf_t& buf, const SocketAddress& from);
432+
voidReceive(constuint8_t* data, size_t len, const SocketAddress& from);
433433

434434
AliasedStruct<Stats> stats_;
435435
AliasedStruct<State> state_;

‎src/quic/session.cc‎

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2109,7 +2109,8 @@ void Session::SetLastError(QuicError&& error) {
21092109
impl_->last_error_ = std::move(error);
21102110
}
21112111

2112-
boolSession::Receive(Store&& store,
2112+
boolSession::Receive(constuint8_t* data,
2113+
size_t len,
21132114
const SocketAddress& local_address,
21142115
const SocketAddress& remote_address,
21152116
const PacketInfo& pkt_info,
@@ -2120,24 +2121,23 @@ bool Session::Receive(Store&& store,
21202121
// The hot receive path uses ReadPacket() directly with deferred
21212122
// flush via BindingData's uv_check callback.
21222123
SendPendingDataScope send_scope(this);
2123-
returnReadPacket(
2124-
std::move(store), local_address, remote_address, pkt_info, ts);
2124+
returnReadPacket(data, len, local_address, remote_address, pkt_info, ts);
21252125
}
21262126

2127-
boolSession::ReadPacket(Store&& store,
2127+
boolSession::ReadPacket(constuint8_t* data,
2128+
size_t len,
21282129
const SocketAddress& local_address,
21292130
const SocketAddress& remote_address,
21302131
const PacketInfo& pkt_info,
21312132
uint64_t ts) {
21322133
DCHECK(!is_destroyed());
21332134
impl_->remote_address_ = remote_address;
21342135

2135-
ngtcp2_vec vec = store;
21362136
Path path(local_address, remote_address);
21372137

21382138
Debug(this,
21392139
"Session is receiving %zu-byte packet received along path %s",
2140-
vec.len,
2140+
len,
21412141
path);
21422142

21432143
// It is important to understand that reading the packet will cause
@@ -2158,19 +2158,18 @@ bool Session::ReadPacket(Store&& store,
21582158
// receive path caches a timestamp and passes it to all ReadPacket()
21592159
// calls in the same I/O burst.
21602160
if (ts == 0) ts = uv_hrtime();
2161-
err = ngtcp2_conn_read_pkt(
2162-
*this, &path, pkt_info, vec.base, vec.len, ts);
2161+
err = ngtcp2_conn_read_pkt(*this, &path, pkt_info, data, len, ts);
21632162
}
21642163
if (is_destroyed()) returnfalse;
21652164

2166-
Debug(this, "Session receiving %zu-byte packet with result %d", vec.len, err);
2165+
Debug(this, "Session receiving %zu-byte packet with result %d", len, err);
21672166

21682167
switch (err) {
21692168
case0: {
2170-
Debug(this, "Session successfully received %zu-byte packet", vec.len);
2169+
Debug(this, "Session successfully received %zu-byte packet", len);
21712170
if (!is_destroyed()) [[likely]] {
21722171
auto& stats_ = impl_->stats_;
2173-
STAT_INCREMENT_N(Stats, bytes_received, vec.len);
2172+
STAT_INCREMENT_N(Stats, bytes_received, len);
21742173
// Process deferred operations that couldn't run inside callback
21752174
// scopes (e.g., HTTP/3 GOAWAY handling that calls into JS).
21762175
application().PostReceive();

‎src/quic/session.h‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,8 @@ class Session final : public AsyncWrap, private SessionTicket::AppData::Source {
353353
bool early = false;
354354
};
355355

356-
boolReceive(Store&& store,
356+
boolReceive(constuint8_t* data,
357+
size_t len,
357358
const SocketAddress& local_address,
358359
const SocketAddress& remote_address,
359360
const PacketInfo& pkt_info = PacketInfo(),
@@ -367,10 +368,14 @@ class Session final : public AsyncWrap, private SessionTicket::AppData::Source {
367368
// Receive() is kept as a convenience wrapper that calls ReadPacket()
368369
// then triggers SendPendingData (for paths like Connect that need
369370
// immediate response).
371+
// The data pointer is used synchronously — ngtcp2_conn_read_pkt does
372+
// not retain a reference after returning, so the caller's buffer can
373+
// be reused immediately.
370374
// When ts is 0 (the default), uv_hrtime() is called internally.
371375
// The batched receive path caches a timestamp and passes it to all
372376
// ReadPacket() calls in the same I/O burst.
373-
boolReadPacket(Store&& store,
377+
boolReadPacket(constuint8_t* data,
378+
size_t len,
374379
const SocketAddress& local_address,
375380
const SocketAddress& remote_address,
376381
const PacketInfo& pkt_info = PacketInfo(),

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit 098e3d7

Browse files
jasnelladuh95
authored andcommitted
quic: eliminate per-received datagram allocation
Signed-off-by: James M Snell <jasnell@gmail.com> Assisted-by: Opencode:Opus 4.6 PR-URL: #63267 Backport-PR-URL: #64675 Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent 53b05e2 commit 098e3d7

5 files changed

Lines changed: 68 additions & 68 deletions

File tree

‎src/quic/application.cc‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -448,9 +448,13 @@ void Session::Application::SendPendingData() {
448448

449449
// Awesome, let's write our packet!
450450
PacketInfo pi;
451-
ssize_t nwrite = WriteVStream(
452-
&path, &pi, packet->data(), &ndatalen, packet->length(),
453-
stream_data, ts);
451+
ssize_t nwrite = WriteVStream(&path,
452+
&pi,
453+
packet->data(),
454+
&ndatalen,
455+
packet->length(),
456+
stream_data,
457+
ts);
454458

455459
// When ndatalen is > 0, that's our indication that stream data was accepted
456460
// in to the packet. Yay!

‎src/quic/endpoint.cc‎

Lines changed: 43 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -311,10 +311,18 @@ class Endpoint::UDP::Impl final : public HandleWrap {
311311
SET_SELF_SIZE(Impl)
312312

313313
private:
314+
// Pre-allocated receive buffer. Reused across all datagrams because
315+
// ngtcp2_conn_read_pkt is synchronous — it copies what it needs and
316+
// does not retain a reference to the buffer after returning. This
317+
// eliminates a malloc(64KB)/free(64KB) cycle per received datagram.
318+
static constexpr size_t kRecvBufferSize = 65536; // UV__UDP_DGRAM_MAXSIZE
319+
char recv_buf_[kRecvBufferSize];
320+
314321
staticvoidOnAlloc(uv_handle_t* handle,
315322
size_t suggested_size,
316323
uv_buf_t* buf) {
317-
*buf = From(handle)->env()->allocate_managed_buffer(suggested_size);
324+
auto* impl = From(handle);
325+
*buf = uv_buf_init(impl->recv_buf_, kRecvBufferSize);
318326
}
319327

320328
staticvoidOnReceive(uv_udp_t* handle,
@@ -326,26 +334,22 @@ class Endpoint::UDP::Impl final : public HandleWrap {
326334
DCHECK_NOT_NULL(impl);
327335
DCHECK_NOT_NULL(impl->endpoint_);
328336

329-
auto release_buf = [&]() {
330-
if (buf->base != nullptr) impl->env()->release_managed_buffer(*buf);
331-
};
332-
333337
// Nothing to do in these cases. Specifically, if the nread
334338
// is zero or we have received a partial packet, we are just
335-
// going to ignore it.
339+
// going to ignore it. No buffer release needed — recv_buf_
340+
// is pre-allocated and reused.
336341
if (nread == 0 || flags & UV_UDP_PARTIAL) {
337-
release_buf();
338342
return;
339343
}
340344

341345
if (nread < 0) {
342-
release_buf();
343346
impl->endpoint_->Destroy(CloseContext::RECEIVE_FAILURE,
344347
static_cast<int>(nread));
345348
return;
346349
}
347350

348-
impl->endpoint_->Receive(uv_buf_init(buf->base, static_cast<size_t>(nread)),
351+
impl->endpoint_->Receive(reinterpret_cast<constuint8_t*>(buf->base),
352+
static_cast<size_t>(nread),
349353
SocketAddress(addr));
350354
}
351355

@@ -1264,24 +1268,25 @@ void Endpoint::CloseGracefully() {
12641268
MaybeDestroy();
12651269
}
12661270

1267-
voidEndpoint::Receive(constuv_buf_t& buf,
1271+
voidEndpoint::Receive(constuint8_t* data,
1272+
size_t len,
12681273
const SocketAddress& remote_address) {
12691274
constauto receive = [&](Session* session,
1270-
Store&& store,
1275+
constuint8_t* pkt_data,
1276+
size_t pkt_len,
12711277
const SocketAddress& local_address,
12721278
const SocketAddress& remote_address,
12731279
constCID& dcid,
12741280
constCID& scid) {
12751281
DCHECK_NOT_NULL(session);
12761282
if (session->is_destroyed()) return;
1277-
size_t len = store.length();
12781283
// Use ReadPacket (no SendPendingDataScope) so that multiple packets
12791284
// received in the same I/O burst are processed before any responses
12801285
// are generated. The deferred flush via BindingData's uv_check
12811286
// callback calls SendPendingData once per dirty session after all
12821287
// packets in the burst have been read.
1283-
if (session->ReadPacket(std::move(store), local_address, remote_address)) {
1284-
STAT_INCREMENT_N(Stats, bytes_received, len);
1288+
if (session->ReadPacket(pkt_data, pkt_len, local_address, remote_address)) {
1289+
STAT_INCREMENT_N(Stats, bytes_received, pkt_len);
12851290
STAT_INCREMENT(Stats, packets_received);
12861291
}
12871292
// Schedule the session for deferred SendPendingData if it hasn't
@@ -1293,7 +1298,9 @@ void Endpoint::Receive(const uv_buf_t& buf,
12931298
}
12941299
};
12951300

1296-
constauto accept = [&](const Session::Config& config, Store&& store) {
1301+
constauto accept = [&](const Session::Config& config,
1302+
constuint8_t* pkt_data,
1303+
size_t pkt_len) {
12971304
// One final check. If the endpoint is closed, closing, or is not listening
12981305
// as a server, then we cannot accept the initial packet.
12991306
if (is_closed() || is_closing() || !is_listening()) return;
@@ -1323,7 +1330,8 @@ void Endpoint::Receive(const uv_buf_t& buf,
13231330
return;
13241331

13251332
receive(session.get(),
1326-
std::move(store),
1333+
pkt_data,
1334+
pkt_len,
13271335
config.local_address,
13281336
config.remote_address,
13291337
config.dcid,
@@ -1333,7 +1341,8 @@ void Endpoint::Receive(const uv_buf_t& buf,
13331341
constauto acceptInitialPacket = [&](constuint32_t version,
13341342
constCID& dcid,
13351343
constCID& scid,
1336-
Store&& store,
1344+
constuint8_t* pkt_data,
1345+
size_t pkt_len,
13371346
const SocketAddress& local_address,
13381347
const SocketAddress& remote_address) {
13391348
// If we're not listening as a server, do not accept an initial packet.
@@ -1343,8 +1352,7 @@ void Endpoint::Receive(const uv_buf_t& buf,
13431352

13441353
// This is our first condition check... A minimal check to see if ngtcp2 can
13451354
// even recognize this packet as a quic packet.
1346-
ngtcp2_vec vec = store;
1347-
if (ngtcp2_accept(&hd, vec.base, vec.len) != NGTCP2_SUCCESS) {
1355+
if (ngtcp2_accept(&hd, pkt_data, pkt_len) != NGTCP2_SUCCESS) {
13481356
// Per the ngtcp2 docs, ngtcp2_accept returns 0 if the check was
13491357
// successful, or an error code if it was not. Currently there's only one
13501358
// documented error code (NGTCP2_ERR_INVALID_ARGUMENT) but we'll handle
@@ -1582,7 +1590,7 @@ void Endpoint::Receive(const uv_buf_t& buf,
15821590
}
15831591
}
15841592

1585-
accept(config, std::move(store));
1593+
accept(config, pkt_data, pkt_len);
15861594
};
15871595

15881596
// When a received packet contains a QUIC short header but cannot be matched
@@ -1598,35 +1606,37 @@ void Endpoint::Receive(const uv_buf_t& buf,
15981606
// possible to avoid a DOS vector.
15991607
constauto maybeStatelessReset = [&](constCID& dcid,
16001608
constCID& scid,
1601-
Store& store,
1609+
constuint8_t* pkt_data,
1610+
size_t pkt_len,
16021611
const SocketAddress& local_address,
16031612
const SocketAddress& remote_address) {
16041613
// Support for stateless resets can be disabled by the application. If that
16051614
// case, or if the packet is too short to contain a reset token, then we
16061615
// skip the remaining checks.
16071616
if (options_.disable_stateless_reset ||
1608-
store.length() < NGTCP2_STATELESS_RESET_TOKENLEN) {
1617+
pkt_len < NGTCP2_STATELESS_RESET_TOKENLEN) {
16091618
returnfalse;
16101619
}
16111620

16121621
// The stateless reset token itself is the *final*
16131622
// NGTCP2_STATELESS_RESET_TOKENLEN bytes in the received packet. If it is a
16141623
// stateless reset then then rest of the bytes in the packet are garbage
16151624
// that we'll ignore.
1616-
ngtcp2_vec vec = store;
1617-
vec.base += (vec.len - NGTCP2_STATELESS_RESET_TOKENLEN);
1625+
constuint8_t* token_pos =
1626+
pkt_data + (pkt_len - NGTCP2_STATELESS_RESET_TOKENLEN);
16181627

16191628
// If a Session has been associated with the token, then it is a valid
16201629
// stateless reset token. We need to dispatch it to the session to be
16211630
// processed.
16221631
auto* session = session_manager().FindSessionByStatelessResetToken(
1623-
StatelessResetToken(vec.base));
1632+
StatelessResetToken(token_pos));
16241633
if (session != nullptr) {
16251634
// If the session happens to have been destroyed already, we'll
16261635
// just ignore the packet.
16271636
if (!session->is_destroyed()) [[likely]] {
16281637
receive(session,
1629-
std::move(store),
1638+
pkt_data,
1639+
pkt_len,
16301640
local_address,
16311641
remote_address,
16321642
dcid,
@@ -1654,22 +1664,8 @@ void Endpoint::Receive(const uv_buf_t& buf,
16541664
// return;
16551665
// }
16561666

1657-
Debug(this, "Received %zu-byte packet from %s", buf.len, remote_address);
1658-
1659-
// The managed buffer here contains the received packet. We do not yet know
1660-
// at this point if it is a valid QUIC packet. We need to do some basic
1661-
// checks. It is critical at this point that we do as little work as possible
1662-
// to avoid a DOS vector.
1663-
std::shared_ptr<BackingStore> backing = env()->release_managed_buffer(buf);
1664-
if (!backing) [[unlikely]] {
1665-
// At this point something bad happened and we need to treat this as a fatal
1666-
// case. There's likely no way to test this specific condition reliably.
1667-
returnDestroy(CloseContext::RECEIVE_FAILURE, UV_ENOMEM);
1668-
}
1669-
1670-
Store store(std::move(backing), buf.len, 0);
1667+
Debug(this, "Received %zu-byte packet from %s", len, remote_address);
16711668

1672-
ngtcp2_vec vec = store;
16731669
ngtcp2_version_cid pversion_cid;
16741670

16751671
// This is our first check to see if the received data can be processed as a
@@ -1678,7 +1674,7 @@ void Endpoint::Receive(const uv_buf_t& buf,
16781674
// valid QUIC header but there is still no guarantee that the packet can be
16791675
// successfully processed.
16801676
switch (ngtcp2_pkt_decode_version_cid(
1681-
&pversion_cid, vec.base, vec.len, NGTCP2_MAX_CIDLEN)) {
1677+
&pversion_cid, data, len, NGTCP2_MAX_CIDLEN)) {
16821678
case0:
16831679
break; // Supported version, continue processing.
16841680
caseNGTCP2_ERR_VERSION_NEGOTIATION: {
@@ -1756,7 +1752,7 @@ void Endpoint::Receive(const uv_buf_t& buf,
17561752
// necessary here. We want to return immediately without committing any
17571753
// further resources.
17581754
if (pversion_cid.version == 0 &&
1759-
maybeStatelessReset(dcid, scid, store, addr, remote_address)) {
1755+
maybeStatelessReset(dcid, scid, data, len, addr, remote_address)) {
17601756
Debug(this, "Packet was a stateless reset");
17611757
return; // Stateless reset! Don't do any further processing.
17621758
}
@@ -1771,17 +1767,13 @@ void Endpoint::Receive(const uv_buf_t& buf,
17711767
SendStatelessReset(
17721768
PathDescriptor{
17731769
pversion_cid.version, dcid, scid, addr, remote_address},
1774-
store.length());
1770+
len);
17751771
return;
17761772
}
17771773

17781774
// Process the packet as an initial packet...
1779-
returnacceptInitialPacket(pversion_cid.version,
1780-
dcid,
1781-
scid,
1782-
std::move(store),
1783-
addr,
1784-
remote_address);
1775+
returnacceptInitialPacket(
1776+
pversion_cid.version, dcid, scid, data, len, addr, remote_address);
17851777
}
17861778

17871779
if (session->is_destroyed()) [[unlikely]] {
@@ -1793,7 +1785,7 @@ void Endpoint::Receive(const uv_buf_t& buf,
17931785
// If we got here, the dcid matched the scid of a known local session. Yay!
17941786
// The session will take over any further processing of the packet.
17951787
Debug(this, "Dispatching packet to known session");
1796-
receive(session.get(), std::move(store), addr, remote_address, dcid, scid);
1788+
receive(session.get(), data, len, addr, remote_address, dcid, scid);
17971789

17981790
// It is important to note that the session may have been destroyed during
17991791
// the call to receive(...). If that's the case, the session object still

‎src/quic/endpoint.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,7 @@ class Endpoint final : public AsyncWrap, public Packet::Listener {
429429
// Ref() causes a listening Endpoint to keep the event loop active.
430430
JS_METHOD(Ref);
431431

432-
voidReceive(constuv_buf_t& buf, const SocketAddress& from);
432+
voidReceive(constuint8_t* data, size_t len, const SocketAddress& from);
433433

434434
AliasedStruct<Stats> stats_;
435435
AliasedStruct<State> state_;

‎src/quic/session.cc‎

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2109,7 +2109,8 @@ void Session::SetLastError(QuicError&& error) {
21092109
impl_->last_error_ = std::move(error);
21102110
}
21112111

2112-
boolSession::Receive(Store&& store,
2112+
boolSession::Receive(constuint8_t* data,
2113+
size_t len,
21132114
const SocketAddress& local_address,
21142115
const SocketAddress& remote_address,
21152116
const PacketInfo& pkt_info,
@@ -2120,24 +2121,23 @@ bool Session::Receive(Store&& store,
21202121
// The hot receive path uses ReadPacket() directly with deferred
21212122
// flush via BindingData's uv_check callback.
21222123
SendPendingDataScope send_scope(this);
2123-
returnReadPacket(
2124-
std::move(store), local_address, remote_address, pkt_info, ts);
2124+
returnReadPacket(data, len, local_address, remote_address, pkt_info, ts);
21252125
}
21262126

2127-
boolSession::ReadPacket(Store&& store,
2127+
boolSession::ReadPacket(constuint8_t* data,
2128+
size_t len,
21282129
const SocketAddress& local_address,
21292130
const SocketAddress& remote_address,
21302131
const PacketInfo& pkt_info,
21312132
uint64_t ts) {
21322133
DCHECK(!is_destroyed());
21332134
impl_->remote_address_ = remote_address;
21342135

2135-
ngtcp2_vec vec = store;
21362136
Path path(local_address, remote_address);
21372137

21382138
Debug(this,
21392139
"Session is receiving %zu-byte packet received along path %s",
2140-
vec.len,
2140+
len,
21412141
path);
21422142

21432143
// It is important to understand that reading the packet will cause
@@ -2158,19 +2158,18 @@ bool Session::ReadPacket(Store&& store,
21582158
// receive path caches a timestamp and passes it to all ReadPacket()
21592159
// calls in the same I/O burst.
21602160
if (ts == 0) ts = uv_hrtime();
2161-
err = ngtcp2_conn_read_pkt(
2162-
*this, &path, pkt_info, vec.base, vec.len, ts);
2161+
err = ngtcp2_conn_read_pkt(*this, &path, pkt_info, data, len, ts);
21632162
}
21642163
if (is_destroyed()) returnfalse;
21652164

2166-
Debug(this, "Session receiving %zu-byte packet with result %d", vec.len, err);
2165+
Debug(this, "Session receiving %zu-byte packet with result %d", len, err);
21672166

21682167
switch (err) {
21692168
case0: {
2170-
Debug(this, "Session successfully received %zu-byte packet", vec.len);
2169+
Debug(this, "Session successfully received %zu-byte packet", len);
21712170
if (!is_destroyed()) [[likely]] {
21722171
auto& stats_ = impl_->stats_;
2173-
STAT_INCREMENT_N(Stats, bytes_received, vec.len);
2172+
STAT_INCREMENT_N(Stats, bytes_received, len);
21742173
// Process deferred operations that couldn't run inside callback
21752174
// scopes (e.g., HTTP/3 GOAWAY handling that calls into JS).
21762175
application().PostReceive();

‎src/quic/session.h‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,8 @@ class Session final : public AsyncWrap, private SessionTicket::AppData::Source {
353353
bool early = false;
354354
};
355355

356-
boolReceive(Store&& store,
356+
boolReceive(constuint8_t* data,
357+
size_t len,
357358
const SocketAddress& local_address,
358359
const SocketAddress& remote_address,
359360
const PacketInfo& pkt_info = PacketInfo(),
@@ -367,10 +368,14 @@ class Session final : public AsyncWrap, private SessionTicket::AppData::Source {
367368
// Receive() is kept as a convenience wrapper that calls ReadPacket()
368369
// then triggers SendPendingData (for paths like Connect that need
369370
// immediate response).
371+
// The data pointer is used synchronously — ngtcp2_conn_read_pkt does
372+
// not retain a reference after returning, so the caller's buffer can
373+
// be reused immediately.
370374
// When ts is 0 (the default), uv_hrtime() is called internally.
371375
// The batched receive path caches a timestamp and passes it to all
372376
// ReadPacket() calls in the same I/O burst.
373-
boolReadPacket(Store&& store,
377+
boolReadPacket(constuint8_t* data,
378+
size_t len,
374379
const SocketAddress& local_address,
375380
const SocketAddress& remote_address,
376381
const PacketInfo& pkt_info = PacketInfo(),

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit 098e3d7

Browse files
jasnelladuh95
authored andcommitted
quic: eliminate per-received datagram allocation
Signed-off-by: James M Snell <jasnell@gmail.com> Assisted-by: Opencode:Opus 4.6 PR-URL: #63267 Backport-PR-URL: #64675 Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent 53b05e2 commit 098e3d7

5 files changed

Lines changed: 68 additions & 68 deletions

File tree

‎src/quic/application.cc‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -448,9 +448,13 @@ void Session::Application::SendPendingData() {
448448

449449
// Awesome, let's write our packet!
450450
PacketInfo pi;
451-
ssize_t nwrite = WriteVStream(
452-
&path, &pi, packet->data(), &ndatalen, packet->length(),
453-
stream_data, ts);
451+
ssize_t nwrite = WriteVStream(&path,
452+
&pi,
453+
packet->data(),
454+
&ndatalen,
455+
packet->length(),
456+
stream_data,
457+
ts);
454458

455459
// When ndatalen is > 0, that's our indication that stream data was accepted
456460
// in to the packet. Yay!

‎src/quic/endpoint.cc‎

Lines changed: 43 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -311,10 +311,18 @@ class Endpoint::UDP::Impl final : public HandleWrap {
311311
SET_SELF_SIZE(Impl)
312312

313313
private:
314+
// Pre-allocated receive buffer. Reused across all datagrams because
315+
// ngtcp2_conn_read_pkt is synchronous — it copies what it needs and
316+
// does not retain a reference to the buffer after returning. This
317+
// eliminates a malloc(64KB)/free(64KB) cycle per received datagram.
318+
static constexpr size_t kRecvBufferSize = 65536; // UV__UDP_DGRAM_MAXSIZE
319+
char recv_buf_[kRecvBufferSize];
320+
314321
staticvoidOnAlloc(uv_handle_t* handle,
315322
size_t suggested_size,
316323
uv_buf_t* buf) {
317-
*buf = From(handle)->env()->allocate_managed_buffer(suggested_size);
324+
auto* impl = From(handle);
325+
*buf = uv_buf_init(impl->recv_buf_, kRecvBufferSize);
318326
}
319327

320328
staticvoidOnReceive(uv_udp_t* handle,
@@ -326,26 +334,22 @@ class Endpoint::UDP::Impl final : public HandleWrap {
326334
DCHECK_NOT_NULL(impl);
327335
DCHECK_NOT_NULL(impl->endpoint_);
328336

329-
auto release_buf = [&]() {
330-
if (buf->base != nullptr) impl->env()->release_managed_buffer(*buf);
331-
};
332-
333337
// Nothing to do in these cases. Specifically, if the nread
334338
// is zero or we have received a partial packet, we are just
335-
// going to ignore it.
339+
// going to ignore it. No buffer release needed — recv_buf_
340+
// is pre-allocated and reused.
336341
if (nread == 0 || flags & UV_UDP_PARTIAL) {
337-
release_buf();
338342
return;
339343
}
340344

341345
if (nread < 0) {
342-
release_buf();
343346
impl->endpoint_->Destroy(CloseContext::RECEIVE_FAILURE,
344347
static_cast<int>(nread));
345348
return;
346349
}
347350

348-
impl->endpoint_->Receive(uv_buf_init(buf->base, static_cast<size_t>(nread)),
351+
impl->endpoint_->Receive(reinterpret_cast<constuint8_t*>(buf->base),
352+
static_cast<size_t>(nread),
349353
SocketAddress(addr));
350354
}
351355

@@ -1264,24 +1268,25 @@ void Endpoint::CloseGracefully() {
12641268
MaybeDestroy();
12651269
}
12661270

1267-
voidEndpoint::Receive(constuv_buf_t& buf,
1271+
voidEndpoint::Receive(constuint8_t* data,
1272+
size_t len,
12681273
const SocketAddress& remote_address) {
12691274
constauto receive = [&](Session* session,
1270-
Store&& store,
1275+
constuint8_t* pkt_data,
1276+
size_t pkt_len,
12711277
const SocketAddress& local_address,
12721278
const SocketAddress& remote_address,
12731279
constCID& dcid,
12741280
constCID& scid) {
12751281
DCHECK_NOT_NULL(session);
12761282
if (session->is_destroyed()) return;
1277-
size_t len = store.length();
12781283
// Use ReadPacket (no SendPendingDataScope) so that multiple packets
12791284
// received in the same I/O burst are processed before any responses
12801285
// are generated. The deferred flush via BindingData's uv_check
12811286
// callback calls SendPendingData once per dirty session after all
12821287
// packets in the burst have been read.
1283-
if (session->ReadPacket(std::move(store), local_address, remote_address)) {
1284-
STAT_INCREMENT_N(Stats, bytes_received, len);
1288+
if (session->ReadPacket(pkt_data, pkt_len, local_address, remote_address)) {
1289+
STAT_INCREMENT_N(Stats, bytes_received, pkt_len);
12851290
STAT_INCREMENT(Stats, packets_received);
12861291
}
12871292
// Schedule the session for deferred SendPendingData if it hasn't
@@ -1293,7 +1298,9 @@ void Endpoint::Receive(const uv_buf_t& buf,
12931298
}
12941299
};
12951300

1296-
constauto accept = [&](const Session::Config& config, Store&& store) {
1301+
constauto accept = [&](const Session::Config& config,
1302+
constuint8_t* pkt_data,
1303+
size_t pkt_len) {
12971304
// One final check. If the endpoint is closed, closing, or is not listening
12981305
// as a server, then we cannot accept the initial packet.
12991306
if (is_closed() || is_closing() || !is_listening()) return;
@@ -1323,7 +1330,8 @@ void Endpoint::Receive(const uv_buf_t& buf,
13231330
return;
13241331

13251332
receive(session.get(),
1326-
std::move(store),
1333+
pkt_data,
1334+
pkt_len,
13271335
config.local_address,
13281336
config.remote_address,
13291337
config.dcid,
@@ -1333,7 +1341,8 @@ void Endpoint::Receive(const uv_buf_t& buf,
13331341
constauto acceptInitialPacket = [&](constuint32_t version,
13341342
constCID& dcid,
13351343
constCID& scid,
1336-
Store&& store,
1344+
constuint8_t* pkt_data,
1345+
size_t pkt_len,
13371346
const SocketAddress& local_address,
13381347
const SocketAddress& remote_address) {
13391348
// If we're not listening as a server, do not accept an initial packet.
@@ -1343,8 +1352,7 @@ void Endpoint::Receive(const uv_buf_t& buf,
13431352

13441353
// This is our first condition check... A minimal check to see if ngtcp2 can
13451354
// even recognize this packet as a quic packet.
1346-
ngtcp2_vec vec = store;
1347-
if (ngtcp2_accept(&hd, vec.base, vec.len) != NGTCP2_SUCCESS) {
1355+
if (ngtcp2_accept(&hd, pkt_data, pkt_len) != NGTCP2_SUCCESS) {
13481356
// Per the ngtcp2 docs, ngtcp2_accept returns 0 if the check was
13491357
// successful, or an error code if it was not. Currently there's only one
13501358
// documented error code (NGTCP2_ERR_INVALID_ARGUMENT) but we'll handle
@@ -1582,7 +1590,7 @@ void Endpoint::Receive(const uv_buf_t& buf,
15821590
}
15831591
}
15841592

1585-
accept(config, std::move(store));
1593+
accept(config, pkt_data, pkt_len);
15861594
};
15871595

15881596
// When a received packet contains a QUIC short header but cannot be matched
@@ -1598,35 +1606,37 @@ void Endpoint::Receive(const uv_buf_t& buf,
15981606
// possible to avoid a DOS vector.
15991607
constauto maybeStatelessReset = [&](constCID& dcid,
16001608
constCID& scid,
1601-
Store& store,
1609+
constuint8_t* pkt_data,
1610+
size_t pkt_len,
16021611
const SocketAddress& local_address,
16031612
const SocketAddress& remote_address) {
16041613
// Support for stateless resets can be disabled by the application. If that
16051614
// case, or if the packet is too short to contain a reset token, then we
16061615
// skip the remaining checks.
16071616
if (options_.disable_stateless_reset ||
1608-
store.length() < NGTCP2_STATELESS_RESET_TOKENLEN) {
1617+
pkt_len < NGTCP2_STATELESS_RESET_TOKENLEN) {
16091618
returnfalse;
16101619
}
16111620

16121621
// The stateless reset token itself is the *final*
16131622
// NGTCP2_STATELESS_RESET_TOKENLEN bytes in the received packet. If it is a
16141623
// stateless reset then then rest of the bytes in the packet are garbage
16151624
// that we'll ignore.
1616-
ngtcp2_vec vec = store;
1617-
vec.base += (vec.len - NGTCP2_STATELESS_RESET_TOKENLEN);
1625+
constuint8_t* token_pos =
1626+
pkt_data + (pkt_len - NGTCP2_STATELESS_RESET_TOKENLEN);
16181627

16191628
// If a Session has been associated with the token, then it is a valid
16201629
// stateless reset token. We need to dispatch it to the session to be
16211630
// processed.
16221631
auto* session = session_manager().FindSessionByStatelessResetToken(
1623-
StatelessResetToken(vec.base));
1632+
StatelessResetToken(token_pos));
16241633
if (session != nullptr) {
16251634
// If the session happens to have been destroyed already, we'll
16261635
// just ignore the packet.
16271636
if (!session->is_destroyed()) [[likely]] {
16281637
receive(session,
1629-
std::move(store),
1638+
pkt_data,
1639+
pkt_len,
16301640
local_address,
16311641
remote_address,
16321642
dcid,
@@ -1654,22 +1664,8 @@ void Endpoint::Receive(const uv_buf_t& buf,
16541664
// return;
16551665
// }
16561666

1657-
Debug(this, "Received %zu-byte packet from %s", buf.len, remote_address);
1658-
1659-
// The managed buffer here contains the received packet. We do not yet know
1660-
// at this point if it is a valid QUIC packet. We need to do some basic
1661-
// checks. It is critical at this point that we do as little work as possible
1662-
// to avoid a DOS vector.
1663-
std::shared_ptr<BackingStore> backing = env()->release_managed_buffer(buf);
1664-
if (!backing) [[unlikely]] {
1665-
// At this point something bad happened and we need to treat this as a fatal
1666-
// case. There's likely no way to test this specific condition reliably.
1667-
returnDestroy(CloseContext::RECEIVE_FAILURE, UV_ENOMEM);
1668-
}
1669-
1670-
Store store(std::move(backing), buf.len, 0);
1667+
Debug(this, "Received %zu-byte packet from %s", len, remote_address);
16711668

1672-
ngtcp2_vec vec = store;
16731669
ngtcp2_version_cid pversion_cid;
16741670

16751671
// This is our first check to see if the received data can be processed as a
@@ -1678,7 +1674,7 @@ void Endpoint::Receive(const uv_buf_t& buf,
16781674
// valid QUIC header but there is still no guarantee that the packet can be
16791675
// successfully processed.
16801676
switch (ngtcp2_pkt_decode_version_cid(
1681-
&pversion_cid, vec.base, vec.len, NGTCP2_MAX_CIDLEN)) {
1677+
&pversion_cid, data, len, NGTCP2_MAX_CIDLEN)) {
16821678
case0:
16831679
break; // Supported version, continue processing.
16841680
caseNGTCP2_ERR_VERSION_NEGOTIATION: {
@@ -1756,7 +1752,7 @@ void Endpoint::Receive(const uv_buf_t& buf,
17561752
// necessary here. We want to return immediately without committing any
17571753
// further resources.
17581754
if (pversion_cid.version == 0 &&
1759-
maybeStatelessReset(dcid, scid, store, addr, remote_address)) {
1755+
maybeStatelessReset(dcid, scid, data, len, addr, remote_address)) {
17601756
Debug(this, "Packet was a stateless reset");
17611757
return; // Stateless reset! Don't do any further processing.
17621758
}
@@ -1771,17 +1767,13 @@ void Endpoint::Receive(const uv_buf_t& buf,
17711767
SendStatelessReset(
17721768
PathDescriptor{
17731769
pversion_cid.version, dcid, scid, addr, remote_address},
1774-
store.length());
1770+
len);
17751771
return;
17761772
}
17771773

17781774
// Process the packet as an initial packet...
1779-
returnacceptInitialPacket(pversion_cid.version,
1780-
dcid,
1781-
scid,
1782-
std::move(store),
1783-
addr,
1784-
remote_address);
1775+
returnacceptInitialPacket(
1776+
pversion_cid.version, dcid, scid, data, len, addr, remote_address);
17851777
}
17861778

17871779
if (session->is_destroyed()) [[unlikely]] {
@@ -1793,7 +1785,7 @@ void Endpoint::Receive(const uv_buf_t& buf,
17931785
// If we got here, the dcid matched the scid of a known local session. Yay!
17941786
// The session will take over any further processing of the packet.
17951787
Debug(this, "Dispatching packet to known session");
1796-
receive(session.get(), std::move(store), addr, remote_address, dcid, scid);
1788+
receive(session.get(), data, len, addr, remote_address, dcid, scid);
17971789

17981790
// It is important to note that the session may have been destroyed during
17991791
// the call to receive(...). If that's the case, the session object still

‎src/quic/endpoint.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,7 @@ class Endpoint final : public AsyncWrap, public Packet::Listener {
429429
// Ref() causes a listening Endpoint to keep the event loop active.
430430
JS_METHOD(Ref);
431431

432-
voidReceive(constuv_buf_t& buf, const SocketAddress& from);
432+
voidReceive(constuint8_t* data, size_t len, const SocketAddress& from);
433433

434434
AliasedStruct<Stats> stats_;
435435
AliasedStruct<State> state_;

‎src/quic/session.cc‎

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2109,7 +2109,8 @@ void Session::SetLastError(QuicError&& error) {
21092109
impl_->last_error_ = std::move(error);
21102110
}
21112111

2112-
boolSession::Receive(Store&& store,
2112+
boolSession::Receive(constuint8_t* data,
2113+
size_t len,
21132114
const SocketAddress& local_address,
21142115
const SocketAddress& remote_address,
21152116
const PacketInfo& pkt_info,
@@ -2120,24 +2121,23 @@ bool Session::Receive(Store&& store,
21202121
// The hot receive path uses ReadPacket() directly with deferred
21212122
// flush via BindingData's uv_check callback.
21222123
SendPendingDataScope send_scope(this);
2123-
returnReadPacket(
2124-
std::move(store), local_address, remote_address, pkt_info, ts);
2124+
returnReadPacket(data, len, local_address, remote_address, pkt_info, ts);
21252125
}
21262126

2127-
boolSession::ReadPacket(Store&& store,
2127+
boolSession::ReadPacket(constuint8_t* data,
2128+
size_t len,
21282129
const SocketAddress& local_address,
21292130
const SocketAddress& remote_address,
21302131
const PacketInfo& pkt_info,
21312132
uint64_t ts) {
21322133
DCHECK(!is_destroyed());
21332134
impl_->remote_address_ = remote_address;
21342135

2135-
ngtcp2_vec vec = store;
21362136
Path path(local_address, remote_address);
21372137

21382138
Debug(this,
21392139
"Session is receiving %zu-byte packet received along path %s",
2140-
vec.len,
2140+
len,
21412141
path);
21422142

21432143
// It is important to understand that reading the packet will cause
@@ -2158,19 +2158,18 @@ bool Session::ReadPacket(Store&& store,
21582158
// receive path caches a timestamp and passes it to all ReadPacket()
21592159
// calls in the same I/O burst.
21602160
if (ts == 0) ts = uv_hrtime();
2161-
err = ngtcp2_conn_read_pkt(
2162-
*this, &path, pkt_info, vec.base, vec.len, ts);
2161+
err = ngtcp2_conn_read_pkt(*this, &path, pkt_info, data, len, ts);
21632162
}
21642163
if (is_destroyed()) returnfalse;
21652164

2166-
Debug(this, "Session receiving %zu-byte packet with result %d", vec.len, err);
2165+
Debug(this, "Session receiving %zu-byte packet with result %d", len, err);
21672166

21682167
switch (err) {
21692168
case0: {
2170-
Debug(this, "Session successfully received %zu-byte packet", vec.len);
2169+
Debug(this, "Session successfully received %zu-byte packet", len);
21712170
if (!is_destroyed()) [[likely]] {
21722171
auto& stats_ = impl_->stats_;
2173-
STAT_INCREMENT_N(Stats, bytes_received, vec.len);
2172+
STAT_INCREMENT_N(Stats, bytes_received, len);
21742173
// Process deferred operations that couldn't run inside callback
21752174
// scopes (e.g., HTTP/3 GOAWAY handling that calls into JS).
21762175
application().PostReceive();

‎src/quic/session.h‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,8 @@ class Session final : public AsyncWrap, private SessionTicket::AppData::Source {
353353
bool early = false;
354354
};
355355

356-
boolReceive(Store&& store,
356+
boolReceive(constuint8_t* data,
357+
size_t len,
357358
const SocketAddress& local_address,
358359
const SocketAddress& remote_address,
359360
const PacketInfo& pkt_info = PacketInfo(),
@@ -367,10 +368,14 @@ class Session final : public AsyncWrap, private SessionTicket::AppData::Source {
367368
// Receive() is kept as a convenience wrapper that calls ReadPacket()
368369
// then triggers SendPendingData (for paths like Connect that need
369370
// immediate response).
371+
// The data pointer is used synchronously — ngtcp2_conn_read_pkt does
372+
// not retain a reference after returning, so the caller's buffer can
373+
// be reused immediately.
370374
// When ts is 0 (the default), uv_hrtime() is called internally.
371375
// The batched receive path caches a timestamp and passes it to all
372376
// ReadPacket() calls in the same I/O burst.
373-
boolReadPacket(Store&& store,
377+
boolReadPacket(constuint8_t* data,
378+
size_t len,
374379
const SocketAddress& local_address,
375380
const SocketAddress& remote_address,
376381
const PacketInfo& pkt_info = PacketInfo(),

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Commit 098e3d7

Browse files
jasnelladuh95
authored andcommitted
quic: eliminate per-received datagram allocation
Signed-off-by: James M Snell <jasnell@gmail.com> Assisted-by: Opencode:Opus 4.6 PR-URL: #63267 Backport-PR-URL: #64675 Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent 53b05e2 commit 098e3d7

5 files changed

Lines changed: 68 additions & 68 deletions

File tree

‎src/quic/application.cc‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -448,9 +448,13 @@ void Session::Application::SendPendingData() {
448448

449449
// Awesome, let's write our packet!
450450
PacketInfo pi;
451-
ssize_t nwrite = WriteVStream(
452-
&path, &pi, packet->data(), &ndatalen, packet->length(),
453-
stream_data, ts);
451+
ssize_t nwrite = WriteVStream(&path,
452+
&pi,
453+
packet->data(),
454+
&ndatalen,
455+
packet->length(),
456+
stream_data,
457+
ts);
454458

455459
// When ndatalen is > 0, that's our indication that stream data was accepted
456460
// in to the packet. Yay!

‎src/quic/endpoint.cc‎

Lines changed: 43 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -311,10 +311,18 @@ class Endpoint::UDP::Impl final : public HandleWrap {
311311
SET_SELF_SIZE(Impl)
312312

313313
private:
314+
// Pre-allocated receive buffer. Reused across all datagrams because
315+
// ngtcp2_conn_read_pkt is synchronous — it copies what it needs and
316+
// does not retain a reference to the buffer after returning. This
317+
// eliminates a malloc(64KB)/free(64KB) cycle per received datagram.
318+
static constexpr size_t kRecvBufferSize = 65536; // UV__UDP_DGRAM_MAXSIZE
319+
char recv_buf_[kRecvBufferSize];
320+
314321
staticvoidOnAlloc(uv_handle_t* handle,
315322
size_t suggested_size,
316323
uv_buf_t* buf) {
317-
*buf = From(handle)->env()->allocate_managed_buffer(suggested_size);
324+
auto* impl = From(handle);
325+
*buf = uv_buf_init(impl->recv_buf_, kRecvBufferSize);
318326
}
319327

320328
staticvoidOnReceive(uv_udp_t* handle,
@@ -326,26 +334,22 @@ class Endpoint::UDP::Impl final : public HandleWrap {
326334
DCHECK_NOT_NULL(impl);
327335
DCHECK_NOT_NULL(impl->endpoint_);
328336

329-
auto release_buf = [&]() {
330-
if (buf->base != nullptr) impl->env()->release_managed_buffer(*buf);
331-
};
332-
333337
// Nothing to do in these cases. Specifically, if the nread
334338
// is zero or we have received a partial packet, we are just
335-
// going to ignore it.
339+
// going to ignore it. No buffer release needed — recv_buf_
340+
// is pre-allocated and reused.
336341
if (nread == 0 || flags & UV_UDP_PARTIAL) {
337-
release_buf();
338342
return;
339343
}
340344

341345
if (nread < 0) {
342-
release_buf();
343346
impl->endpoint_->Destroy(CloseContext::RECEIVE_FAILURE,
344347
static_cast<int>(nread));
345348
return;
346349
}
347350

348-
impl->endpoint_->Receive(uv_buf_init(buf->base, static_cast<size_t>(nread)),
351+
impl->endpoint_->Receive(reinterpret_cast<constuint8_t*>(buf->base),
352+
static_cast<size_t>(nread),
349353
SocketAddress(addr));
350354
}
351355

@@ -1264,24 +1268,25 @@ void Endpoint::CloseGracefully() {
12641268
MaybeDestroy();
12651269
}
12661270

1267-
voidEndpoint::Receive(constuv_buf_t& buf,
1271+
voidEndpoint::Receive(constuint8_t* data,
1272+
size_t len,
12681273
const SocketAddress& remote_address) {
12691274
constauto receive = [&](Session* session,
1270-
Store&& store,
1275+
constuint8_t* pkt_data,
1276+
size_t pkt_len,
12711277
const SocketAddress& local_address,
12721278
const SocketAddress& remote_address,
12731279
constCID& dcid,
12741280
constCID& scid) {
12751281
DCHECK_NOT_NULL(session);
12761282
if (session->is_destroyed()) return;
1277-
size_t len = store.length();
12781283
// Use ReadPacket (no SendPendingDataScope) so that multiple packets
12791284
// received in the same I/O burst are processed before any responses
12801285
// are generated. The deferred flush via BindingData's uv_check
12811286
// callback calls SendPendingData once per dirty session after all
12821287
// packets in the burst have been read.
1283-
if (session->ReadPacket(std::move(store), local_address, remote_address)) {
1284-
STAT_INCREMENT_N(Stats, bytes_received, len);
1288+
if (session->ReadPacket(pkt_data, pkt_len, local_address, remote_address)) {
1289+
STAT_INCREMENT_N(Stats, bytes_received, pkt_len);
12851290
STAT_INCREMENT(Stats, packets_received);
12861291
}
12871292
// Schedule the session for deferred SendPendingData if it hasn't
@@ -1293,7 +1298,9 @@ void Endpoint::Receive(const uv_buf_t& buf,
12931298
}
12941299
};
12951300

1296-
constauto accept = [&](const Session::Config& config, Store&& store) {
1301+
constauto accept = [&](const Session::Config& config,
1302+
constuint8_t* pkt_data,
1303+
size_t pkt_len) {
12971304
// One final check. If the endpoint is closed, closing, or is not listening
12981305
// as a server, then we cannot accept the initial packet.
12991306
if (is_closed() || is_closing() || !is_listening()) return;
@@ -1323,7 +1330,8 @@ void Endpoint::Receive(const uv_buf_t& buf,
13231330
return;
13241331

13251332
receive(session.get(),
1326-
std::move(store),
1333+
pkt_data,
1334+
pkt_len,
13271335
config.local_address,
13281336
config.remote_address,
13291337
config.dcid,
@@ -1333,7 +1341,8 @@ void Endpoint::Receive(const uv_buf_t& buf,
13331341
constauto acceptInitialPacket = [&](constuint32_t version,
13341342
constCID& dcid,
13351343
constCID& scid,
1336-
Store&& store,
1344+
constuint8_t* pkt_data,
1345+
size_t pkt_len,
13371346
const SocketAddress& local_address,
13381347
const SocketAddress& remote_address) {
13391348
// If we're not listening as a server, do not accept an initial packet.
@@ -1343,8 +1352,7 @@ void Endpoint::Receive(const uv_buf_t& buf,
13431352

13441353
// This is our first condition check... A minimal check to see if ngtcp2 can
13451354
// even recognize this packet as a quic packet.
1346-
ngtcp2_vec vec = store;
1347-
if (ngtcp2_accept(&hd, vec.base, vec.len) != NGTCP2_SUCCESS) {
1355+
if (ngtcp2_accept(&hd, pkt_data, pkt_len) != NGTCP2_SUCCESS) {
13481356
// Per the ngtcp2 docs, ngtcp2_accept returns 0 if the check was
13491357
// successful, or an error code if it was not. Currently there's only one
13501358
// documented error code (NGTCP2_ERR_INVALID_ARGUMENT) but we'll handle
@@ -1582,7 +1590,7 @@ void Endpoint::Receive(const uv_buf_t& buf,
15821590
}
15831591
}
15841592

1585-
accept(config, std::move(store));
1593+
accept(config, pkt_data, pkt_len);
15861594
};
15871595

15881596
// When a received packet contains a QUIC short header but cannot be matched
@@ -1598,35 +1606,37 @@ void Endpoint::Receive(const uv_buf_t& buf,
15981606
// possible to avoid a DOS vector.
15991607
constauto maybeStatelessReset = [&](constCID& dcid,
16001608
constCID& scid,
1601-
Store& store,
1609+
constuint8_t* pkt_data,
1610+
size_t pkt_len,
16021611
const SocketAddress& local_address,
16031612
const SocketAddress& remote_address) {
16041613
// Support for stateless resets can be disabled by the application. If that
16051614
// case, or if the packet is too short to contain a reset token, then we
16061615
// skip the remaining checks.
16071616
if (options_.disable_stateless_reset ||
1608-
store.length() < NGTCP2_STATELESS_RESET_TOKENLEN) {
1617+
pkt_len < NGTCP2_STATELESS_RESET_TOKENLEN) {
16091618
returnfalse;
16101619
}
16111620

16121621
// The stateless reset token itself is the *final*
16131622
// NGTCP2_STATELESS_RESET_TOKENLEN bytes in the received packet. If it is a
16141623
// stateless reset then then rest of the bytes in the packet are garbage
16151624
// that we'll ignore.
1616-
ngtcp2_vec vec = store;
1617-
vec.base += (vec.len - NGTCP2_STATELESS_RESET_TOKENLEN);
1625+
constuint8_t* token_pos =
1626+
pkt_data + (pkt_len - NGTCP2_STATELESS_RESET_TOKENLEN);
16181627

16191628
// If a Session has been associated with the token, then it is a valid
16201629
// stateless reset token. We need to dispatch it to the session to be
16211630
// processed.
16221631
auto* session = session_manager().FindSessionByStatelessResetToken(
1623-
StatelessResetToken(vec.base));
1632+
StatelessResetToken(token_pos));
16241633
if (session != nullptr) {
16251634
// If the session happens to have been destroyed already, we'll
16261635
// just ignore the packet.
16271636
if (!session->is_destroyed()) [[likely]] {
16281637
receive(session,
1629-
std::move(store),
1638+
pkt_data,
1639+
pkt_len,
16301640
local_address,
16311641
remote_address,
16321642
dcid,
@@ -1654,22 +1664,8 @@ void Endpoint::Receive(const uv_buf_t& buf,
16541664
// return;
16551665
// }
16561666

1657-
Debug(this, "Received %zu-byte packet from %s", buf.len, remote_address);
1658-
1659-
// The managed buffer here contains the received packet. We do not yet know
1660-
// at this point if it is a valid QUIC packet. We need to do some basic
1661-
// checks. It is critical at this point that we do as little work as possible
1662-
// to avoid a DOS vector.
1663-
std::shared_ptr<BackingStore> backing = env()->release_managed_buffer(buf);
1664-
if (!backing) [[unlikely]] {
1665-
// At this point something bad happened and we need to treat this as a fatal
1666-
// case. There's likely no way to test this specific condition reliably.
1667-
returnDestroy(CloseContext::RECEIVE_FAILURE, UV_ENOMEM);
1668-
}
1669-
1670-
Store store(std::move(backing), buf.len, 0);
1667+
Debug(this, "Received %zu-byte packet from %s", len, remote_address);
16711668

1672-
ngtcp2_vec vec = store;
16731669
ngtcp2_version_cid pversion_cid;
16741670

16751671
// This is our first check to see if the received data can be processed as a
@@ -1678,7 +1674,7 @@ void Endpoint::Receive(const uv_buf_t& buf,
16781674
// valid QUIC header but there is still no guarantee that the packet can be
16791675
// successfully processed.
16801676
switch (ngtcp2_pkt_decode_version_cid(
1681-
&pversion_cid, vec.base, vec.len, NGTCP2_MAX_CIDLEN)) {
1677+
&pversion_cid, data, len, NGTCP2_MAX_CIDLEN)) {
16821678
case0:
16831679
break; // Supported version, continue processing.
16841680
caseNGTCP2_ERR_VERSION_NEGOTIATION: {
@@ -1756,7 +1752,7 @@ void Endpoint::Receive(const uv_buf_t& buf,
17561752
// necessary here. We want to return immediately without committing any
17571753
// further resources.
17581754
if (pversion_cid.version == 0 &&
1759-
maybeStatelessReset(dcid, scid, store, addr, remote_address)) {
1755+
maybeStatelessReset(dcid, scid, data, len, addr, remote_address)) {
17601756
Debug(this, "Packet was a stateless reset");
17611757
return; // Stateless reset! Don't do any further processing.
17621758
}
@@ -1771,17 +1767,13 @@ void Endpoint::Receive(const uv_buf_t& buf,
17711767
SendStatelessReset(
17721768
PathDescriptor{
17731769
pversion_cid.version, dcid, scid, addr, remote_address},
1774-
store.length());
1770+
len);
17751771
return;
17761772
}
17771773

17781774
// Process the packet as an initial packet...
1779-
returnacceptInitialPacket(pversion_cid.version,
1780-
dcid,
1781-
scid,
1782-
std::move(store),
1783-
addr,
1784-
remote_address);
1775+
returnacceptInitialPacket(
1776+
pversion_cid.version, dcid, scid, data, len, addr, remote_address);
17851777
}
17861778

17871779
if (session->is_destroyed()) [[unlikely]] {
@@ -1793,7 +1785,7 @@ void Endpoint::Receive(const uv_buf_t& buf,
17931785
// If we got here, the dcid matched the scid of a known local session. Yay!
17941786
// The session will take over any further processing of the packet.
17951787
Debug(this, "Dispatching packet to known session");
1796-
receive(session.get(), std::move(store), addr, remote_address, dcid, scid);
1788+
receive(session.get(), data, len, addr, remote_address, dcid, scid);
17971789

17981790
// It is important to note that the session may have been destroyed during
17991791
// the call to receive(...). If that's the case, the session object still

‎src/quic/endpoint.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,7 @@ class Endpoint final : public AsyncWrap, public Packet::Listener {
429429
// Ref() causes a listening Endpoint to keep the event loop active.
430430
JS_METHOD(Ref);
431431

432-
voidReceive(constuv_buf_t& buf, const SocketAddress& from);
432+
voidReceive(constuint8_t* data, size_t len, const SocketAddress& from);
433433

434434
AliasedStruct<Stats> stats_;
435435
AliasedStruct<State> state_;

‎src/quic/session.cc‎

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2109,7 +2109,8 @@ void Session::SetLastError(QuicError&& error) {
21092109
impl_->last_error_ = std::move(error);
21102110
}
21112111

2112-
boolSession::Receive(Store&& store,
2112+
boolSession::Receive(constuint8_t* data,
2113+
size_t len,
21132114
const SocketAddress& local_address,
21142115
const SocketAddress& remote_address,
21152116
const PacketInfo& pkt_info,
@@ -2120,24 +2121,23 @@ bool Session::Receive(Store&& store,
21202121
// The hot receive path uses ReadPacket() directly with deferred
21212122
// flush via BindingData's uv_check callback.
21222123
SendPendingDataScope send_scope(this);
2123-
returnReadPacket(
2124-
std::move(store), local_address, remote_address, pkt_info, ts);
2124+
returnReadPacket(data, len, local_address, remote_address, pkt_info, ts);
21252125
}
21262126

2127-
boolSession::ReadPacket(Store&& store,
2127+
boolSession::ReadPacket(constuint8_t* data,
2128+
size_t len,
21282129
const SocketAddress& local_address,
21292130
const SocketAddress& remote_address,
21302131
const PacketInfo& pkt_info,
21312132
uint64_t ts) {
21322133
DCHECK(!is_destroyed());
21332134
impl_->remote_address_ = remote_address;
21342135

2135-
ngtcp2_vec vec = store;
21362136
Path path(local_address, remote_address);
21372137

21382138
Debug(this,
21392139
"Session is receiving %zu-byte packet received along path %s",
2140-
vec.len,
2140+
len,
21412141
path);
21422142

21432143
// It is important to understand that reading the packet will cause
@@ -2158,19 +2158,18 @@ bool Session::ReadPacket(Store&& store,
21582158
// receive path caches a timestamp and passes it to all ReadPacket()
21592159
// calls in the same I/O burst.
21602160
if (ts == 0) ts = uv_hrtime();
2161-
err = ngtcp2_conn_read_pkt(
2162-
*this, &path, pkt_info, vec.base, vec.len, ts);
2161+
err = ngtcp2_conn_read_pkt(*this, &path, pkt_info, data, len, ts);
21632162
}
21642163
if (is_destroyed()) returnfalse;
21652164

2166-
Debug(this, "Session receiving %zu-byte packet with result %d", vec.len, err);
2165+
Debug(this, "Session receiving %zu-byte packet with result %d", len, err);
21672166

21682167
switch (err) {
21692168
case0: {
2170-
Debug(this, "Session successfully received %zu-byte packet", vec.len);
2169+
Debug(this, "Session successfully received %zu-byte packet", len);
21712170
if (!is_destroyed()) [[likely]] {
21722171
auto& stats_ = impl_->stats_;
2173-
STAT_INCREMENT_N(Stats, bytes_received, vec.len);
2172+
STAT_INCREMENT_N(Stats, bytes_received, len);
21742173
// Process deferred operations that couldn't run inside callback
21752174
// scopes (e.g., HTTP/3 GOAWAY handling that calls into JS).
21762175
application().PostReceive();

‎src/quic/session.h‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,8 @@ class Session final : public AsyncWrap, private SessionTicket::AppData::Source {
353353
bool early = false;
354354
};
355355

356-
boolReceive(Store&& store,
356+
boolReceive(constuint8_t* data,
357+
size_t len,
357358
const SocketAddress& local_address,
358359
const SocketAddress& remote_address,
359360
const PacketInfo& pkt_info = PacketInfo(),
@@ -367,10 +368,14 @@ class Session final : public AsyncWrap, private SessionTicket::AppData::Source {
367368
// Receive() is kept as a convenience wrapper that calls ReadPacket()
368369
// then triggers SendPendingData (for paths like Connect that need
369370
// immediate response).
371+
// The data pointer is used synchronously — ngtcp2_conn_read_pkt does
372+
// not retain a reference after returning, so the caller's buffer can
373+
// be reused immediately.
370374
// When ts is 0 (the default), uv_hrtime() is called internally.
371375
// The batched receive path caches a timestamp and passes it to all
372376
// ReadPacket() calls in the same I/O burst.
373-
boolReadPacket(Store&& store,
377+
boolReadPacket(constuint8_t* data,
378+
size_t len,
374379
const SocketAddress& local_address,
375380
const SocketAddress& remote_address,
376381
const PacketInfo& pkt_info = PacketInfo(),

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit 098e3d7

Browse files
jasnelladuh95
authored andcommitted
quic: eliminate per-received datagram allocation
Signed-off-by: James M Snell <jasnell@gmail.com> Assisted-by: Opencode:Opus 4.6 PR-URL: #63267 Backport-PR-URL: #64675 Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent 53b05e2 commit 098e3d7

5 files changed

Lines changed: 68 additions & 68 deletions

File tree

‎src/quic/application.cc‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -448,9 +448,13 @@ void Session::Application::SendPendingData() {
448448

449449
// Awesome, let's write our packet!
450450
PacketInfo pi;
451-
ssize_t nwrite = WriteVStream(
452-
&path, &pi, packet->data(), &ndatalen, packet->length(),
453-
stream_data, ts);
451+
ssize_t nwrite = WriteVStream(&path,
452+
&pi,
453+
packet->data(),
454+
&ndatalen,
455+
packet->length(),
456+
stream_data,
457+
ts);
454458

455459
// When ndatalen is > 0, that's our indication that stream data was accepted
456460
// in to the packet. Yay!

‎src/quic/endpoint.cc‎

Lines changed: 43 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -311,10 +311,18 @@ class Endpoint::UDP::Impl final : public HandleWrap {
311311
SET_SELF_SIZE(Impl)
312312

313313
private:
314+
// Pre-allocated receive buffer. Reused across all datagrams because
315+
// ngtcp2_conn_read_pkt is synchronous — it copies what it needs and
316+
// does not retain a reference to the buffer after returning. This
317+
// eliminates a malloc(64KB)/free(64KB) cycle per received datagram.
318+
static constexpr size_t kRecvBufferSize = 65536; // UV__UDP_DGRAM_MAXSIZE
319+
char recv_buf_[kRecvBufferSize];
320+
314321
staticvoidOnAlloc(uv_handle_t* handle,
315322
size_t suggested_size,
316323
uv_buf_t* buf) {
317-
*buf = From(handle)->env()->allocate_managed_buffer(suggested_size);
324+
auto* impl = From(handle);
325+
*buf = uv_buf_init(impl->recv_buf_, kRecvBufferSize);
318326
}
319327

320328
staticvoidOnReceive(uv_udp_t* handle,
@@ -326,26 +334,22 @@ class Endpoint::UDP::Impl final : public HandleWrap {
326334
DCHECK_NOT_NULL(impl);
327335
DCHECK_NOT_NULL(impl->endpoint_);
328336

329-
auto release_buf = [&]() {
330-
if (buf->base != nullptr) impl->env()->release_managed_buffer(*buf);
331-
};
332-
333337
// Nothing to do in these cases. Specifically, if the nread
334338
// is zero or we have received a partial packet, we are just
335-
// going to ignore it.
339+
// going to ignore it. No buffer release needed — recv_buf_
340+
// is pre-allocated and reused.
336341
if (nread == 0 || flags & UV_UDP_PARTIAL) {
337-
release_buf();
338342
return;
339343
}
340344

341345
if (nread < 0) {
342-
release_buf();
343346
impl->endpoint_->Destroy(CloseContext::RECEIVE_FAILURE,
344347
static_cast<int>(nread));
345348
return;
346349
}
347350

348-
impl->endpoint_->Receive(uv_buf_init(buf->base, static_cast<size_t>(nread)),
351+
impl->endpoint_->Receive(reinterpret_cast<constuint8_t*>(buf->base),
352+
static_cast<size_t>(nread),
349353
SocketAddress(addr));
350354
}
351355

@@ -1264,24 +1268,25 @@ void Endpoint::CloseGracefully() {
12641268
MaybeDestroy();
12651269
}
12661270

1267-
voidEndpoint::Receive(constuv_buf_t& buf,
1271+
voidEndpoint::Receive(constuint8_t* data,
1272+
size_t len,
12681273
const SocketAddress& remote_address) {
12691274
constauto receive = [&](Session* session,
1270-
Store&& store,
1275+
constuint8_t* pkt_data,
1276+
size_t pkt_len,
12711277
const SocketAddress& local_address,
12721278
const SocketAddress& remote_address,
12731279
constCID& dcid,
12741280
constCID& scid) {
12751281
DCHECK_NOT_NULL(session);
12761282
if (session->is_destroyed()) return;
1277-
size_t len = store.length();
12781283
// Use ReadPacket (no SendPendingDataScope) so that multiple packets
12791284
// received in the same I/O burst are processed before any responses
12801285
// are generated. The deferred flush via BindingData's uv_check
12811286
// callback calls SendPendingData once per dirty session after all
12821287
// packets in the burst have been read.
1283-
if (session->ReadPacket(std::move(store), local_address, remote_address)) {
1284-
STAT_INCREMENT_N(Stats, bytes_received, len);
1288+
if (session->ReadPacket(pkt_data, pkt_len, local_address, remote_address)) {
1289+
STAT_INCREMENT_N(Stats, bytes_received, pkt_len);
12851290
STAT_INCREMENT(Stats, packets_received);
12861291
}
12871292
// Schedule the session for deferred SendPendingData if it hasn't
@@ -1293,7 +1298,9 @@ void Endpoint::Receive(const uv_buf_t& buf,
12931298
}
12941299
};
12951300

1296-
constauto accept = [&](const Session::Config& config, Store&& store) {
1301+
constauto accept = [&](const Session::Config& config,
1302+
constuint8_t* pkt_data,
1303+
size_t pkt_len) {
12971304
// One final check. If the endpoint is closed, closing, or is not listening
12981305
// as a server, then we cannot accept the initial packet.
12991306
if (is_closed() || is_closing() || !is_listening()) return;
@@ -1323,7 +1330,8 @@ void Endpoint::Receive(const uv_buf_t& buf,
13231330
return;
13241331

13251332
receive(session.get(),
1326-
std::move(store),
1333+
pkt_data,
1334+
pkt_len,
13271335
config.local_address,
13281336
config.remote_address,
13291337
config.dcid,
@@ -1333,7 +1341,8 @@ void Endpoint::Receive(const uv_buf_t& buf,
13331341
constauto acceptInitialPacket = [&](constuint32_t version,
13341342
constCID& dcid,
13351343
constCID& scid,
1336-
Store&& store,
1344+
constuint8_t* pkt_data,
1345+
size_t pkt_len,
13371346
const SocketAddress& local_address,
13381347
const SocketAddress& remote_address) {
13391348
// If we're not listening as a server, do not accept an initial packet.
@@ -1343,8 +1352,7 @@ void Endpoint::Receive(const uv_buf_t& buf,
13431352

13441353
// This is our first condition check... A minimal check to see if ngtcp2 can
13451354
// even recognize this packet as a quic packet.
1346-
ngtcp2_vec vec = store;
1347-
if (ngtcp2_accept(&hd, vec.base, vec.len) != NGTCP2_SUCCESS) {
1355+
if (ngtcp2_accept(&hd, pkt_data, pkt_len) != NGTCP2_SUCCESS) {
13481356
// Per the ngtcp2 docs, ngtcp2_accept returns 0 if the check was
13491357
// successful, or an error code if it was not. Currently there's only one
13501358
// documented error code (NGTCP2_ERR_INVALID_ARGUMENT) but we'll handle
@@ -1582,7 +1590,7 @@ void Endpoint::Receive(const uv_buf_t& buf,
15821590
}
15831591
}
15841592

1585-
accept(config, std::move(store));
1593+
accept(config, pkt_data, pkt_len);
15861594
};
15871595

15881596
// When a received packet contains a QUIC short header but cannot be matched
@@ -1598,35 +1606,37 @@ void Endpoint::Receive(const uv_buf_t& buf,
15981606
// possible to avoid a DOS vector.
15991607
constauto maybeStatelessReset = [&](constCID& dcid,
16001608
constCID& scid,
1601-
Store& store,
1609+
constuint8_t* pkt_data,
1610+
size_t pkt_len,
16021611
const SocketAddress& local_address,
16031612
const SocketAddress& remote_address) {
16041613
// Support for stateless resets can be disabled by the application. If that
16051614
// case, or if the packet is too short to contain a reset token, then we
16061615
// skip the remaining checks.
16071616
if (options_.disable_stateless_reset ||
1608-
store.length() < NGTCP2_STATELESS_RESET_TOKENLEN) {
1617+
pkt_len < NGTCP2_STATELESS_RESET_TOKENLEN) {
16091618
returnfalse;
16101619
}
16111620

16121621
// The stateless reset token itself is the *final*
16131622
// NGTCP2_STATELESS_RESET_TOKENLEN bytes in the received packet. If it is a
16141623
// stateless reset then then rest of the bytes in the packet are garbage
16151624
// that we'll ignore.
1616-
ngtcp2_vec vec = store;
1617-
vec.base += (vec.len - NGTCP2_STATELESS_RESET_TOKENLEN);
1625+
constuint8_t* token_pos =
1626+
pkt_data + (pkt_len - NGTCP2_STATELESS_RESET_TOKENLEN);
16181627

16191628
// If a Session has been associated with the token, then it is a valid
16201629
// stateless reset token. We need to dispatch it to the session to be
16211630
// processed.
16221631
auto* session = session_manager().FindSessionByStatelessResetToken(
1623-
StatelessResetToken(vec.base));
1632+
StatelessResetToken(token_pos));
16241633
if (session != nullptr) {
16251634
// If the session happens to have been destroyed already, we'll
16261635
// just ignore the packet.
16271636
if (!session->is_destroyed()) [[likely]] {
16281637
receive(session,
1629-
std::move(store),
1638+
pkt_data,
1639+
pkt_len,
16301640
local_address,
16311641
remote_address,
16321642
dcid,
@@ -1654,22 +1664,8 @@ void Endpoint::Receive(const uv_buf_t& buf,
16541664
// return;
16551665
// }
16561666

1657-
Debug(this, "Received %zu-byte packet from %s", buf.len, remote_address);
1658-
1659-
// The managed buffer here contains the received packet. We do not yet know
1660-
// at this point if it is a valid QUIC packet. We need to do some basic
1661-
// checks. It is critical at this point that we do as little work as possible
1662-
// to avoid a DOS vector.
1663-
std::shared_ptr<BackingStore> backing = env()->release_managed_buffer(buf);
1664-
if (!backing) [[unlikely]] {
1665-
// At this point something bad happened and we need to treat this as a fatal
1666-
// case. There's likely no way to test this specific condition reliably.
1667-
returnDestroy(CloseContext::RECEIVE_FAILURE, UV_ENOMEM);
1668-
}
1669-
1670-
Store store(std::move(backing), buf.len, 0);
1667+
Debug(this, "Received %zu-byte packet from %s", len, remote_address);
16711668

1672-
ngtcp2_vec vec = store;
16731669
ngtcp2_version_cid pversion_cid;
16741670

16751671
// This is our first check to see if the received data can be processed as a
@@ -1678,7 +1674,7 @@ void Endpoint::Receive(const uv_buf_t& buf,
16781674
// valid QUIC header but there is still no guarantee that the packet can be
16791675
// successfully processed.
16801676
switch (ngtcp2_pkt_decode_version_cid(
1681-
&pversion_cid, vec.base, vec.len, NGTCP2_MAX_CIDLEN)) {
1677+
&pversion_cid, data, len, NGTCP2_MAX_CIDLEN)) {
16821678
case0:
16831679
break; // Supported version, continue processing.
16841680
caseNGTCP2_ERR_VERSION_NEGOTIATION: {
@@ -1756,7 +1752,7 @@ void Endpoint::Receive(const uv_buf_t& buf,
17561752
// necessary here. We want to return immediately without committing any
17571753
// further resources.
17581754
if (pversion_cid.version == 0 &&
1759-
maybeStatelessReset(dcid, scid, store, addr, remote_address)) {
1755+
maybeStatelessReset(dcid, scid, data, len, addr, remote_address)) {
17601756
Debug(this, "Packet was a stateless reset");
17611757
return; // Stateless reset! Don't do any further processing.
17621758
}
@@ -1771,17 +1767,13 @@ void Endpoint::Receive(const uv_buf_t& buf,
17711767
SendStatelessReset(
17721768
PathDescriptor{
17731769
pversion_cid.version, dcid, scid, addr, remote_address},
1774-
store.length());
1770+
len);
17751771
return;
17761772
}
17771773

17781774
// Process the packet as an initial packet...
1779-
returnacceptInitialPacket(pversion_cid.version,
1780-
dcid,
1781-
scid,
1782-
std::move(store),
1783-
addr,
1784-
remote_address);
1775+
returnacceptInitialPacket(
1776+
pversion_cid.version, dcid, scid, data, len, addr, remote_address);
17851777
}
17861778

17871779
if (session->is_destroyed()) [[unlikely]] {
@@ -1793,7 +1785,7 @@ void Endpoint::Receive(const uv_buf_t& buf,
17931785
// If we got here, the dcid matched the scid of a known local session. Yay!
17941786
// The session will take over any further processing of the packet.
17951787
Debug(this, "Dispatching packet to known session");
1796-
receive(session.get(), std::move(store), addr, remote_address, dcid, scid);
1788+
receive(session.get(), data, len, addr, remote_address, dcid, scid);
17971789

17981790
// It is important to note that the session may have been destroyed during
17991791
// the call to receive(...). If that's the case, the session object still

‎src/quic/endpoint.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,7 @@ class Endpoint final : public AsyncWrap, public Packet::Listener {
429429
// Ref() causes a listening Endpoint to keep the event loop active.
430430
JS_METHOD(Ref);
431431

432-
voidReceive(constuv_buf_t& buf, const SocketAddress& from);
432+
voidReceive(constuint8_t* data, size_t len, const SocketAddress& from);
433433

434434
AliasedStruct<Stats> stats_;
435435
AliasedStruct<State> state_;

‎src/quic/session.cc‎

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2109,7 +2109,8 @@ void Session::SetLastError(QuicError&& error) {
21092109
impl_->last_error_ = std::move(error);
21102110
}
21112111

2112-
boolSession::Receive(Store&& store,
2112+
boolSession::Receive(constuint8_t* data,
2113+
size_t len,
21132114
const SocketAddress& local_address,
21142115
const SocketAddress& remote_address,
21152116
const PacketInfo& pkt_info,
@@ -2120,24 +2121,23 @@ bool Session::Receive(Store&& store,
21202121
// The hot receive path uses ReadPacket() directly with deferred
21212122
// flush via BindingData's uv_check callback.
21222123
SendPendingDataScope send_scope(this);
2123-
returnReadPacket(
2124-
std::move(store), local_address, remote_address, pkt_info, ts);
2124+
returnReadPacket(data, len, local_address, remote_address, pkt_info, ts);
21252125
}
21262126

2127-
boolSession::ReadPacket(Store&& store,
2127+
boolSession::ReadPacket(constuint8_t* data,
2128+
size_t len,
21282129
const SocketAddress& local_address,
21292130
const SocketAddress& remote_address,
21302131
const PacketInfo& pkt_info,
21312132
uint64_t ts) {
21322133
DCHECK(!is_destroyed());
21332134
impl_->remote_address_ = remote_address;
21342135

2135-
ngtcp2_vec vec = store;
21362136
Path path(local_address, remote_address);
21372137

21382138
Debug(this,
21392139
"Session is receiving %zu-byte packet received along path %s",
2140-
vec.len,
2140+
len,
21412141
path);
21422142

21432143
// It is important to understand that reading the packet will cause
@@ -2158,19 +2158,18 @@ bool Session::ReadPacket(Store&& store,
21582158
// receive path caches a timestamp and passes it to all ReadPacket()
21592159
// calls in the same I/O burst.
21602160
if (ts == 0) ts = uv_hrtime();
2161-
err = ngtcp2_conn_read_pkt(
2162-
*this, &path, pkt_info, vec.base, vec.len, ts);
2161+
err = ngtcp2_conn_read_pkt(*this, &path, pkt_info, data, len, ts);
21632162
}
21642163
if (is_destroyed()) returnfalse;
21652164

2166-
Debug(this, "Session receiving %zu-byte packet with result %d", vec.len, err);
2165+
Debug(this, "Session receiving %zu-byte packet with result %d", len, err);
21672166

21682167
switch (err) {
21692168
case0: {
2170-
Debug(this, "Session successfully received %zu-byte packet", vec.len);
2169+
Debug(this, "Session successfully received %zu-byte packet", len);
21712170
if (!is_destroyed()) [[likely]] {
21722171
auto& stats_ = impl_->stats_;
2173-
STAT_INCREMENT_N(Stats, bytes_received, vec.len);
2172+
STAT_INCREMENT_N(Stats, bytes_received, len);
21742173
// Process deferred operations that couldn't run inside callback
21752174
// scopes (e.g., HTTP/3 GOAWAY handling that calls into JS).
21762175
application().PostReceive();

‎src/quic/session.h‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,8 @@ class Session final : public AsyncWrap, private SessionTicket::AppData::Source {
353353
bool early = false;
354354
};
355355

356-
boolReceive(Store&& store,
356+
boolReceive(constuint8_t* data,
357+
size_t len,
357358
const SocketAddress& local_address,
358359
const SocketAddress& remote_address,
359360
const PacketInfo& pkt_info = PacketInfo(),
@@ -367,10 +368,14 @@ class Session final : public AsyncWrap, private SessionTicket::AppData::Source {
367368
// Receive() is kept as a convenience wrapper that calls ReadPacket()
368369
// then triggers SendPendingData (for paths like Connect that need
369370
// immediate response).
371+
// The data pointer is used synchronously — ngtcp2_conn_read_pkt does
372+
// not retain a reference after returning, so the caller's buffer can
373+
// be reused immediately.
370374
// When ts is 0 (the default), uv_hrtime() is called internally.
371375
// The batched receive path caches a timestamp and passes it to all
372376
// ReadPacket() calls in the same I/O burst.
373-
boolReadPacket(Store&& store,
377+
boolReadPacket(constuint8_t* data,
378+
size_t len,
374379
const SocketAddress& local_address,
375380
const SocketAddress& remote_address,
376381
const PacketInfo& pkt_info = PacketInfo(),

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Commit 098e3d7

Browse files
jasnelladuh95
authored andcommitted
quic: eliminate per-received datagram allocation
Signed-off-by: James M Snell <jasnell@gmail.com> Assisted-by: Opencode:Opus 4.6 PR-URL: #63267 Backport-PR-URL: #64675 Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent 53b05e2 commit 098e3d7

5 files changed

Lines changed: 68 additions & 68 deletions

File tree

‎src/quic/application.cc‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -448,9 +448,13 @@ void Session::Application::SendPendingData() {
448448

449449
// Awesome, let's write our packet!
450450
PacketInfo pi;
451-
ssize_t nwrite = WriteVStream(
452-
&path, &pi, packet->data(), &ndatalen, packet->length(),
453-
stream_data, ts);
451+
ssize_t nwrite = WriteVStream(&path,
452+
&pi,
453+
packet->data(),
454+
&ndatalen,
455+
packet->length(),
456+
stream_data,
457+
ts);
454458

455459
// When ndatalen is > 0, that's our indication that stream data was accepted
456460
// in to the packet. Yay!

‎src/quic/endpoint.cc‎

Lines changed: 43 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -311,10 +311,18 @@ class Endpoint::UDP::Impl final : public HandleWrap {
311311
SET_SELF_SIZE(Impl)
312312

313313
private:
314+
// Pre-allocated receive buffer. Reused across all datagrams because
315+
// ngtcp2_conn_read_pkt is synchronous — it copies what it needs and
316+
// does not retain a reference to the buffer after returning. This
317+
// eliminates a malloc(64KB)/free(64KB) cycle per received datagram.
318+
static constexpr size_t kRecvBufferSize = 65536; // UV__UDP_DGRAM_MAXSIZE
319+
char recv_buf_[kRecvBufferSize];
320+
314321
staticvoidOnAlloc(uv_handle_t* handle,
315322
size_t suggested_size,
316323
uv_buf_t* buf) {
317-
*buf = From(handle)->env()->allocate_managed_buffer(suggested_size);
324+
auto* impl = From(handle);
325+
*buf = uv_buf_init(impl->recv_buf_, kRecvBufferSize);
318326
}
319327

320328
staticvoidOnReceive(uv_udp_t* handle,
@@ -326,26 +334,22 @@ class Endpoint::UDP::Impl final : public HandleWrap {
326334
DCHECK_NOT_NULL(impl);
327335
DCHECK_NOT_NULL(impl->endpoint_);
328336

329-
auto release_buf = [&]() {
330-
if (buf->base != nullptr) impl->env()->release_managed_buffer(*buf);
331-
};
332-
333337
// Nothing to do in these cases. Specifically, if the nread
334338
// is zero or we have received a partial packet, we are just
335-
// going to ignore it.
339+
// going to ignore it. No buffer release needed — recv_buf_
340+
// is pre-allocated and reused.
336341
if (nread == 0 || flags & UV_UDP_PARTIAL) {
337-
release_buf();
338342
return;
339343
}
340344

341345
if (nread < 0) {
342-
release_buf();
343346
impl->endpoint_->Destroy(CloseContext::RECEIVE_FAILURE,
344347
static_cast<int>(nread));
345348
return;
346349
}
347350

348-
impl->endpoint_->Receive(uv_buf_init(buf->base, static_cast<size_t>(nread)),
351+
impl->endpoint_->Receive(reinterpret_cast<constuint8_t*>(buf->base),
352+
static_cast<size_t>(nread),
349353
SocketAddress(addr));
350354
}
351355

@@ -1264,24 +1268,25 @@ void Endpoint::CloseGracefully() {
12641268
MaybeDestroy();
12651269
}
12661270

1267-
voidEndpoint::Receive(constuv_buf_t& buf,
1271+
voidEndpoint::Receive(constuint8_t* data,
1272+
size_t len,
12681273
const SocketAddress& remote_address) {
12691274
constauto receive = [&](Session* session,
1270-
Store&& store,
1275+
constuint8_t* pkt_data,
1276+
size_t pkt_len,
12711277
const SocketAddress& local_address,
12721278
const SocketAddress& remote_address,
12731279
constCID& dcid,
12741280
constCID& scid) {
12751281
DCHECK_NOT_NULL(session);
12761282
if (session->is_destroyed()) return;
1277-
size_t len = store.length();
12781283
// Use ReadPacket (no SendPendingDataScope) so that multiple packets
12791284
// received in the same I/O burst are processed before any responses
12801285
// are generated. The deferred flush via BindingData's uv_check
12811286
// callback calls SendPendingData once per dirty session after all
12821287
// packets in the burst have been read.
1283-
if (session->ReadPacket(std::move(store), local_address, remote_address)) {
1284-
STAT_INCREMENT_N(Stats, bytes_received, len);
1288+
if (session->ReadPacket(pkt_data, pkt_len, local_address, remote_address)) {
1289+
STAT_INCREMENT_N(Stats, bytes_received, pkt_len);
12851290
STAT_INCREMENT(Stats, packets_received);
12861291
}
12871292
// Schedule the session for deferred SendPendingData if it hasn't
@@ -1293,7 +1298,9 @@ void Endpoint::Receive(const uv_buf_t& buf,
12931298
}
12941299
};
12951300

1296-
constauto accept = [&](const Session::Config& config, Store&& store) {
1301+
constauto accept = [&](const Session::Config& config,
1302+
constuint8_t* pkt_data,
1303+
size_t pkt_len) {
12971304
// One final check. If the endpoint is closed, closing, or is not listening
12981305
// as a server, then we cannot accept the initial packet.
12991306
if (is_closed() || is_closing() || !is_listening()) return;
@@ -1323,7 +1330,8 @@ void Endpoint::Receive(const uv_buf_t& buf,
13231330
return;
13241331

13251332
receive(session.get(),
1326-
std::move(store),
1333+
pkt_data,
1334+
pkt_len,
13271335
config.local_address,
13281336
config.remote_address,
13291337
config.dcid,
@@ -1333,7 +1341,8 @@ void Endpoint::Receive(const uv_buf_t& buf,
13331341
constauto acceptInitialPacket = [&](constuint32_t version,
13341342
constCID& dcid,
13351343
constCID& scid,
1336-
Store&& store,
1344+
constuint8_t* pkt_data,
1345+
size_t pkt_len,
13371346
const SocketAddress& local_address,
13381347
const SocketAddress& remote_address) {
13391348
// If we're not listening as a server, do not accept an initial packet.
@@ -1343,8 +1352,7 @@ void Endpoint::Receive(const uv_buf_t& buf,
13431352

13441353
// This is our first condition check... A minimal check to see if ngtcp2 can
13451354
// even recognize this packet as a quic packet.
1346-
ngtcp2_vec vec = store;
1347-
if (ngtcp2_accept(&hd, vec.base, vec.len) != NGTCP2_SUCCESS) {
1355+
if (ngtcp2_accept(&hd, pkt_data, pkt_len) != NGTCP2_SUCCESS) {
13481356
// Per the ngtcp2 docs, ngtcp2_accept returns 0 if the check was
13491357
// successful, or an error code if it was not. Currently there's only one
13501358
// documented error code (NGTCP2_ERR_INVALID_ARGUMENT) but we'll handle
@@ -1582,7 +1590,7 @@ void Endpoint::Receive(const uv_buf_t& buf,
15821590
}
15831591
}
15841592

1585-
accept(config, std::move(store));
1593+
accept(config, pkt_data, pkt_len);
15861594
};
15871595

15881596
// When a received packet contains a QUIC short header but cannot be matched
@@ -1598,35 +1606,37 @@ void Endpoint::Receive(const uv_buf_t& buf,
15981606
// possible to avoid a DOS vector.
15991607
constauto maybeStatelessReset = [&](constCID& dcid,
16001608
constCID& scid,
1601-
Store& store,
1609+
constuint8_t* pkt_data,
1610+
size_t pkt_len,
16021611
const SocketAddress& local_address,
16031612
const SocketAddress& remote_address) {
16041613
// Support for stateless resets can be disabled by the application. If that
16051614
// case, or if the packet is too short to contain a reset token, then we
16061615
// skip the remaining checks.
16071616
if (options_.disable_stateless_reset ||
1608-
store.length() < NGTCP2_STATELESS_RESET_TOKENLEN) {
1617+
pkt_len < NGTCP2_STATELESS_RESET_TOKENLEN) {
16091618
returnfalse;
16101619
}
16111620

16121621
// The stateless reset token itself is the *final*
16131622
// NGTCP2_STATELESS_RESET_TOKENLEN bytes in the received packet. If it is a
16141623
// stateless reset then then rest of the bytes in the packet are garbage
16151624
// that we'll ignore.
1616-
ngtcp2_vec vec = store;
1617-
vec.base += (vec.len - NGTCP2_STATELESS_RESET_TOKENLEN);
1625+
constuint8_t* token_pos =
1626+
pkt_data + (pkt_len - NGTCP2_STATELESS_RESET_TOKENLEN);
16181627

16191628
// If a Session has been associated with the token, then it is a valid
16201629
// stateless reset token. We need to dispatch it to the session to be
16211630
// processed.
16221631
auto* session = session_manager().FindSessionByStatelessResetToken(
1623-
StatelessResetToken(vec.base));
1632+
StatelessResetToken(token_pos));
16241633
if (session != nullptr) {
16251634
// If the session happens to have been destroyed already, we'll
16261635
// just ignore the packet.
16271636
if (!session->is_destroyed()) [[likely]] {
16281637
receive(session,
1629-
std::move(store),
1638+
pkt_data,
1639+
pkt_len,
16301640
local_address,
16311641
remote_address,
16321642
dcid,
@@ -1654,22 +1664,8 @@ void Endpoint::Receive(const uv_buf_t& buf,
16541664
// return;
16551665
// }
16561666

1657-
Debug(this, "Received %zu-byte packet from %s", buf.len, remote_address);
1658-
1659-
// The managed buffer here contains the received packet. We do not yet know
1660-
// at this point if it is a valid QUIC packet. We need to do some basic
1661-
// checks. It is critical at this point that we do as little work as possible
1662-
// to avoid a DOS vector.
1663-
std::shared_ptr<BackingStore> backing = env()->release_managed_buffer(buf);
1664-
if (!backing) [[unlikely]] {
1665-
// At this point something bad happened and we need to treat this as a fatal
1666-
// case. There's likely no way to test this specific condition reliably.
1667-
returnDestroy(CloseContext::RECEIVE_FAILURE, UV_ENOMEM);
1668-
}
1669-
1670-
Store store(std::move(backing), buf.len, 0);
1667+
Debug(this, "Received %zu-byte packet from %s", len, remote_address);
16711668

1672-
ngtcp2_vec vec = store;
16731669
ngtcp2_version_cid pversion_cid;
16741670

16751671
// This is our first check to see if the received data can be processed as a
@@ -1678,7 +1674,7 @@ void Endpoint::Receive(const uv_buf_t& buf,
16781674
// valid QUIC header but there is still no guarantee that the packet can be
16791675
// successfully processed.
16801676
switch (ngtcp2_pkt_decode_version_cid(
1681-
&pversion_cid, vec.base, vec.len, NGTCP2_MAX_CIDLEN)) {
1677+
&pversion_cid, data, len, NGTCP2_MAX_CIDLEN)) {
16821678
case0:
16831679
break; // Supported version, continue processing.
16841680
caseNGTCP2_ERR_VERSION_NEGOTIATION: {
@@ -1756,7 +1752,7 @@ void Endpoint::Receive(const uv_buf_t& buf,
17561752
// necessary here. We want to return immediately without committing any
17571753
// further resources.
17581754
if (pversion_cid.version == 0 &&
1759-
maybeStatelessReset(dcid, scid, store, addr, remote_address)) {
1755+
maybeStatelessReset(dcid, scid, data, len, addr, remote_address)) {
17601756
Debug(this, "Packet was a stateless reset");
17611757
return; // Stateless reset! Don't do any further processing.
17621758
}
@@ -1771,17 +1767,13 @@ void Endpoint::Receive(const uv_buf_t& buf,
17711767
SendStatelessReset(
17721768
PathDescriptor{
17731769
pversion_cid.version, dcid, scid, addr, remote_address},
1774-
store.length());
1770+
len);
17751771
return;
17761772
}
17771773

17781774
// Process the packet as an initial packet...
1779-
returnacceptInitialPacket(pversion_cid.version,
1780-
dcid,
1781-
scid,
1782-
std::move(store),
1783-
addr,
1784-
remote_address);
1775+
returnacceptInitialPacket(
1776+
pversion_cid.version, dcid, scid, data, len, addr, remote_address);
17851777
}
17861778

17871779
if (session->is_destroyed()) [[unlikely]] {
@@ -1793,7 +1785,7 @@ void Endpoint::Receive(const uv_buf_t& buf,
17931785
// If we got here, the dcid matched the scid of a known local session. Yay!
17941786
// The session will take over any further processing of the packet.
17951787
Debug(this, "Dispatching packet to known session");
1796-
receive(session.get(), std::move(store), addr, remote_address, dcid, scid);
1788+
receive(session.get(), data, len, addr, remote_address, dcid, scid);
17971789

17981790
// It is important to note that the session may have been destroyed during
17991791
// the call to receive(...). If that's the case, the session object still

‎src/quic/endpoint.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,7 @@ class Endpoint final : public AsyncWrap, public Packet::Listener {
429429
// Ref() causes a listening Endpoint to keep the event loop active.
430430
JS_METHOD(Ref);
431431

432-
voidReceive(constuv_buf_t& buf, const SocketAddress& from);
432+
voidReceive(constuint8_t* data, size_t len, const SocketAddress& from);
433433

434434
AliasedStruct<Stats> stats_;
435435
AliasedStruct<State> state_;

‎src/quic/session.cc‎

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2109,7 +2109,8 @@ void Session::SetLastError(QuicError&& error) {
21092109
impl_->last_error_ = std::move(error);
21102110
}
21112111

2112-
boolSession::Receive(Store&& store,
2112+
boolSession::Receive(constuint8_t* data,
2113+
size_t len,
21132114
const SocketAddress& local_address,
21142115
const SocketAddress& remote_address,
21152116
const PacketInfo& pkt_info,
@@ -2120,24 +2121,23 @@ bool Session::Receive(Store&& store,
21202121
// The hot receive path uses ReadPacket() directly with deferred
21212122
// flush via BindingData's uv_check callback.
21222123
SendPendingDataScope send_scope(this);
2123-
returnReadPacket(
2124-
std::move(store), local_address, remote_address, pkt_info, ts);
2124+
returnReadPacket(data, len, local_address, remote_address, pkt_info, ts);
21252125
}
21262126

2127-
boolSession::ReadPacket(Store&& store,
2127+
boolSession::ReadPacket(constuint8_t* data,
2128+
size_t len,
21282129
const SocketAddress& local_address,
21292130
const SocketAddress& remote_address,
21302131
const PacketInfo& pkt_info,
21312132
uint64_t ts) {
21322133
DCHECK(!is_destroyed());
21332134
impl_->remote_address_ = remote_address;
21342135

2135-
ngtcp2_vec vec = store;
21362136
Path path(local_address, remote_address);
21372137

21382138
Debug(this,
21392139
"Session is receiving %zu-byte packet received along path %s",
2140-
vec.len,
2140+
len,
21412141
path);
21422142

21432143
// It is important to understand that reading the packet will cause
@@ -2158,19 +2158,18 @@ bool Session::ReadPacket(Store&& store,
21582158
// receive path caches a timestamp and passes it to all ReadPacket()
21592159
// calls in the same I/O burst.
21602160
if (ts == 0) ts = uv_hrtime();
2161-
err = ngtcp2_conn_read_pkt(
2162-
*this, &path, pkt_info, vec.base, vec.len, ts);
2161+
err = ngtcp2_conn_read_pkt(*this, &path, pkt_info, data, len, ts);
21632162
}
21642163
if (is_destroyed()) returnfalse;
21652164

2166-
Debug(this, "Session receiving %zu-byte packet with result %d", vec.len, err);
2165+
Debug(this, "Session receiving %zu-byte packet with result %d", len, err);
21672166

21682167
switch (err) {
21692168
case0: {
2170-
Debug(this, "Session successfully received %zu-byte packet", vec.len);
2169+
Debug(this, "Session successfully received %zu-byte packet", len);
21712170
if (!is_destroyed()) [[likely]] {
21722171
auto& stats_ = impl_->stats_;
2173-
STAT_INCREMENT_N(Stats, bytes_received, vec.len);
2172+
STAT_INCREMENT_N(Stats, bytes_received, len);
21742173
// Process deferred operations that couldn't run inside callback
21752174
// scopes (e.g., HTTP/3 GOAWAY handling that calls into JS).
21762175
application().PostReceive();

‎src/quic/session.h‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,8 @@ class Session final : public AsyncWrap, private SessionTicket::AppData::Source {
353353
bool early = false;
354354
};
355355

356-
boolReceive(Store&& store,
356+
boolReceive(constuint8_t* data,
357+
size_t len,
357358
const SocketAddress& local_address,
358359
const SocketAddress& remote_address,
359360
const PacketInfo& pkt_info = PacketInfo(),
@@ -367,10 +368,14 @@ class Session final : public AsyncWrap, private SessionTicket::AppData::Source {
367368
// Receive() is kept as a convenience wrapper that calls ReadPacket()
368369
// then triggers SendPendingData (for paths like Connect that need
369370
// immediate response).
371+
// The data pointer is used synchronously — ngtcp2_conn_read_pkt does
372+
// not retain a reference after returning, so the caller's buffer can
373+
// be reused immediately.
370374
// When ts is 0 (the default), uv_hrtime() is called internally.
371375
// The batched receive path caches a timestamp and passes it to all
372376
// ReadPacket() calls in the same I/O burst.
373-
boolReadPacket(Store&& store,
377+
boolReadPacket(constuint8_t* data,
378+
size_t len,
374379
const SocketAddress& local_address,
375380
const SocketAddress& remote_address,
376381
const PacketInfo& pkt_info = PacketInfo(),

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Commit 098e3d7

Browse files
jasnelladuh95
authored andcommitted
quic: eliminate per-received datagram allocation
Signed-off-by: James M Snell <jasnell@gmail.com> Assisted-by: Opencode:Opus 4.6 PR-URL: #63267 Backport-PR-URL: #64675 Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent 53b05e2 commit 098e3d7

5 files changed

Lines changed: 68 additions & 68 deletions

File tree

‎src/quic/application.cc‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -448,9 +448,13 @@ void Session::Application::SendPendingData() {
448448

449449
// Awesome, let's write our packet!
450450
PacketInfo pi;
451-
ssize_t nwrite = WriteVStream(
452-
&path, &pi, packet->data(), &ndatalen, packet->length(),
453-
stream_data, ts);
451+
ssize_t nwrite = WriteVStream(&path,
452+
&pi,
453+
packet->data(),
454+
&ndatalen,
455+
packet->length(),
456+
stream_data,
457+
ts);
454458

455459
// When ndatalen is > 0, that's our indication that stream data was accepted
456460
// in to the packet. Yay!

‎src/quic/endpoint.cc‎

Lines changed: 43 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -311,10 +311,18 @@ class Endpoint::UDP::Impl final : public HandleWrap {
311311
SET_SELF_SIZE(Impl)
312312

313313
private:
314+
// Pre-allocated receive buffer. Reused across all datagrams because
315+
// ngtcp2_conn_read_pkt is synchronous — it copies what it needs and
316+
// does not retain a reference to the buffer after returning. This
317+
// eliminates a malloc(64KB)/free(64KB) cycle per received datagram.
318+
static constexpr size_t kRecvBufferSize = 65536; // UV__UDP_DGRAM_MAXSIZE
319+
char recv_buf_[kRecvBufferSize];
320+
314321
staticvoidOnAlloc(uv_handle_t* handle,
315322
size_t suggested_size,
316323
uv_buf_t* buf) {
317-
*buf = From(handle)->env()->allocate_managed_buffer(suggested_size);
324+
auto* impl = From(handle);
325+
*buf = uv_buf_init(impl->recv_buf_, kRecvBufferSize);
318326
}
319327

320328
staticvoidOnReceive(uv_udp_t* handle,
@@ -326,26 +334,22 @@ class Endpoint::UDP::Impl final : public HandleWrap {
326334
DCHECK_NOT_NULL(impl);
327335
DCHECK_NOT_NULL(impl->endpoint_);
328336

329-
auto release_buf = [&]() {
330-
if (buf->base != nullptr) impl->env()->release_managed_buffer(*buf);
331-
};
332-
333337
// Nothing to do in these cases. Specifically, if the nread
334338
// is zero or we have received a partial packet, we are just
335-
// going to ignore it.
339+
// going to ignore it. No buffer release needed — recv_buf_
340+
// is pre-allocated and reused.
336341
if (nread == 0 || flags & UV_UDP_PARTIAL) {
337-
release_buf();
338342
return;
339343
}
340344

341345
if (nread < 0) {
342-
release_buf();
343346
impl->endpoint_->Destroy(CloseContext::RECEIVE_FAILURE,
344347
static_cast<int>(nread));
345348
return;
346349
}
347350

348-
impl->endpoint_->Receive(uv_buf_init(buf->base, static_cast<size_t>(nread)),
351+
impl->endpoint_->Receive(reinterpret_cast<constuint8_t*>(buf->base),
352+
static_cast<size_t>(nread),
349353
SocketAddress(addr));
350354
}
351355

@@ -1264,24 +1268,25 @@ void Endpoint::CloseGracefully() {
12641268
MaybeDestroy();
12651269
}
12661270

1267-
voidEndpoint::Receive(constuv_buf_t& buf,
1271+
voidEndpoint::Receive(constuint8_t* data,
1272+
size_t len,
12681273
const SocketAddress& remote_address) {
12691274
constauto receive = [&](Session* session,
1270-
Store&& store,
1275+
constuint8_t* pkt_data,
1276+
size_t pkt_len,
12711277
const SocketAddress& local_address,
12721278
const SocketAddress& remote_address,
12731279
constCID& dcid,
12741280
constCID& scid) {
12751281
DCHECK_NOT_NULL(session);
12761282
if (session->is_destroyed()) return;
1277-
size_t len = store.length();
12781283
// Use ReadPacket (no SendPendingDataScope) so that multiple packets
12791284
// received in the same I/O burst are processed before any responses
12801285
// are generated. The deferred flush via BindingData's uv_check
12811286
// callback calls SendPendingData once per dirty session after all
12821287
// packets in the burst have been read.
1283-
if (session->ReadPacket(std::move(store), local_address, remote_address)) {
1284-
STAT_INCREMENT_N(Stats, bytes_received, len);
1288+
if (session->ReadPacket(pkt_data, pkt_len, local_address, remote_address)) {
1289+
STAT_INCREMENT_N(Stats, bytes_received, pkt_len);
12851290
STAT_INCREMENT(Stats, packets_received);
12861291
}
12871292
// Schedule the session for deferred SendPendingData if it hasn't
@@ -1293,7 +1298,9 @@ void Endpoint::Receive(const uv_buf_t& buf,
12931298
}
12941299
};
12951300

1296-
constauto accept = [&](const Session::Config& config, Store&& store) {
1301+
constauto accept = [&](const Session::Config& config,
1302+
constuint8_t* pkt_data,
1303+
size_t pkt_len) {
12971304
// One final check. If the endpoint is closed, closing, or is not listening
12981305
// as a server, then we cannot accept the initial packet.
12991306
if (is_closed() || is_closing() || !is_listening()) return;
@@ -1323,7 +1330,8 @@ void Endpoint::Receive(const uv_buf_t& buf,
13231330
return;
13241331

13251332
receive(session.get(),
1326-
std::move(store),
1333+
pkt_data,
1334+
pkt_len,
13271335
config.local_address,
13281336
config.remote_address,
13291337
config.dcid,
@@ -1333,7 +1341,8 @@ void Endpoint::Receive(const uv_buf_t& buf,
13331341
constauto acceptInitialPacket = [&](constuint32_t version,
13341342
constCID& dcid,
13351343
constCID& scid,
1336-
Store&& store,
1344+
constuint8_t* pkt_data,
1345+
size_t pkt_len,
13371346
const SocketAddress& local_address,
13381347
const SocketAddress& remote_address) {
13391348
// If we're not listening as a server, do not accept an initial packet.
@@ -1343,8 +1352,7 @@ void Endpoint::Receive(const uv_buf_t& buf,
13431352

13441353
// This is our first condition check... A minimal check to see if ngtcp2 can
13451354
// even recognize this packet as a quic packet.
1346-
ngtcp2_vec vec = store;
1347-
if (ngtcp2_accept(&hd, vec.base, vec.len) != NGTCP2_SUCCESS) {
1355+
if (ngtcp2_accept(&hd, pkt_data, pkt_len) != NGTCP2_SUCCESS) {
13481356
// Per the ngtcp2 docs, ngtcp2_accept returns 0 if the check was
13491357
// successful, or an error code if it was not. Currently there's only one
13501358
// documented error code (NGTCP2_ERR_INVALID_ARGUMENT) but we'll handle
@@ -1582,7 +1590,7 @@ void Endpoint::Receive(const uv_buf_t& buf,
15821590
}
15831591
}
15841592

1585-
accept(config, std::move(store));
1593+
accept(config, pkt_data, pkt_len);
15861594
};
15871595

15881596
// When a received packet contains a QUIC short header but cannot be matched
@@ -1598,35 +1606,37 @@ void Endpoint::Receive(const uv_buf_t& buf,
15981606
// possible to avoid a DOS vector.
15991607
constauto maybeStatelessReset = [&](constCID& dcid,
16001608
constCID& scid,
1601-
Store& store,
1609+
constuint8_t* pkt_data,
1610+
size_t pkt_len,
16021611
const SocketAddress& local_address,
16031612
const SocketAddress& remote_address) {
16041613
// Support for stateless resets can be disabled by the application. If that
16051614
// case, or if the packet is too short to contain a reset token, then we
16061615
// skip the remaining checks.
16071616
if (options_.disable_stateless_reset ||
1608-
store.length() < NGTCP2_STATELESS_RESET_TOKENLEN) {
1617+
pkt_len < NGTCP2_STATELESS_RESET_TOKENLEN) {
16091618
returnfalse;
16101619
}
16111620

16121621
// The stateless reset token itself is the *final*
16131622
// NGTCP2_STATELESS_RESET_TOKENLEN bytes in the received packet. If it is a
16141623
// stateless reset then then rest of the bytes in the packet are garbage
16151624
// that we'll ignore.
1616-
ngtcp2_vec vec = store;
1617-
vec.base += (vec.len - NGTCP2_STATELESS_RESET_TOKENLEN);
1625+
constuint8_t* token_pos =
1626+
pkt_data + (pkt_len - NGTCP2_STATELESS_RESET_TOKENLEN);
16181627

16191628
// If a Session has been associated with the token, then it is a valid
16201629
// stateless reset token. We need to dispatch it to the session to be
16211630
// processed.
16221631
auto* session = session_manager().FindSessionByStatelessResetToken(
1623-
StatelessResetToken(vec.base));
1632+
StatelessResetToken(token_pos));
16241633
if (session != nullptr) {
16251634
// If the session happens to have been destroyed already, we'll
16261635
// just ignore the packet.
16271636
if (!session->is_destroyed()) [[likely]] {
16281637
receive(session,
1629-
std::move(store),
1638+
pkt_data,
1639+
pkt_len,
16301640
local_address,
16311641
remote_address,
16321642
dcid,
@@ -1654,22 +1664,8 @@ void Endpoint::Receive(const uv_buf_t& buf,
16541664
// return;
16551665
// }
16561666

1657-
Debug(this, "Received %zu-byte packet from %s", buf.len, remote_address);
1658-
1659-
// The managed buffer here contains the received packet. We do not yet know
1660-
// at this point if it is a valid QUIC packet. We need to do some basic
1661-
// checks. It is critical at this point that we do as little work as possible
1662-
// to avoid a DOS vector.
1663-
std::shared_ptr<BackingStore> backing = env()->release_managed_buffer(buf);
1664-
if (!backing) [[unlikely]] {
1665-
// At this point something bad happened and we need to treat this as a fatal
1666-
// case. There's likely no way to test this specific condition reliably.
1667-
returnDestroy(CloseContext::RECEIVE_FAILURE, UV_ENOMEM);
1668-
}
1669-
1670-
Store store(std::move(backing), buf.len, 0);
1667+
Debug(this, "Received %zu-byte packet from %s", len, remote_address);
16711668

1672-
ngtcp2_vec vec = store;
16731669
ngtcp2_version_cid pversion_cid;
16741670

16751671
// This is our first check to see if the received data can be processed as a
@@ -1678,7 +1674,7 @@ void Endpoint::Receive(const uv_buf_t& buf,
16781674
// valid QUIC header but there is still no guarantee that the packet can be
16791675
// successfully processed.
16801676
switch (ngtcp2_pkt_decode_version_cid(
1681-
&pversion_cid, vec.base, vec.len, NGTCP2_MAX_CIDLEN)) {
1677+
&pversion_cid, data, len, NGTCP2_MAX_CIDLEN)) {
16821678
case0:
16831679
break; // Supported version, continue processing.
16841680
caseNGTCP2_ERR_VERSION_NEGOTIATION: {
@@ -1756,7 +1752,7 @@ void Endpoint::Receive(const uv_buf_t& buf,
17561752
// necessary here. We want to return immediately without committing any
17571753
// further resources.
17581754
if (pversion_cid.version == 0 &&
1759-
maybeStatelessReset(dcid, scid, store, addr, remote_address)) {
1755+
maybeStatelessReset(dcid, scid, data, len, addr, remote_address)) {
17601756
Debug(this, "Packet was a stateless reset");
17611757
return; // Stateless reset! Don't do any further processing.
17621758
}
@@ -1771,17 +1767,13 @@ void Endpoint::Receive(const uv_buf_t& buf,
17711767
SendStatelessReset(
17721768
PathDescriptor{
17731769
pversion_cid.version, dcid, scid, addr, remote_address},
1774-
store.length());
1770+
len);
17751771
return;
17761772
}
17771773

17781774
// Process the packet as an initial packet...
1779-
returnacceptInitialPacket(pversion_cid.version,
1780-
dcid,
1781-
scid,
1782-
std::move(store),
1783-
addr,
1784-
remote_address);
1775+
returnacceptInitialPacket(
1776+
pversion_cid.version, dcid, scid, data, len, addr, remote_address);
17851777
}
17861778

17871779
if (session->is_destroyed()) [[unlikely]] {
@@ -1793,7 +1785,7 @@ void Endpoint::Receive(const uv_buf_t& buf,
17931785
// If we got here, the dcid matched the scid of a known local session. Yay!
17941786
// The session will take over any further processing of the packet.
17951787
Debug(this, "Dispatching packet to known session");
1796-
receive(session.get(), std::move(store), addr, remote_address, dcid, scid);
1788+
receive(session.get(), data, len, addr, remote_address, dcid, scid);
17971789

17981790
// It is important to note that the session may have been destroyed during
17991791
// the call to receive(...). If that's the case, the session object still

‎src/quic/endpoint.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,7 @@ class Endpoint final : public AsyncWrap, public Packet::Listener {
429429
// Ref() causes a listening Endpoint to keep the event loop active.
430430
JS_METHOD(Ref);
431431

432-
voidReceive(constuv_buf_t& buf, const SocketAddress& from);
432+
voidReceive(constuint8_t* data, size_t len, const SocketAddress& from);
433433

434434
AliasedStruct<Stats> stats_;
435435
AliasedStruct<State> state_;

‎src/quic/session.cc‎

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2109,7 +2109,8 @@ void Session::SetLastError(QuicError&& error) {
21092109
impl_->last_error_ = std::move(error);
21102110
}
21112111

2112-
boolSession::Receive(Store&& store,
2112+
boolSession::Receive(constuint8_t* data,
2113+
size_t len,
21132114
const SocketAddress& local_address,
21142115
const SocketAddress& remote_address,
21152116
const PacketInfo& pkt_info,
@@ -2120,24 +2121,23 @@ bool Session::Receive(Store&& store,
21202121
// The hot receive path uses ReadPacket() directly with deferred
21212122
// flush via BindingData's uv_check callback.
21222123
SendPendingDataScope send_scope(this);
2123-
returnReadPacket(
2124-
std::move(store), local_address, remote_address, pkt_info, ts);
2124+
returnReadPacket(data, len, local_address, remote_address, pkt_info, ts);
21252125
}
21262126

2127-
boolSession::ReadPacket(Store&& store,
2127+
boolSession::ReadPacket(constuint8_t* data,
2128+
size_t len,
21282129
const SocketAddress& local_address,
21292130
const SocketAddress& remote_address,
21302131
const PacketInfo& pkt_info,
21312132
uint64_t ts) {
21322133
DCHECK(!is_destroyed());
21332134
impl_->remote_address_ = remote_address;
21342135

2135-
ngtcp2_vec vec = store;
21362136
Path path(local_address, remote_address);
21372137

21382138
Debug(this,
21392139
"Session is receiving %zu-byte packet received along path %s",
2140-
vec.len,
2140+
len,
21412141
path);
21422142

21432143
// It is important to understand that reading the packet will cause
@@ -2158,19 +2158,18 @@ bool Session::ReadPacket(Store&& store,
21582158
// receive path caches a timestamp and passes it to all ReadPacket()
21592159
// calls in the same I/O burst.
21602160
if (ts == 0) ts = uv_hrtime();
2161-
err = ngtcp2_conn_read_pkt(
2162-
*this, &path, pkt_info, vec.base, vec.len, ts);
2161+
err = ngtcp2_conn_read_pkt(*this, &path, pkt_info, data, len, ts);
21632162
}
21642163
if (is_destroyed()) returnfalse;
21652164

2166-
Debug(this, "Session receiving %zu-byte packet with result %d", vec.len, err);
2165+
Debug(this, "Session receiving %zu-byte packet with result %d", len, err);
21672166

21682167
switch (err) {
21692168
case0: {
2170-
Debug(this, "Session successfully received %zu-byte packet", vec.len);
2169+
Debug(this, "Session successfully received %zu-byte packet", len);
21712170
if (!is_destroyed()) [[likely]] {
21722171
auto& stats_ = impl_->stats_;
2173-
STAT_INCREMENT_N(Stats, bytes_received, vec.len);
2172+
STAT_INCREMENT_N(Stats, bytes_received, len);
21742173
// Process deferred operations that couldn't run inside callback
21752174
// scopes (e.g., HTTP/3 GOAWAY handling that calls into JS).
21762175
application().PostReceive();

‎src/quic/session.h‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,8 @@ class Session final : public AsyncWrap, private SessionTicket::AppData::Source {
353353
bool early = false;
354354
};
355355

356-
boolReceive(Store&& store,
356+
boolReceive(constuint8_t* data,
357+
size_t len,
357358
const SocketAddress& local_address,
358359
const SocketAddress& remote_address,
359360
const PacketInfo& pkt_info = PacketInfo(),
@@ -367,10 +368,14 @@ class Session final : public AsyncWrap, private SessionTicket::AppData::Source {
367368
// Receive() is kept as a convenience wrapper that calls ReadPacket()
368369
// then triggers SendPendingData (for paths like Connect that need
369370
// immediate response).
371+
// The data pointer is used synchronously — ngtcp2_conn_read_pkt does
372+
// not retain a reference after returning, so the caller's buffer can
373+
// be reused immediately.
370374
// When ts is 0 (the default), uv_hrtime() is called internally.
371375
// The batched receive path caches a timestamp and passes it to all
372376
// ReadPacket() calls in the same I/O burst.
373-
boolReadPacket(Store&& store,
377+
boolReadPacket(constuint8_t* data,
378+
size_t len,
374379
const SocketAddress& local_address,
375380
const SocketAddress& remote_address,
376381
const PacketInfo& pkt_info = PacketInfo(),

0 commit comments

Comments
 (0)