/* * 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 #include #include #include #include #include #include namespace quic { constexpr uint64_t kMaxBwFilterLen = 2; // Measured in number of ProbeBW cycles constexpr std::chrono::microseconds kMinRttFilterLen = 10s; constexpr std::chrono::microseconds kProbeRTTInterval = 5s; constexpr std::chrono::microseconds kProbeRttDuration = 200ms; constexpr uint64_t kMaxExtraAckedFilterLen = 10; // Measured in packet-timed round trips constexpr float kStartupPacingGain = 2.89; // 2 / ln(2) constexpr float kDrainPacingGain = 1 / kStartupPacingGain; constexpr float kProbeBwDownPacingGain = 0.9; constexpr float kProbeBwCruiseRefillPacingGain = 1.0; constexpr float kProbeBwUpPacingGain = 1.25; constexpr float kProbeRttPacingGain = 1.0; constexpr float kStartupCwndGain = 2.89; constexpr float kProbeBwCruiseRefillCwndGain = 2.0; constexpr float kProbeBwUpDownCwndGain = 2.0; constexpr float kProbeRttCwndGain = 0.5; constexpr float kBeta = 0.7; constexpr float kLossThreshold = 0.02; constexpr float kHeadroomFactor = 0.15; #ifndef CLANG_LAZY_INIT #define CLANG_LAZY_INIT #endif CLANG_LAZY_INIT quic::Bandwidth kMinPacingRateForSendQuantum{1200 * 1000, 1s}; // The experimental pacer currently achieves ~99% of the target rate // we should not reduce the target by adding an extra margin. // TODO: add the margin back if the pacer performance improves further. constexpr uint8_t kPacingMarginPercent = 0; Bbr2CongestionController::Bbr2CongestionController( QuicConnectionStateBase& conn) : conn_(conn), // WindowedFilter window_length is expiry time which inflates the window // length by 1 maxBwFilter_(kMaxBwFilterLen - 1, Bandwidth(), 0), probeRttMinTimestamp_(Clock::now()), maxExtraAckedFilter_(kMaxExtraAckedFilterLen, 0, 0), cwndBytes_( conn_.udpSendPacketLen * conn_.transportSettings.initCwndInMss) { resetCongestionSignals(); resetLowerBounds(); // Start with the default pacing settings. enterStartup(); } // Congestion Controller Interface void Bbr2CongestionController::onRemoveBytesFromInflight( uint64_t bytesToRemove) { subtractAndCheckUnderflow(conn_.lossState.inflightBytes, bytesToRemove); } void Bbr2CongestionController::onPacketSent( const OutstandingPacketWrapper& packet) { // Handle restart from idle if (conn_.lossState.inflightBytes == 0 && isAppLimited()) { idleRestart_ = true; extraAckedStartTimestamp_ = Clock::now(); extraAckedDelivered_ = 0; if (isProbeBwState(state_)) { setPacing(); } else if (state_ == State::ProbeRTT) { checkProbeRttDone(); } } addAndCheckOverflow( conn_.lossState.inflightBytes, packet.metadata.encodedSize); // Maintain cwndLimited flag. We consider the transport being cwnd limited if // we are using > 90% of the cwnd. if (conn_.lossState.inflightBytes > cwndBytes_ * 9 / 10) { cwndLimitedInRound_ |= true; } } void Bbr2CongestionController::onPacketAckOrLoss( const AckEvent* FOLLY_NULLABLE ackEvent, const LossEvent* FOLLY_NULLABLE lossEvent) { if (conn_.qLogger) { conn_.qLogger->addCongestionMetricUpdate( conn_.lossState.inflightBytes, getCongestionWindow(), kCongestionPacketAck, bbr2StateToString(state_)); conn_.qLogger->addNetworkPathModelUpdate( inflightHi_.value_or(0), inflightLo_.value_or(0), bandwidthHi_.has_value() ? bandwidthHi_->units : 0, bandwidthHi_.has_value() ? bandwidthHi_->interval : std::chrono::microseconds(1), bandwidthLo_.has_value() ? bandwidthLo_->units : 0, bandwidthLo_.has_value() ? bandwidthLo_->interval : std::chrono::microseconds(1)); } if (ackEvent) { subtractAndCheckUnderflow( conn_.lossState.inflightBytes, ackEvent->ackedBytes); } if (lossEvent) { subtractAndCheckUnderflow( conn_.lossState.inflightBytes, lossEvent->lostBytes); } SCOPE_EXIT { VLOG(6) << "State=" << bbr2StateToString(state_) << " inflight=" << conn_.lossState.inflightBytes << " cwnd=" << getCongestionWindow() << "(gain=" << cwndGain_ << ")"; }; if (lossEvent && lossEvent->lostPackets > 0 && conn_.transportSettings.ccaConfig.conservativeRecovery) { // The pseudo code in BBRHandleLostPacket is included in // updateProbeBwCyclePhase. No need to repeat it here. // We don't expose the loss type here, so always use fast recovery for // non-persistent congestion saveCwnd(); inPacketConservation_ = true; // Mark the connection as app-limited so bw samples during recovery are not // taken into account. setAppLimited(); packetConservationStartTime_ = Clock::now(); if (lossEvent->persistentCongestion) { cwndBytes_ = kMinCwndInMssForBbr * conn_.udpSendPacketLen; } else { auto newlyAcked = ackEvent ? ackEvent->ackedBytes : 0; cwndBytes_ = conn_.lossState.inflightBytes + std::max(newlyAcked, conn_.udpSendPacketLen); } } if (ackEvent) { if (ackEvent->implicit) { // Implicit acks should not be used for bandwidth or rtt estimation setCwnd(ackEvent->ackedBytes, 0); return; } if (appLimited_ && appLimitedLastSendTime_ <= ackEvent->largestNewlyAckedPacketSentTime) { appLimited_ = false; if (conn_.qLogger) { conn_.qLogger->addAppUnlimitedUpdate(); } } if (inPacketConservation_ && packetConservationStartTime_ <= ackEvent->largestNewlyAckedPacketSentTime) { inPacketConservation_ = false; restoreCwnd(); } // UpdateModelAndState updateLatestDeliverySignals(*ackEvent); updateRound(*ackEvent); // Update cwndLimited state if (roundStart_) { cwndLimitedInRound_ = false; } updateCongestionSignals(lossEvent); updateAckAggregation(*ackEvent); checkStartupDone(); checkDrain(); auto inflightBytesAtLargestAckedPacket = ackEvent->getLargestNewlyAckedPacket() ? ackEvent->getLargestNewlyAckedPacket() ->outstandingPacketMetadata.inflightBytes : conn_.lossState.inflightBytes; auto lostBytes = lossEvent ? lossEvent->lostBytes : 0; updateProbeBwCyclePhase( ackEvent->ackedBytes, inflightBytesAtLargestAckedPacket, lostBytes); updateMinRtt(); checkProbeRtt(ackEvent->ackedBytes); advanceLatestDeliverySignals(*ackEvent); boundBwForModel(); // UpdateControlParameters setPacing(); setSendQuantum(); setCwnd(ackEvent->ackedBytes, lostBytes); } } uint64_t Bbr2CongestionController::getWritableBytes() const noexcept { return getCongestionWindow() > conn_.lossState.inflightBytes ? getCongestionWindow() - conn_.lossState.inflightBytes : 0; } uint64_t Bbr2CongestionController::getCongestionWindow() const noexcept { return cwndBytes_; } CongestionControlType Bbr2CongestionController::type() const noexcept { return CongestionControlType::BBR2; } bool Bbr2CongestionController::isInBackgroundMode() const { return false; } Optional Bbr2CongestionController::getBandwidth() const { return bandwidth_; } bool Bbr2CongestionController::isAppLimited() const { return appLimited_; } void Bbr2CongestionController::setAppLimited() noexcept { appLimited_ = true; appLimitedLastSendTime_ = Clock::now(); if (conn_.qLogger) { conn_.qLogger->addAppLimitedUpdate(); } } // Internals void Bbr2CongestionController::resetCongestionSignals() { lossBytesInRound_ = 0; lossEventsInRound_ = 0; bandwidthLatest_ = Bandwidth(); inflightLatest_ = 0; } void Bbr2CongestionController::resetLowerBounds() { bandwidthLo_.reset(); inflightLo_.reset(); } void Bbr2CongestionController::enterStartup() { state_ = State::Startup; updatePacingAndCwndGain(); } void Bbr2CongestionController::setPacing() { if (!conn_.transportSettings.ccaConfig.paceInitCwnd && conn_.lossState.totalBytesSent < conn_.transportSettings.initCwndInMss * conn_.udpSendPacketLen) { return; } uint64_t pacingWindow = bandwidth_ * minRtt_ * pacingGain_ * (100 - kPacingMarginPercent) / 100; VLOG(6) << "Setting pacing to " << Bandwidth(pacingWindow, minRtt_).normalizedDescribe() << " from bandwidth_=" << bandwidth_.normalizedDescribe() << " pacingGain_=" << pacingGain_ << " kPacingMarginPercent=" << kPacingMarginPercent << " units=" << pacingWindow << " interval=" << minRtt_.count(); if (state_ == State::Startup && !filledPipe_) { pacingWindow = std::max( pacingWindow, conn_.udpSendPacketLen * conn_.transportSettings.initCwndInMss); } conn_.pacer->refreshPacingRate(pacingWindow, minRtt_); } void Bbr2CongestionController::setSendQuantum() { auto rate = bandwidth_ * pacingGain_ * (100 - kPacingMarginPercent) / 100; auto floor = 2 * conn_.udpSendPacketLen; if (rate < kMinPacingRateForSendQuantum) { floor = conn_.udpSendPacketLen; } auto rateIn1Ms = rate * 1ms; sendQuantum_ = std::min(rateIn1Ms, decltype(rateIn1Ms)(64 * 1024)); sendQuantum_ = std::max(sendQuantum_, floor); } void Bbr2CongestionController::setCwnd( uint64_t ackedBytes, uint64_t lostBytes) { // BBRUpdateMaxInflight() auto inflightMax = addQuantizationBudget( getBDPWithGain(cwndGain_) + maxExtraAckedFilter_.GetBest()); // BBRModulateCwndForRecovery() if (lostBytes > 0 && !conn_.transportSettings.ccaConfig.ignoreLoss) { cwndBytes_ = std::max( cwndBytes_ - std::min(lostBytes, cwndBytes_), kMinCwndInMssForBbr * conn_.udpSendPacketLen); } if (inPacketConservation_) { cwndBytes_ = std::max(cwndBytes_, conn_.lossState.inflightBytes + ackedBytes); } if (!inPacketConservation_) { if (filledPipe_) { cwndBytes_ = std::min(cwndBytes_ + ackedBytes, inflightMax); } else if ( cwndBytes_ < inflightMax || conn_.lossState.totalBytesAcked < conn_.transportSettings.initCwndInMss * conn_.udpSendPacketLen) { cwndBytes_ += ackedBytes; } cwndBytes_ = std::max(cwndBytes_, kMinCwndInMssForBbr * conn_.udpSendPacketLen); } // BBRBoundCwndForProbeRTT() if (state_ == State::ProbeRTT) { cwndBytes_ = std::min(cwndBytes_, getProbeRTTCwnd()); } // BBRBoundCwndForModel() auto cap = std::numeric_limits::max(); if (inflightHi_.has_value() && !conn_.transportSettings.ccaConfig.ignoreInflightHi) { if (isProbeBwState(state_) && state_ != State::ProbeBw_Cruise) { cap = *inflightHi_; } else if (state_ == State::ProbeRTT || state_ == State::ProbeBw_Cruise) { cap = getTargetInflightWithHeadroom(); } } if (inflightLo_.has_value() && !conn_.transportSettings.ccaConfig.ignoreLoss) { cap = std::min(cap, *inflightLo_); } cap = std::max(cap, kMinCwndInMssForBbr * conn_.udpSendPacketLen); cwndBytes_ = std::min(cwndBytes_, cap); } void Bbr2CongestionController::checkProbeRttDone() { auto timeNow = Clock::now(); if ((probeRttDoneTimestamp_ && timeNow > *probeRttDoneTimestamp_) || conn_.lossState.inflightBytes == 0) { // Schedule the next ProbeRTT probeRttMinTimestamp_ = timeNow; restoreCwnd(); exitProbeRtt(); } } void Bbr2CongestionController::restoreCwnd() { cwndBytes_ = std::max(cwndBytes_, previousCwndBytes_); VLOG(6) << "Restored cwnd: " << cwndBytes_; } void Bbr2CongestionController::exitProbeRtt() { resetLowerBounds(); if (filledPipe_) { startProbeBwDown(); startProbeBwCruise(); } else { enterStartup(); } } void Bbr2CongestionController::updateLatestDeliverySignals( const AckEvent& ackEvent) { lossRoundStart_ = false; bandwidthLatest_ = std::max(bandwidthLatest_, getBandwidthSampleFromAck(ackEvent)); VLOG(6) << "Bandwidth latest=" << bandwidthLatest_.normalizedDescribe() << " AppLimited=" << bandwidthLatest_.isAppLimited; inflightLatest_ = std::max(inflightLatest_, bandwidthLatest_.units); auto pkt = ackEvent.getLargestNewlyAckedPacket(); if (pkt && pkt->outstandingPacketMetadata.totalBytesSent > lossRoundEndBytesSent_) { // Uses bytes sent instead of ACKed in the spec. This doesn't affect the // round counting lossPctInLastRound_ = static_cast(lossBytesInRound_) / static_cast(conn_.lossState.totalBytesSent - lossRoundEndBytesSent_); lossEventsInLastRound_ = lossEventsInRound_; lossRoundEndBytesSent_ = conn_.lossState.totalBytesSent; lossRoundStart_ = true; } } void Bbr2CongestionController::updateCongestionSignals( const LossEvent* FOLLY_NULLABLE lossEvent) { // Update max bandwidth if (bandwidthLatest_ > maxBwFilter_.GetBest() || !bandwidthLatest_.isAppLimited) { VLOG(6) << "Updating bandwidth filter with sample: " << bandwidthLatest_.normalizedDescribe(); maxBwFilter_.Update(bandwidthLatest_, cycleCount_); } // Update loss signal if (lossEvent && lossEvent->lostBytes > 0) { lossBytesInRound_ += lossEvent->lostBytes; lossEventsInRound_ += 1; } if (!lossRoundStart_) { return; // we're still within the same round } // AdaptLowerBoundsFromCongestion - once per round-trip if (state_ == State::ProbeBw_Up) { return; } if (lossBytesInRound_ > 0) { // InitLowerBounds if (!bandwidthLo_.has_value()) { bandwidthLo_ = maxBwFilter_.GetBest(); } if (!inflightLo_.has_value()) { inflightLo_ = cwndBytes_; } // LossLowerBounds bandwidthLo_ = std::max(bandwidthLatest_, *bandwidthLo_ * kBeta); inflightLo_ = std::max(inflightLatest_, static_cast(*inflightLo_ * kBeta)); } lossBytesInRound_ = 0; lossEventsInRound_ = 0; } void Bbr2CongestionController::updateAckAggregation(const AckEvent& ackEvent) { /* Find excess ACKed beyond expected amount over this interval */ auto interval = Clock::now() - extraAckedStartTimestamp_.value_or(TimePoint()); auto expectedDelivered = bandwidth_ * std::chrono::duration_cast(interval); /* Reset interval if ACK rate is below expected rate: */ if (extraAckedDelivered_ < expectedDelivered) { extraAckedDelivered_ = 0; extraAckedStartTimestamp_ = Clock::now(); expectedDelivered = 0; } extraAckedDelivered_ += ackEvent.ackedBytes; auto extra = extraAckedDelivered_ - expectedDelivered; extra = std::min(extra, cwndBytes_); maxExtraAckedFilter_.Update(extra, roundCount_); } void Bbr2CongestionController::checkStartupDone() { checkStartupFullBandwidth(); checkStartupHighLoss(); if (state_ == State::Startup && filledPipe_) { enterDrain(); } } void Bbr2CongestionController::checkStartupFullBandwidth() { if (filledPipe_ || !roundStart_ || isAppLimited()) { return; /* no need to check for a full pipe now */ } if (maxBwFilter_.GetBest() >= filledPipeBandwidth_ * 1.25) { /* still growing? */ filledPipeBandwidth_ = maxBwFilter_.GetBest(); /* record new baseline level */ filledPipeCount_ = 0; return; } filledPipeCount_++; /* another round w/o much growth */ if (filledPipeCount_ >= 3) { filledPipe_ = true; } } void Bbr2CongestionController::checkStartupHighLoss() { /* Our implementation differs from the spec a bit here. The conditions in the spec are: 1. The connection has been in fast recovery for at least one full round trip. 2. The loss rate over the time scale of a single full round trip exceeds BBRLossThresh (2%). 3. There are at least BBRStartupFullLossCnt=3 noncontiguous sequence ranges lost in that round trip. For 1,2 we use the loss pct from the last loss round which means we could exit before a full RTT. For 3, we check we received three separate loss events which servers a similar purpose to discontiguous ranges but it's not exactly the same. */ if (filledPipe_ || !roundStart_ || isAppLimited() || conn_.transportSettings.ccaConfig.ignoreLoss) { // TODO: the appLimited condition means we could tolerate losses in startup // if we haven't found the full bandwidth. This may need to be revisited. return; /* no need to check for a the loss exit condition now */ } if (lossPctInLastRound_ > kLossThreshold && lossEventsInLastRound_ >= 3) { filledPipe_ = true; } } void Bbr2CongestionController::enterDrain() { state_ = State::Drain; updatePacingAndCwndGain(); } void Bbr2CongestionController::checkDrain() { if (state_ == State::Drain) { VLOG(6) << "Current inflight" << conn_.lossState.inflightBytes << " target inflight " << getTargetInflightWithGain(1.0); } if (state_ == State::Drain && conn_.lossState.inflightBytes <= getTargetInflightWithGain(1.0)) { enterProbeBW(); /* BBR estimates the queue was drained */ } } void Bbr2CongestionController::updateProbeBwCyclePhase( uint64_t ackedBytes, uint64_t inflightBytesAtLargestAckedPacket, uint64_t lostBytes) { /* The core state machine logic for ProbeBW: */ if (!filledPipe_) { return; /* only handling steady-state behavior here */ } adaptUpperBounds(ackedBytes, inflightBytesAtLargestAckedPacket, lostBytes); if (!isProbeBwState(state_)) { return; /* only handling ProbeBW states here: */ } switch (state_) { case State::ProbeBw_Down: if (checkTimeToProbeBW()) { return; /* already decided state transition */ } if (checkTimeToCruise()) { startProbeBwCruise(); } break; case State::ProbeBw_Cruise: if (checkTimeToProbeBW()) { return; /* already decided state transition */ } break; case State::ProbeBw_Refill: /* After one round of REFILL, start UP */ if (roundStart_) { // Enable one reaction to loss per probe bw cycle. bwProbeShouldHandleLoss_ = true; startProbeBwUp(); } break; case State::ProbeBw_Up: if (hasElapsedInPhase(minRtt_) && conn_.lossState.inflightBytes > getTargetInflightWithGain(1.25)) { startProbeBwDown(); } break; default: throw QuicInternalException( "BBR2: Unexpected state in ProbeBW phase: " + bbr2StateToString(state_), LocalErrorCode::CONGESTION_CONTROL_ERROR); } } void Bbr2CongestionController::adaptUpperBounds( uint64_t ackedBytes, uint64_t inflightBytesAtLargestAckedPacket, uint64_t lostBytes) { /* Update BBR.inflight_hi and BBR.bw_hi. */ if (!checkInflightTooHigh(inflightBytesAtLargestAckedPacket, lostBytes)) { if (!inflightHi_.has_value() || !bandwidthHi_.has_value()) { // No loss has occurred yet so these values are not set and do not need to // be raised. return; } /* There is loss but it's at safe levels. The limits are populated so we * update them */ if (inflightBytesAtLargestAckedPacket > *inflightHi_) { inflightHi_ = inflightBytesAtLargestAckedPacket; } if (bandwidthLatest_ > *bandwidthHi_) { bandwidthHi_ = bandwidthLatest_; } if (state_ == State::ProbeBw_Up) { probeInflightHiUpward(ackedBytes); } } } bool Bbr2CongestionController::checkTimeToProbeBW() { if (hasElapsedInPhase(bwProbeWait_) || isRenoCoexistenceProbeTime()) { startProbeBwRefill(); return true; } else { return false; } } bool Bbr2CongestionController::checkTimeToCruise() { if (conn_.lossState.inflightBytes > getTargetInflightWithHeadroom()) { return false; /* not enough headroom */ } else if (conn_.lossState.inflightBytes <= getTargetInflightWithGain()) { return true; /* inflight <= estimated BDP */ } // Neither conditions met. Do not cruise yet. return false; } bool Bbr2CongestionController::hasElapsedInPhase( std::chrono::microseconds interval) { return Clock::now() > probeBWCycleStart_ + interval; } // Was the loss percent too high for the last ack received? bool Bbr2CongestionController::checkInflightTooHigh( uint64_t inflightBytesAtLargestAckedPacket, uint64_t lostBytes) { if (isInflightTooHigh(inflightBytesAtLargestAckedPacket, lostBytes)) { if (bwProbeShouldHandleLoss_) { handleInFlightTooHigh(inflightBytesAtLargestAckedPacket); } return true; } else { return false; } } bool Bbr2CongestionController::isInflightTooHigh( uint64_t inflightBytesAtLargestAckedPacket, uint64_t lostBytes) { return static_cast(lostBytes) > static_cast(inflightBytesAtLargestAckedPacket) * kLossThreshold; } void Bbr2CongestionController::handleInFlightTooHigh( uint64_t inflightBytesAtLargestAckedPacket) { bwProbeShouldHandleLoss_ = false; // TODO: Should this be the app limited state of the largest acknowledged // packet? if (!isAppLimited()) { inflightHi_ = std::max( inflightBytesAtLargestAckedPacket, static_cast( static_cast(getTargetInflightWithGain()) * kBeta)); bandwidthHi_ = maxBwFilter_.GetBest(); } if (state_ == State::ProbeBw_Up) { startProbeBwDown(); } } uint64_t Bbr2CongestionController::getTargetInflightWithHeadroom() const { /* Return a volume of data that tries to leave free * headroom in the bottleneck buffer or link for * other flows, for fairness convergence and lower * RTTs and loss */ if (!inflightHi_.has_value()) { return std::numeric_limits::max(); } else { auto headroom = static_cast( std::max(1.0f, kHeadroomFactor * static_cast(*inflightHi_))); return std::max( *inflightHi_ - headroom, quic::kMinCwndInMssForBbr * conn_.udpSendPacketLen); } } void Bbr2CongestionController::probeInflightHiUpward(uint64_t ackedBytes) { if (!inflightHi_.has_value() || !cwndLimitedInRound_ || cwndBytes_ < *inflightHi_) { return; /* no inflight_hi set or not fully using inflight_hi, so don't grow it */ } probeUpAcks_ += ackedBytes; if (probeUpAcks_ >= probeUpCount_) { auto delta = probeUpAcks_ / probeUpCount_; probeUpAcks_ -= delta * probeUpCount_; addAndCheckOverflow(*inflightHi_, delta); } if (roundStart_) { raiseInflightHiSlope(); } } void Bbr2CongestionController::updateMinRtt() { if (idleRestart_) { probeRttMinTimestamp_ = Clock::now(); probeRttMinValue_ = kDefaultMinRtt; } probeRttExpired_ = probeRttMinTimestamp_ ? Clock::now() > (probeRttMinTimestamp_.value() + kProbeRTTInterval) : true; auto& lrtt = conn_.lossState.lrtt; if (lrtt > 0us && (lrtt < probeRttMinValue_ || probeRttExpired_)) { probeRttMinValue_ = lrtt; probeRttMinTimestamp_ = Clock::now(); } auto minRttExpired = minRttTimestamp_ ? Clock::now() > (minRttTimestamp_.value() + kMinRttFilterLen) : true; if (probeRttMinValue_ < minRtt_ || minRttExpired) { minRtt_ = probeRttMinValue_; minRttTimestamp_ = probeRttMinTimestamp_; } } void Bbr2CongestionController::checkProbeRtt(uint64_t ackedBytes) { if (state_ != State::ProbeRTT && probeRttExpired_ && !idleRestart_) { enterProbeRtt(); saveCwnd(); probeRttDoneTimestamp_.reset(); startRound(); } if (state_ == State::ProbeRTT) { handleProbeRtt(); } if (ackedBytes > 0) { idleRestart_ = false; } } void Bbr2CongestionController::enterProbeRtt() { state_ = State::ProbeRTT; updatePacingAndCwndGain(); } void Bbr2CongestionController::handleProbeRtt() { /* Ignore low rate samples during ProbeRTT: */ // TODO: I don't understand the logic in the spec in // MarkConnectionAppLimited() but just setting app limited is reasonable setAppLimited(); if (!probeRttDoneTimestamp_ && conn_.lossState.inflightBytes <= getProbeRTTCwnd()) { /* Wait for at least ProbeRTTDuration to elapse: */ probeRttDoneTimestamp_ = Clock::now() + kProbeRttDuration; /* Wait for at least one round to elapse: */ // Is this needed? BBR.probe_rtt_round_done = false startRound(); } else if (probeRttDoneTimestamp_) { if (roundStart_) { checkProbeRttDone(); } } } void Bbr2CongestionController::advanceLatestDeliverySignals( const AckEvent& ackEvent) { if (lossRoundStart_) { bandwidthLatest_ = getBandwidthSampleFromAck(ackEvent); inflightLatest_ = bandwidthLatest_.units; } } uint64_t Bbr2CongestionController::getProbeRTTCwnd() { return std::max( getBDPWithGain(kProbeRttCwndGain), quic::kMinCwndInMssForBbr * conn_.udpSendPacketLen); } void Bbr2CongestionController::boundCwndForProbeRTT() { if (state_ == State::ProbeRTT) { cwndBytes_ = std::min(cwndBytes_, getProbeRTTCwnd()); } } void Bbr2CongestionController::boundBwForModel() { Bandwidth previousBw = bandwidth_; bandwidth_ = maxBwFilter_.GetBest(); if (state_ != State::Startup) { if (bandwidthLo_.has_value() && !conn_.transportSettings.ccaConfig.ignoreLoss) { bandwidth_ = std::min(bandwidth_, *bandwidthLo_); } if (bandwidthHi_.has_value() && !conn_.transportSettings.ccaConfig.ignoreInflightHi) { bandwidth_ = std::min(bandwidth_, *bandwidthHi_); } } if (conn_.qLogger && previousBw != bandwidth_) { conn_.qLogger->addBandwidthEstUpdate(bandwidth_.units, bandwidth_.interval); } } uint64_t Bbr2CongestionController::addQuantizationBudget(uint64_t input) const { // BBRUpdateOffloadBudget() auto offloadBudget = 3 * sendQuantum_; input = std::max(input, offloadBudget); input = std::max(input, quic::kMinCwndInMssForBbr * conn_.udpSendPacketLen); if (state_ == State::ProbeBw_Up) { // This number is arbitrary from the spec. It's probably to guarantee that // probing up is more aggressive (?) input += 2 * conn_.udpSendPacketLen; } return input; } void Bbr2CongestionController::saveCwnd() { if (!inLossRecovery_ && state_ != State::ProbeRTT) { previousCwndBytes_ = cwndBytes_; } else { previousCwndBytes_ = std::max(cwndBytes_, previousCwndBytes_); } VLOG(6) << "Saved cwnd: " << previousCwndBytes_; } uint64_t Bbr2CongestionController::getTargetInflightWithGain(float gain) const { return addQuantizationBudget(getBDPWithGain(gain)); } uint64_t Bbr2CongestionController::getBDPWithGain(float gain) const { if (minRtt_ == kDefaultMinRtt) { return uint64_t( gain * conn_.transportSettings.initCwndInMss * conn_.udpSendPacketLen); } else { return uint64_t(gain * (minRtt_ * bandwidth_)); } } void Bbr2CongestionController::enterProbeBW() { startProbeBwDown(); } void Bbr2CongestionController::startRound() { nextRoundDelivered_ = conn_.lossState.totalBytesAcked; } void Bbr2CongestionController::updateRound(const AckEvent& ackEvent) { auto pkt = ackEvent.getLargestNewlyAckedPacket(); if (pkt && pkt->lastAckedPacketInfo && pkt->lastAckedPacketInfo->totalBytesAcked >= nextRoundDelivered_) { startRound(); roundCount_++; roundsSinceBwProbe_++; roundStart_ = true; } else { roundStart_ = false; } } void Bbr2CongestionController::startProbeBwDown() { resetCongestionSignals(); probeUpCount_ = std::numeric_limits::max(); /* not growing inflight_hi */ /* Decide random round-trip bound for wait: */ roundsSinceBwProbe_ = folly::Random::rand32() % 2; /* Decide the random wall clock bound for wait: between 2-3 seconds */ bwProbeWait_ = std::chrono::milliseconds(2000 + (folly::Random::rand32() % 1000)); probeBWCycleStart_ = Clock::now(); state_ = State::ProbeBw_Down; updatePacingAndCwndGain(); startRound(); // This is a new ProbeBW cycle. Advance the max bw filter if we're not app // limited if (!isAppLimited()) { cycleCount_++; } } void Bbr2CongestionController::startProbeBwCruise() { state_ = State::ProbeBw_Cruise; updatePacingAndCwndGain(); } void Bbr2CongestionController::startProbeBwRefill() { resetLowerBounds(); probeUpRounds_ = 0; probeUpAcks_ = 0; state_ = State::ProbeBw_Refill; updatePacingAndCwndGain(); startRound(); } void Bbr2CongestionController::startProbeBwUp() { probeBWCycleStart_ = Clock::now(); state_ = State::ProbeBw_Up; updatePacingAndCwndGain(); startRound(); raiseInflightHiSlope(); } void Bbr2CongestionController::raiseInflightHiSlope() { auto growthThisRound = conn_.udpSendPacketLen << probeUpRounds_; probeUpRounds_ = std::min(probeUpRounds_ + 1, decltype(probeUpRounds_)(30)); probeUpCount_ = std::max(cwndBytes_ / growthThisRound, decltype(cwndBytes_)(1)); } // Utilities bool Bbr2CongestionController::isProbeBwState( const Bbr2CongestionController::State state) { return ( state == Bbr2CongestionController::State::ProbeBw_Down || state == Bbr2CongestionController::State::ProbeBw_Cruise || state == Bbr2CongestionController::State::ProbeBw_Refill || state == Bbr2CongestionController::State::ProbeBw_Up); } Bandwidth Bbr2CongestionController::getBandwidthSampleFromAck( const AckEvent& ackEvent) { auto ackTime = ackEvent.adjustedAckTime; auto bwSample = Bandwidth(); for (auto const& ackedPacket : ackEvent.ackedPackets) { auto pkt = &ackedPacket; if (ackedPacket.outstandingPacketMetadata.encodedSize == 0) { continue; } auto& lastAckedPacket = pkt->lastAckedPacketInfo; auto lastSentTime = lastAckedPacket ? lastAckedPacket->sentTime : conn_.connectionTime; auto sendElapsed = pkt->outstandingPacketMetadata.time - lastSentTime; auto lastAckTime = lastAckedPacket ? lastAckedPacket->adjustedAckTime : conn_.connectionTime; auto ackElapsed = ackTime - lastAckTime; auto interval = std::max(ackElapsed, sendElapsed); if (interval == 0us) { return Bandwidth(); } auto lastBytesDelivered = lastAckedPacket ? lastAckedPacket->totalBytesAcked : 0; auto bytesDelivered = ackEvent.totalBytesAcked - lastBytesDelivered; Bandwidth bw( bytesDelivered, std::chrono::duration_cast(interval), pkt->isAppLimited || lastSentTime < appLimitedLastSendTime_); if (bw > bwSample) { bwSample = bw; } } return bwSample; } bool Bbr2CongestionController::isRenoCoexistenceProbeTime() { if (!conn_.transportSettings.ccaConfig.enableRenoCoexistence) { return false; } auto renoBdpInPackets = std::min(getTargetInflightWithGain(), cwndBytes_) / conn_.udpSendPacketLen; auto roundsBeforeRenoProbe = std::min(renoBdpInPackets, decltype(renoBdpInPackets)(63)); return roundsSinceBwProbe_ >= roundsBeforeRenoProbe; } Bbr2CongestionController::State Bbr2CongestionController::getState() const noexcept { return state_; } void Bbr2CongestionController::getStats( CongestionControllerStats& stats) const { stats.bbr2Stats.state = uint8_t(state_); } void Bbr2CongestionController::updatePacingAndCwndGain() { switch (state_) { case State::Startup: pacingGain_ = conn_.transportSettings.ccaConfig.overrideStartupPacingGain > 0 ? conn_.transportSettings.ccaConfig.overrideStartupPacingGain : kStartupPacingGain; cwndGain_ = kStartupCwndGain; break; case State::Drain: pacingGain_ = kDrainPacingGain; cwndGain_ = kStartupCwndGain; break; case State::ProbeBw_Up: pacingGain_ = kProbeBwUpPacingGain; cwndGain_ = kProbeBwUpDownCwndGain; break; case State::ProbeBw_Down: pacingGain_ = kProbeBwDownPacingGain; cwndGain_ = kProbeBwUpDownCwndGain; break; case State::ProbeBw_Cruise: case State::ProbeBw_Refill: pacingGain_ = conn_.transportSettings.ccaConfig.overrideCruisePacingGain > 0 ? conn_.transportSettings.ccaConfig.overrideCruisePacingGain : kProbeBwCruiseRefillPacingGain; cwndGain_ = conn_.transportSettings.ccaConfig.overrideCruiseCwndGain > 0 ? conn_.transportSettings.ccaConfig.overrideCruiseCwndGain : kProbeBwCruiseRefillCwndGain; break; case State::ProbeRTT: pacingGain_ = kProbeRttPacingGain; cwndGain_ = kProbeRttCwndGain; break; } } std::string bbr2StateToString(Bbr2CongestionController::State state) { switch (state) { case Bbr2CongestionController::State::Startup: return "Startup"; case Bbr2CongestionController::State::Drain: return "Drain"; case Bbr2CongestionController::State::ProbeBw_Down: return "ProbeBw_Down"; case Bbr2CongestionController::State::ProbeBw_Cruise: return "ProbeBw_Cruise"; case Bbr2CongestionController::State::ProbeBw_Refill: return "ProbeBw_Refill"; case Bbr2CongestionController::State::ProbeBw_Up: return "ProbeBw_Up"; case Bbr2CongestionController::State::ProbeRTT: return "ProbeRTT"; } folly::assume_unreachable(); } } // namespace quic