Loading src/quic.cpp +37 −6 Original line number Diff line number Diff line Loading @@ -6326,7 +6326,11 @@ size_t quic::sendStreamData(uint64_t stream_id, const uint8_t* data, size_t len, // First: spin-pump a few times without syscall (ACK may already be queued) bool unblocked = false; for (int spin = 0; spin < 8; ++spin) { pumpIncoming(); // quic_mtx() is already held throughout this function (see // `lock` above) — pumpIncomingLocked() skips the redundant // recursive re-lock pumpIncoming() would otherwise do here, // up to 8 times per congestion-window stall (F31). pumpIncomingLocked(); if (cwndAllowsSend(est_pkt_size)) { unblocked = true; break; } } // If still blocked, poll with timeout. Reuse one socketwait Loading @@ -6351,7 +6355,9 @@ size_t quic::sendStreamData(uint64_t stream_id, const uint8_t* data, size_t len, lock.unlock(); _cc_socketwait->waitRead(*this, 1); lock.lock(); pumpIncoming(); // Re-locked just above — see the spin-loop's identical // comment on pumpIncomingLocked() (F31). pumpIncomingLocked(); if (cwndAllowsSend(est_pkt_size)) { unblocked = true; break; } } } Loading Loading @@ -6514,8 +6520,9 @@ size_t quic::sendStreamData(uint64_t stream_id, const uint8_t* data, size_t len, flushBatch(); batch_count = 0; // Pace: pump incoming every batch to process ACKs/FC updates pumpIncoming(); // Pace: pump incoming every batch to process ACKs/FC updates. // quic_mtx() is already held throughout this function (F31). pumpIncomingLocked(); if (_conn_state.load() == ConnectionState::Closed || _conn_state.load() == ConnectionState::Draining) { break; Loading Loading @@ -6762,6 +6769,11 @@ std::vector<uint8_t> quic::buildAckFrame(uint64_t largest_ack, uint64_t ack_dela // ============================================================================ void quic::pumpIncoming() { std::lock_guard<std::recursive_mutex> lock(quic_mtx()); pumpIncomingLocked(); } void quic::pumpIncomingLocked() { quic* parent = _parent ? _parent : this; // Use udp::recvBatch (sendmmsg/recvmmsg + GRO) to read datagrams. Safe Loading Loading @@ -6856,6 +6868,21 @@ void quic::pumpIncoming() { } for (auto& [t, pkts] : grouped) { if (t == this) { // pumpIncomingLocked()'s precondition is that quic_mtx() on // `this` is already held by the caller — re-locking it here // (as the general t != this branch below does for every other // connection's own, genuinely different mutex) would just be // two redundant recursive lock/unlock cycles on top of the // caller's, wasted every time this runs from // sendStreamData()'s congestion-window-wait spin loop (F31). // The two sub-blocks below are unreachable for t == this // anyway (both explicitly guarded on t != this), so there's // nothing else this branch needs to do. t->processApplicationPacketsBatch(pkts); continue; } std::unique_lock<std::recursive_mutex> target_lock(t->quic_mtx()); t->processApplicationPacketsBatch(pkts); // The self case's pending flow-control frames are flushed by the Loading Loading @@ -6908,8 +6935,12 @@ void quic::pumpIncoming() { } // Flush own pending flow control frames, and check for lost packets to // retransmit — both operate on `this`'s own state. std::lock_guard<std::recursive_mutex> lock(quic_mtx()); // retransmit — both operate on `this`'s own already-locked state (see // pumpIncomingLocked()'s precondition). flushPendingFlowControlAndCheckLoss(); } void quic::flushPendingFlowControlAndCheckLoss() { if (_handshake_complete && _conn_state.load() == ConnectionState::Connected) { for (auto& [sid, limit] : _pending_max_stream_data) { sendMaxStreamData(sid, limit); Loading src/socket.h +13 −0 Original line number Diff line number Diff line Loading @@ -797,6 +797,19 @@ namespace netplus { void pumpIncoming(); private: // Body of pumpIncoming(), assuming quic_mtx() on `this` is already // held by the caller (recursive_mutex, so re-entering it isn't // incorrect — just two extra uncontended lock/unlock cycles per // call, for the self-targeted case only, that add up when this // runs repeatedly from sendStreamData()'s congestion-window-wait // spin loop). Every other connection's own mutex (`t->quic_mtx()` // for `t != this` in the grouped-datagram loop) is still acquired // normally — those are genuinely different locks, not redundant. void pumpIncomingLocked(); // Shared tail of pumpIncoming()/pumpIncomingLocked(): flush pending // flow-control frames and check for retransmits, both operating on // `this`'s own already-locked state. void flushPendingFlowControlAndCheckLoss(); // Current encryption level for frame processing. Declared here // (rather than down with the other crypto state) because it's a // parameter type for protectPacket()/unprotectPacket() below, and a Loading Loading
src/quic.cpp +37 −6 Original line number Diff line number Diff line Loading @@ -6326,7 +6326,11 @@ size_t quic::sendStreamData(uint64_t stream_id, const uint8_t* data, size_t len, // First: spin-pump a few times without syscall (ACK may already be queued) bool unblocked = false; for (int spin = 0; spin < 8; ++spin) { pumpIncoming(); // quic_mtx() is already held throughout this function (see // `lock` above) — pumpIncomingLocked() skips the redundant // recursive re-lock pumpIncoming() would otherwise do here, // up to 8 times per congestion-window stall (F31). pumpIncomingLocked(); if (cwndAllowsSend(est_pkt_size)) { unblocked = true; break; } } // If still blocked, poll with timeout. Reuse one socketwait Loading @@ -6351,7 +6355,9 @@ size_t quic::sendStreamData(uint64_t stream_id, const uint8_t* data, size_t len, lock.unlock(); _cc_socketwait->waitRead(*this, 1); lock.lock(); pumpIncoming(); // Re-locked just above — see the spin-loop's identical // comment on pumpIncomingLocked() (F31). pumpIncomingLocked(); if (cwndAllowsSend(est_pkt_size)) { unblocked = true; break; } } } Loading Loading @@ -6514,8 +6520,9 @@ size_t quic::sendStreamData(uint64_t stream_id, const uint8_t* data, size_t len, flushBatch(); batch_count = 0; // Pace: pump incoming every batch to process ACKs/FC updates pumpIncoming(); // Pace: pump incoming every batch to process ACKs/FC updates. // quic_mtx() is already held throughout this function (F31). pumpIncomingLocked(); if (_conn_state.load() == ConnectionState::Closed || _conn_state.load() == ConnectionState::Draining) { break; Loading Loading @@ -6762,6 +6769,11 @@ std::vector<uint8_t> quic::buildAckFrame(uint64_t largest_ack, uint64_t ack_dela // ============================================================================ void quic::pumpIncoming() { std::lock_guard<std::recursive_mutex> lock(quic_mtx()); pumpIncomingLocked(); } void quic::pumpIncomingLocked() { quic* parent = _parent ? _parent : this; // Use udp::recvBatch (sendmmsg/recvmmsg + GRO) to read datagrams. Safe Loading Loading @@ -6856,6 +6868,21 @@ void quic::pumpIncoming() { } for (auto& [t, pkts] : grouped) { if (t == this) { // pumpIncomingLocked()'s precondition is that quic_mtx() on // `this` is already held by the caller — re-locking it here // (as the general t != this branch below does for every other // connection's own, genuinely different mutex) would just be // two redundant recursive lock/unlock cycles on top of the // caller's, wasted every time this runs from // sendStreamData()'s congestion-window-wait spin loop (F31). // The two sub-blocks below are unreachable for t == this // anyway (both explicitly guarded on t != this), so there's // nothing else this branch needs to do. t->processApplicationPacketsBatch(pkts); continue; } std::unique_lock<std::recursive_mutex> target_lock(t->quic_mtx()); t->processApplicationPacketsBatch(pkts); // The self case's pending flow-control frames are flushed by the Loading Loading @@ -6908,8 +6935,12 @@ void quic::pumpIncoming() { } // Flush own pending flow control frames, and check for lost packets to // retransmit — both operate on `this`'s own state. std::lock_guard<std::recursive_mutex> lock(quic_mtx()); // retransmit — both operate on `this`'s own already-locked state (see // pumpIncomingLocked()'s precondition). flushPendingFlowControlAndCheckLoss(); } void quic::flushPendingFlowControlAndCheckLoss() { if (_handshake_complete && _conn_state.load() == ConnectionState::Connected) { for (auto& [sid, limit] : _pending_max_stream_data) { sendMaxStreamData(sid, limit); Loading
src/socket.h +13 −0 Original line number Diff line number Diff line Loading @@ -797,6 +797,19 @@ namespace netplus { void pumpIncoming(); private: // Body of pumpIncoming(), assuming quic_mtx() on `this` is already // held by the caller (recursive_mutex, so re-entering it isn't // incorrect — just two extra uncontended lock/unlock cycles per // call, for the self-targeted case only, that add up when this // runs repeatedly from sendStreamData()'s congestion-window-wait // spin loop). Every other connection's own mutex (`t->quic_mtx()` // for `t != this` in the grouped-datagram loop) is still acquired // normally — those are genuinely different locks, not redundant. void pumpIncomingLocked(); // Shared tail of pumpIncoming()/pumpIncomingLocked(): flush pending // flow-control frames and check for retransmits, both operating on // `this`'s own already-locked state. void flushPendingFlowControlAndCheckLoss(); // Current encryption level for frame processing. Declared here // (rather than down with the other crypto state) because it's a // parameter type for protectPacket()/unprotectPacket() below, and a Loading