@@ -2112,13 +2112,21 @@ void Session::SetLastError(QuicError&& error) {
21122112bool Session::Receive (Store&& store,
21132113const SocketAddress& local_address,
21142114const SocketAddress& remote_address) {
2115+ // Convenience wrapper: reads the packet and immediately triggers
2116+ // SendPendingData. Used by paths that need an immediate response
2117+ // (e.g., Endpoint::Connect for client Initial packets).
2118+ // The hot receive path uses ReadPacket() directly with deferred
2119+ // flush via BindingData's uv_check callback.
2120+ SendPendingDataScope send_scope (this );
2121+ return ReadPacket (std::move (store), local_address, remote_address);
2122+ }
2123+
2124+ bool Session::ReadPacket (Store&& store,
2125+ const SocketAddress& local_address,
2126+ const SocketAddress& remote_address) {
21152127DCHECK (!is_destroyed ());
21162128 impl_->remote_address_ = remote_address;
21172129
2118- // When we are done processing this packet, we arrange to send any
2119- // pending data for this session.
2120- SendPendingDataScope send_scope (this );
2121-
21222130 ngtcp2_vec vec = store;
21232131 Path path (local_address, remote_address);
21242132
@@ -2133,14 +2141,16 @@ bool Session::Receive(Store&& store,
21332141// ensures that any deferred destroy waits until all callbacks for this
21342142// packet have completed. After calling ngtcp2_conn_read_pkt here, we
21352143// will need to double check that the session is not destroyed before
2136- // we try doing anything with it (like updating stats, sending pending
2137- // data, etc).
2144+ // we try doing anything with it (like updating stats, etc).
21382145int err;
21392146 {
21402147 NgTcp2CallbackScope callback_scope (this );
2148+ // ECN codepoint (ngtcp2_pkt_info.ecn) is not yet populated because
2149+ // libuv does not currently deliver per-packet ECN metadata. When
2150+ // libuv gains ECN receive reporting, the pkt_info should be
2151+ // populated from the per-packet metadata and passed through here.
21412152 err = ngtcp2_conn_read_pkt (*this ,
21422153 &path,
2143- // TODO(@jasnell): ECN pkt_info blocked on libuv
21442154nullptr ,
21452155 vec.base ,
21462156 vec.len ,
@@ -2253,6 +2263,17 @@ bool Session::Receive(Store&& store,
22532263return false ;
22542264}
22552265
2266+ void Session::FlushPendingData () {
2267+ DCHECK (!is_destroyed ());
2268+ if (impl_->application_ ) {
2269+ // Prefer synchronous sends during the deferred flush to avoid the
2270+ // one-tick latency of async uv_udp_send from the uv_check callback.
2271+ prefer_try_send_ = true ;
2272+ application ().SendPendingData ();
2273+ prefer_try_send_ = false ;
2274+ }
2275+ }
2276+
22562277void Session::Send (Packet::Ptr packet) {
22572278// Sending a Packet is generally best effort. If we're not in a state
22582279// where we can send a packet, it's ok to drop it on the floor. The
@@ -2269,6 +2290,16 @@ void Session::Send(Packet::Ptr packet) {
22692290return ;
22702291 }
22712292
2293+ // When called from the deferred flush path (uv_check callback),
2294+ // prefer synchronous send to avoid the one-tick latency of async
2295+ // uv_udp_send. SendOrTrySend uses uv_udp_try_send first, falling
2296+ // back to uv_udp_send on EAGAIN.
2297+ if (prefer_try_send_) {
2298+ Debug (this , " Session is sending (try_send) %s" , packet->ToString ());
2299+ endpoint ().SendOrTrySend (std::move (packet));
2300+ return ;
2301+ }
2302+
22722303Debug (this , " Session is sending %s" , packet->ToString ());
22732304endpoint ().Send (std::move (packet));
22742305}
0 commit comments