quic/congestion_control/Copa2.cpp (209 lines of code) (raw):

/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #include <quic/congestion_control/Copa2.h> #include <quic/congestion_control/CongestionControlFunctions.h> #include <quic/logging/QLoggerConstants.h> namespace quic { using namespace std::chrono; Copa2::Copa2(QuicConnectionStateBase& conn) : conn_(conn), cwndBytes_(conn.transportSettings.initCwndInMss * conn.udpSendPacketLen), minRTTFilter_(kCopa2MinRttWindowLength.count(), 0us, 0) { VLOG(10) << __func__ << " writable=" << Copa2::getWritableBytes() << " cwnd=" << cwndBytes_ << " inflight=" << conn_.lossState.inflightBytes << " " << conn_; } void Copa2::onRemoveBytesFromInflight(uint64_t bytes) { subtractAndCheckUnderflow(conn_.lossState.inflightBytes, bytes); VLOG(10) << __func__ << " writable=" << getWritableBytes() << " cwnd=" << cwndBytes_ << " inflight=" << conn_.lossState.inflightBytes << " " << conn_; if (conn_.qLogger) { conn_.qLogger->addCongestionMetricUpdate( conn_.lossState.inflightBytes, getCongestionWindow(), kRemoveInflight); } } void Copa2::onPacketSent(const OutstandingPacket& packet) { addAndCheckOverflow( conn_.lossState.inflightBytes, packet.metadata.encodedSize); VLOG(10) << __func__ << " writable=" << getWritableBytes() << " cwnd=" << cwndBytes_ << " inflight=" << conn_.lossState.inflightBytes << " bytesBufferred=" << conn_.flowControlState.sumCurStreamBufferLen << " packetNum=" << packet.packet.header.getPacketSequenceNum() << " " << conn_; if (conn_.qLogger) { conn_.qLogger->addCongestionMetricUpdate( conn_.lossState.inflightBytes, getCongestionWindow(), kCongestionPacketSent); } } void Copa2::onPacketAckOrLoss( const AckEvent* FOLLY_NULLABLE ack, const LossEvent* FOLLY_NULLABLE loss) { if (loss) { onPacketLoss(*loss); if (conn_.pacer) { conn_.pacer->onPacketsLoss(); } } if (ack && ack->largestNewlyAckedPacket.has_value()) { if (appLimited_) { if (appLimitedExitTarget_ < ack->largestNewlyAckedPacketSentTime) { appLimited_ = false; if (conn_.qLogger) { conn_.qLogger->addAppUnlimitedUpdate(); } } } onPacketAcked(*ack); } } // Switch to and from lossy mode void Copa2::manageLossyMode(folly::Optional<TimePoint> sentTime) { if (!sentTime) { // Loss happened and we don't know when. Be safe lossyMode_ = true; numAckedInLossCycle_ = 0; numLostInLossCycle_ = 0; lossCycleStartTime_ = Clock::now(); return; } auto numPktsInLossCycle = numAckedInLossCycle_ + numLostInLossCycle_; if (*sentTime < lossCycleStartTime_) { // Wait for at-least one RTT before declaring lossyMode_ return; } if (numPktsInLossCycle < 2 / lossToleranceParam_ && numLostInLossCycle_ < 2) { // Second condition is needed in case there are losses, but not acks return; } VLOG(5) << __func__ << " lossyMode=" << lossyMode_ << " num lost=" << numLostInLossCycle_ << " num acked=" << numAckedInLossCycle_ << " " << conn_; // Cycle has ended. Take stock of the situation DCHECK(numPktsInLossCycle > 0); lossyMode_ = numLostInLossCycle_ >= numPktsInLossCycle * lossToleranceParam_; numAckedInLossCycle_ = 0; numLostInLossCycle_ = 0; lossCycleStartTime_ = Clock::now(); } void Copa2::onPacketLoss(const LossEvent& loss) { VLOG(10) << __func__ << " lostBytes=" << loss.lostBytes << " lostPackets=" << loss.lostPackets << " cwnd=" << cwndBytes_ << " inflight=" << conn_.lossState.inflightBytes << " " << conn_; if (conn_.qLogger) { conn_.qLogger->addCongestionMetricUpdate( conn_.lossState.inflightBytes, getCongestionWindow(), kCongestionPacketLoss); } DCHECK(loss.largestLostPacketNum.has_value()); subtractAndCheckUnderflow(conn_.lossState.inflightBytes, loss.lostBytes); if (loss.persistentCongestion) { VLOG(10) << __func__ << " writable=" << getWritableBytes() << " cwnd=" << cwndBytes_ << " inflight=" << conn_.lossState.inflightBytes << " " << conn_; cwndBytes_ = conn_.transportSettings.minCwndInMss * conn_.udpSendPacketLen; if (conn_.pacer) { // TODO Which min RTT should we use? conn_.pacer->refreshPacingRate(cwndBytes_, conn_.lossState.mrtt); } if (conn_.qLogger) { conn_.qLogger->addCongestionMetricUpdate( conn_.lossState.inflightBytes, getCongestionWindow(), kPersistentCongestion); } } numLostInLossCycle_ += loss.lostPackets; manageLossyMode(loss.largestLostSentTime); } void Copa2::onPacketAcked(const AckEvent& ack) { DCHECK(ack.largestNewlyAckedPacket.has_value()); subtractAndCheckUnderflow(conn_.lossState.inflightBytes, ack.ackedBytes); minRTTFilter_.Update( conn_.lossState.lrtt, std::chrono::duration_cast<microseconds>(ack.ackTime.time_since_epoch()) .count()); bytesAckedInCycle_ += ack.ackedBytes; for (const auto& ackPkt : ack.ackedPackets) { appLimitedInCycle_ = appLimitedInCycle_ || ackPkt.isAppLimited; } auto rttMin = minRTTFilter_.GetBest(); numAckedInLossCycle_ += ack.ackedPackets.size(); manageLossyMode(ack.largestNewlyAckedPacketSentTime); auto dParam = rttMin; if (lossyMode_) { // Looks like a short buffer. Let's become less aggressive dParam = duration_cast<microseconds>(rttMin * 2. * lossToleranceParam_); } // The duration over which we calculate the number of bytes acked auto cycleDur = rttMin + dParam; if (probeRtt_) { // Do we exit probe RTT now? if (lastProbeRtt_ + dParam <= ack.ackTime) { // Note, probe rtt should ideally never decrease ack rate, since // it just barely empties the queue. Hence all ack rate samples // are good independent of whether we probed for rtt probeRtt_ = false; } } else { // See if we need to enter probe rtt mode auto interval = kCopa2ProbeRttInterval / (conn_.lossState.lrtt < rttMin + dParam ? 2 : 1); if (lastProbeRtt_ + interval <= ack.ackTime) { probeRtt_ = true; lastProbeRtt_ = ack.ackTime; } } if (!cycleStartTime_) { cycleStartTime_ = ack.ackTime; return; } // See if cycle needs to be continued if (*cycleStartTime_ + cycleDur > ack.ackTime) { return; } // Cycle has ended. Update cwnd and rate auto newCwnd = bytesAckedInCycle_ + alphaParam_ * conn_.udpSendPacketLen; if (!appLimitedInCycle_ || cwndBytes_ < newCwnd) { // If CC was app limited, don't decrease cwnd cwndBytes_ = newCwnd; } auto minCwnd = conn_.transportSettings.minCwndInMss * conn_.udpSendPacketLen; if (probeRtt_ || cwndBytes_ < minCwnd) { cwndBytes_ = minCwnd; } if (conn_.pacer) { conn_.pacer->refreshPacingRate(cwndBytes_, rttMin); } VLOG(5) << __func__ << "updated cwnd=" << cwndBytes_ << " rttMin=" << rttMin.count() << " lrtt=" << conn_.lossState.lrtt.count() << " dParam=" << dParam.count() << " " << conn_; cycleStartTime_ = ack.ackTime; bytesAckedInCycle_ = 0; appLimitedInCycle_ = false; } uint64_t Copa2::getWritableBytes() const noexcept { if (conn_.lossState.inflightBytes > cwndBytes_) { return 0; } else { return cwndBytes_ - conn_.lossState.inflightBytes; } } uint64_t Copa2::getCongestionWindow() const noexcept { return cwndBytes_; } CongestionControlType Copa2::type() const noexcept { return CongestionControlType::Copa2; } bool Copa2::inLossyMode() const noexcept { return lossyMode_; } bool Copa2::inProbeRtt() const noexcept { return probeRtt_; } uint64_t Copa2::getBytesInFlight() const noexcept { return conn_.lossState.inflightBytes; } void Copa2::setAppIdle(bool, TimePoint) noexcept {} void Copa2::setAppLimited() { // BBR uses this logic, so we use it too :) if (conn_.lossState.inflightBytes > getCongestionWindow()) { return; } appLimited_ = true; appLimitedExitTarget_ = Clock::now(); if (conn_.qLogger) { conn_.qLogger->addAppLimitedUpdate(); } } bool Copa2::isAppLimited() const noexcept { return appLimited_; } void Copa2::getStats(CongestionControllerStats& /* stats */) const {} } // namespace quic