folly::Optional detectLossPackets()

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;
}