in quic/loss/QuicLossFunctions.h [199:360]
folly::Optional<CongestionController::LossEvent> detectLossPackets(
QuicConnectionStateBase& conn,
folly::Optional<PacketNum> largestAcked,
const LossVisitor& lossVisitor,
TimePoint lossTime,
PacketNumberSpace pnSpace) {
getLossTime(conn, pnSpace).reset();
std::chrono::microseconds rttSample =
std::max(conn.lossState.srtt, conn.lossState.lrtt);
std::chrono::microseconds delayUntilLost = rttSample *
conn.transportSettings.timeReorderingThreshDividend /
conn.transportSettings.timeReorderingThreshDivisor;
VLOG(10) << __func__ << " outstanding=" << conn.outstandings.numOutstanding()
<< " largestAcked=" << largestAcked.value_or(0)
<< " delayUntilLost=" << delayUntilLost.count() << "us"
<< " " << conn;
CongestionController::LossEvent lossEvent(lossTime);
folly::Optional<Observer::LossEvent> observerLossEvent;
if (!conn.observers->empty()) {
observerLossEvent.emplace(lossTime);
}
// Note that time based loss detection is also within the same PNSpace.
auto iter = getFirstOutstandingPacket(conn, pnSpace);
bool shouldSetTimer = false;
while (iter != conn.outstandings.packets.end()) {
auto& pkt = *iter;
auto currentPacketNum = pkt.packet.header.getPacketSequenceNum();
if (!largestAcked.has_value() || currentPacketNum >= *largestAcked) {
break;
}
auto currentPacketNumberSpace = pkt.packet.header.getPacketNumberSpace();
if (currentPacketNumberSpace != pnSpace) {
iter++;
continue;
}
bool lostByTimeout = (lossTime - pkt.metadata.time) > delayUntilLost;
bool lostByReorder =
(*largestAcked - currentPacketNum) > conn.lossState.reorderingThreshold;
if (!(lostByTimeout || lostByReorder)) {
// We can exit early here because if packet N doesn't meet the
// threshold, then packet N + 1 will not either.
shouldSetTimer = true;
break;
}
if (pkt.metadata.isD6DProbe) {
// It's a D6D probe, we'll mark it as lost to avoid its stale
// ack from affecting PMTU. We don't add it to loss event to
// avoid affecting congestion control when there's probably no
// congestion
CHECK(conn.d6d.lastProbe.hasValue());
// Check the decalredLost field first, to avoid double counting
// the lost probe since we don't erase them from op list yet
if (!pkt.declaredLost) {
++conn.outstandings.declaredLostCount;
pkt.declaredLost = true;
if (lostByTimeout && rttSample.count() > 0) {
pkt.lossTimeoutDividend = (lossTime - pkt.metadata.time) *
conn.transportSettings.timeReorderingThreshDivisor / rttSample;
}
if (lostByReorder) {
pkt.lossReorderDistance = *largestAcked - currentPacketNum;
}
++conn.d6d.meta.totalLostProbes;
if (currentPacketNum == conn.d6d.lastProbe->packetNum) {
onD6DLastProbeLost(conn);
}
}
iter++;
continue;
}
detectPMTUBlackhole(conn, pkt);
lossEvent.addLostPacket(pkt);
if (observerLossEvent) {
observerLossEvent->addLostPacket(lostByTimeout, lostByReorder, pkt);
}
if (pkt.isDSRPacket) {
CHECK_GT(conn.outstandings.dsrCount, 0);
--conn.outstandings.dsrCount;
}
if (pkt.associatedEvent) {
CHECK(conn.outstandings.clonedPacketCount[pnSpace]);
--conn.outstandings.clonedPacketCount[pnSpace];
}
// Invoke LossVisitor if the packet doesn't have a associated PacketEvent;
// or if the PacketEvent is present in conn.outstandings.packetEvents.
bool processed = pkt.associatedEvent &&
!conn.outstandings.packetEvents.count(*pkt.associatedEvent);
lossVisitor(conn, pkt.packet, processed);
// Remove the PacketEvent from the outstandings.packetEvents set
if (pkt.associatedEvent) {
conn.outstandings.packetEvents.erase(*pkt.associatedEvent);
}
if (!processed) {
CHECK(conn.outstandings.packetCount[currentPacketNumberSpace]);
--conn.outstandings.packetCount[currentPacketNumberSpace];
}
VLOG(10) << __func__ << " lost packetNum=" << currentPacketNum
<< " handshake=" << pkt.metadata.isHandshake << " " << conn;
// Rather than erasing here, instead mark the packet as lost so we can
// determine if this was spurious later.
conn.lossState.totalPacketsMarkedLost++;
if (lostByTimeout && rttSample.count() > 0) {
conn.lossState.totalPacketsMarkedLostByPto++;
pkt.lossTimeoutDividend = (lossTime - pkt.metadata.time) *
conn.transportSettings.timeReorderingThreshDivisor / rttSample;
}
if (lostByReorder) {
conn.lossState.totalPacketsMarkedLostByReorderingThreshold++;
iter->lossReorderDistance = *largestAcked - currentPacketNum;
}
conn.outstandings.declaredLostCount++;
iter->declaredLost = true;
iter++;
} // while (iter != conn.outstandings.packets.end()) {
// if there are observers, enqueue a function to call it
if (observerLossEvent && observerLossEvent->hasPackets()) {
for (const auto& observer : *(conn.observers)) {
conn.pendingCallbacks.emplace_back(
[observer, observerLossEvent](QuicSocket* qSocket) {
if (observer->getConfig().lossEvents) {
observer->packetLossDetected(qSocket, *observerLossEvent);
}
});
}
}
auto earliest = getFirstOutstandingPacket(conn, pnSpace);
for (; earliest != conn.outstandings.packets.end();
earliest = getNextOutstandingPacket(conn, pnSpace, earliest + 1)) {
if (!earliest->associatedEvent ||
conn.outstandings.packetEvents.count(*earliest->associatedEvent)) {
break;
}
}
if (shouldSetTimer && earliest != conn.outstandings.packets.end()) {
// We are eligible to set a loss timer and there are a few packets which
// are unacked, so we can set the early retransmit timer for them.
VLOG(10) << __func__ << " early retransmit timer outstanding="
<< conn.outstandings.packets.empty() << " delayUntilLost"
<< delayUntilLost.count() << "us"
<< " " << conn;
getLossTime(conn, pnSpace) = delayUntilLost + earliest->metadata.time;
}
if (lossEvent.largestLostPacketNum.hasValue()) {
DCHECK(lossEvent.largestLostSentTime && lossEvent.smallestLostSentTime);
if (conn.qLogger) {
conn.qLogger->addPacketsLost(
lossEvent.largestLostPacketNum.value(),
lossEvent.lostBytes,
lossEvent.lostPackets);
}
conn.lossState.rtxCount += lossEvent.lostPackets;
if (conn.congestionController) {
return lossEvent;
}
}
return folly::none;
}