Internal change

PiperOrigin-RevId: 374759204
diff --git a/quic/core/congestion_control/bbr2_misc.cc b/quic/core/congestion_control/bbr2_misc.cc
index d24e50e..8d4a493 100644
--- a/quic/core/congestion_control/bbr2_misc.cc
+++ b/quic/core/congestion_control/bbr2_misc.cc
@@ -116,9 +116,9 @@
         << total_bytes_acked() - prior_bytes_acked << " bytes from "
         << acked_packets.size()
         << " packets have been acked, but sample_max_bandwidth is zero.";
+    congestion_event->sample_max_bandwidth = sample.sample_max_bandwidth;
     if (!sample.sample_is_app_limited ||
         sample.sample_max_bandwidth > MaxBandwidth()) {
-      congestion_event->sample_max_bandwidth = sample.sample_max_bandwidth;
       max_bandwidth_filter_.Update(congestion_event->sample_max_bandwidth);
     }
   }
@@ -188,99 +188,112 @@
 
 void Bbr2NetworkModel::AdaptLowerBounds(
     const Bbr2CongestionEvent& congestion_event) {
-  if (Params().bw_lo_mode_ != Bbr2Params::DEFAULT) {
-    if (congestion_event.bytes_lost == 0) {
+  if (Params().bw_lo_mode_ == Bbr2Params::DEFAULT) {
+    if (!congestion_event.end_of_round_trip ||
+        congestion_event.is_probing_for_bandwidth) {
       return;
     }
-    // Ignore losses from packets sent when probing for more bandwidth in
-    // STARTUP or PROBE_UP when they're lost in DRAIN or PROBE_DOWN.
-    if (pacing_gain_ < 1) {
-      return;
-    }
-    // Decrease bandwidth_lo whenever there is loss.
-    // Set bandwidth_lo_ if it is not yet set.
-    if (bandwidth_lo_.IsInfinite()) {
-      bandwidth_lo_ = MaxBandwidth();
-    }
-    // Save bandwidth_lo_ if it hasn't already been saved.
-    if (prior_bandwidth_lo_.IsZero()) {
-      prior_bandwidth_lo_ = bandwidth_lo_;
-    }
-    switch (Params().bw_lo_mode_) {
-      case Bbr2Params::MIN_RTT_REDUCTION:
-        bandwidth_lo_ =
-            bandwidth_lo_ - QuicBandwidth::FromBytesAndTimeDelta(
-                                congestion_event.bytes_lost, MinRtt());
-        break;
-      case Bbr2Params::INFLIGHT_REDUCTION: {
-        // Use a max of BDP and inflight to avoid starving app-limited flows.
-        const QuicByteCount effective_inflight =
-            std::max(BDP(), congestion_event.prior_bytes_in_flight);
-        // This could use bytes_lost_in_round if the bandwidth_lo_ was saved
-        // when entering 'recovery', but this BBRv2 implementation doesn't have
-        // recovery defined.
-        bandwidth_lo_ = bandwidth_lo_ *
-                        ((effective_inflight - congestion_event.bytes_lost) /
-                         static_cast<double>(effective_inflight));
-        break;
+
+    if (bytes_lost_in_round_ > 0) {
+      if (bandwidth_lo_.IsInfinite()) {
+        bandwidth_lo_ = MaxBandwidth();
       }
-      case Bbr2Params::CWND_REDUCTION:
-        bandwidth_lo_ =
-            bandwidth_lo_ *
-            ((congestion_event.prior_cwnd - congestion_event.bytes_lost) /
-             static_cast<double>(congestion_event.prior_cwnd));
-        break;
-      case Bbr2Params::DEFAULT:
-        QUIC_BUG(quic_bug_10466_1) << "Unreachable case DEFAULT.";
-    }
-    if (pacing_gain_ > Params().startup_full_bw_threshold) {
-      // In STARTUP, pacing_gain_ is applied to bandwidth_lo_ in
-      // UpdatePacingRate, so this backs that multiplication out to allow the
-      // pacing rate to decrease, but not below
-      // bandwidth_latest_ * startup_full_bw_threshold.
       bandwidth_lo_ =
-          std::max(bandwidth_lo_,
-                   bandwidth_latest_ *
-                       (Params().startup_full_bw_threshold / pacing_gain_));
-    } else {
-      // Ensure bandwidth_lo isn't lower than bandwidth_latest_.
-      bandwidth_lo_ = std::max(bandwidth_lo_, bandwidth_latest_);
+          std::max(bandwidth_latest_, bandwidth_lo_ * (1.0 - Params().beta));
+      QUIC_DVLOG(3) << "bandwidth_lo_ updated to " << bandwidth_lo_
+                    << ", bandwidth_latest_ is " << bandwidth_latest_;
+
+      if (Params().ignore_inflight_lo) {
+        return;
+      }
+      if (inflight_lo_ == inflight_lo_default()) {
+        inflight_lo_ = congestion_event.prior_cwnd;
+      }
+      inflight_lo_ = std::max<QuicByteCount>(
+          inflight_latest_, inflight_lo_ * (1.0 - Params().beta));
     }
-    // If it's the end of the round, ensure bandwidth_lo doesn't decrease more
-    // than beta.
-    if (GetQuicReloadableFlag(quic_bbr2_fix_bw_lo_mode) &&
-        congestion_event.end_of_round_trip) {
-      QUIC_RELOADABLE_FLAG_COUNT_N(quic_bbr2_fix_bw_lo_mode, 2, 2);
-      bandwidth_lo_ =
-          std::max(bandwidth_lo_, prior_bandwidth_lo_ * (1.0 - Params().beta));
-      prior_bandwidth_lo_ = QuicBandwidth::Zero();
-    }
-    // This early return ignores inflight_lo as well.
-    return;
-  }
-  if (!congestion_event.end_of_round_trip ||
-      congestion_event.is_probing_for_bandwidth) {
     return;
   }
 
-  if (bytes_lost_in_round_ > 0) {
-    if (bandwidth_lo_.IsInfinite()) {
-      bandwidth_lo_ = MaxBandwidth();
+  // Params().bw_lo_mode_ != Bbr2Params::DEFAULT
+  if (congestion_event.bytes_lost == 0) {
+    return;
+  }
+  // Ignore losses from packets sent when probing for more bandwidth in
+  // STARTUP or PROBE_UP when they're lost in DRAIN or PROBE_DOWN.
+  if (pacing_gain_ < 1) {
+    return;
+  }
+  // Decrease bandwidth_lo whenever there is loss.
+  // Set bandwidth_lo_ if it is not yet set.
+  if (bandwidth_lo_.IsInfinite()) {
+    bandwidth_lo_ = MaxBandwidth();
+  }
+  // Save bandwidth_lo_ if it hasn't already been saved.
+  if (prior_bandwidth_lo_.IsZero()) {
+    prior_bandwidth_lo_ = bandwidth_lo_;
+  }
+  switch (Params().bw_lo_mode_) {
+    case Bbr2Params::MIN_RTT_REDUCTION:
+      bandwidth_lo_ =
+          bandwidth_lo_ - QuicBandwidth::FromBytesAndTimeDelta(
+                              congestion_event.bytes_lost, MinRtt());
+      break;
+    case Bbr2Params::INFLIGHT_REDUCTION: {
+      // Use a max of BDP and inflight to avoid starving app-limited flows.
+      const QuicByteCount effective_inflight =
+          std::max(BDP(), congestion_event.prior_bytes_in_flight);
+      // This could use bytes_lost_in_round if the bandwidth_lo_ was saved
+      // when entering 'recovery', but this BBRv2 implementation doesn't have
+      // recovery defined.
+      bandwidth_lo_ =
+          bandwidth_lo_ * ((effective_inflight - congestion_event.bytes_lost) /
+                           static_cast<double>(effective_inflight));
+      break;
     }
+    case Bbr2Params::CWND_REDUCTION:
+      bandwidth_lo_ =
+          bandwidth_lo_ *
+          ((congestion_event.prior_cwnd - congestion_event.bytes_lost) /
+           static_cast<double>(congestion_event.prior_cwnd));
+      break;
+    case Bbr2Params::DEFAULT:
+      QUIC_BUG(quic_bug_10466_1) << "Unreachable case DEFAULT.";
+  }
+  QuicBandwidth last_bandwidth = bandwidth_latest_;
+  // sample_max_bandwidth will be Zero() if the loss is triggered by a timer
+  // expiring.  Ideally we'd use the most recent bandwidth sample,
+  // but bandwidth_latest is safer than Zero().
+  if (GetQuicReloadableFlag(quic_bbr2_fix_bw_lo_mode2) &&
+      !congestion_event.sample_max_bandwidth.IsZero()) {
+    QUIC_RELOADABLE_FLAG_COUNT_N(quic_bbr2_fix_bw_lo_mode2, 1, 2);
+    // bandwidth_latest_ is the max bandwidth for the round, but to allow
+    // fast, conservation style response to loss, use the last sample.
+    last_bandwidth = congestion_event.sample_max_bandwidth;
+  }
+  if (pacing_gain_ > Params().startup_full_bw_threshold) {
+    // In STARTUP, pacing_gain_ is applied to bandwidth_lo_ in
+    // UpdatePacingRate, so this backs that multiplication out to allow the
+    // pacing rate to decrease, but not below
+    // last_bandwidth * startup_full_bw_threshold.
+    // TODO(ianswett): Consider altering pacing_gain_ when in STARTUP instead.
+    bandwidth_lo_ = std::max(
+        bandwidth_lo_,
+        last_bandwidth * (Params().startup_full_bw_threshold / pacing_gain_));
+  } else {
+    // Ensure bandwidth_lo isn't lower than last_bandwidth.
+    bandwidth_lo_ = std::max(bandwidth_lo_, last_bandwidth);
+  }
+  // If it's the end of the round, ensure bandwidth_lo doesn't decrease more
+  // than beta.
+  if (GetQuicReloadableFlag(quic_bbr2_fix_bw_lo_mode) &&
+      congestion_event.end_of_round_trip) {
+    QUIC_RELOADABLE_FLAG_COUNT_N(quic_bbr2_fix_bw_lo_mode, 2, 2);
     bandwidth_lo_ =
-        std::max(bandwidth_latest_, bandwidth_lo_ * (1.0 - Params().beta));
-    QUIC_DVLOG(3) << "bandwidth_lo_ updated to " << bandwidth_lo_
-                  << ", bandwidth_latest_ is " << bandwidth_latest_;
-
-    if (Params().ignore_inflight_lo) {
-      return;
-    }
-    if (inflight_lo_ == inflight_lo_default()) {
-      inflight_lo_ = congestion_event.prior_cwnd;
-    }
-    inflight_lo_ = std::max<QuicByteCount>(
-        inflight_latest_, inflight_lo_ * (1.0 - Params().beta));
+        std::max(bandwidth_lo_, prior_bandwidth_lo_ * (1.0 - Params().beta));
+    prior_bandwidth_lo_ = QuicBandwidth::Zero();
   }
+  // These modes ignore inflight_lo as well.
 }
 
 void Bbr2NetworkModel::OnCongestionEventFinish(
diff --git a/quic/core/congestion_control/bbr2_misc.h b/quic/core/congestion_control/bbr2_misc.h
index 14a0882..b7007f5 100644
--- a/quic/core/congestion_control/bbr2_misc.h
+++ b/quic/core/congestion_control/bbr2_misc.h
@@ -313,6 +313,8 @@
   QuicTime::Delta sample_min_rtt = QuicTime::Delta::Infinite();
 
   // Maximum bandwidth of all bandwidth samples from acked_packets.
+  // This sample may be app-limited, and will be Zero() if there are no newly
+  // acknowledged inflight packets.
   QuicBandwidth sample_max_bandwidth = QuicBandwidth::Zero();
 
   // The send state of the largest packet in acked_packets, unless it is empty.
diff --git a/quic/core/congestion_control/bbr2_probe_bw.cc b/quic/core/congestion_control/bbr2_probe_bw.cc
index 9045d7e..af0f00f 100644
--- a/quic/core/congestion_control/bbr2_probe_bw.cc
+++ b/quic/core/congestion_control/bbr2_probe_bw.cc
@@ -500,6 +500,15 @@
   cycle_.rounds_in_phase = 0;
   cycle_.phase_start_time = now;
   ++sender_->connection_stats_->bbr_num_cycles;
+  if (GetQuicReloadableFlag(quic_bbr2_fix_bw_lo_mode2) &&
+      Params().bw_lo_mode_ != Bbr2Params::QuicBandwidthLoMode::DEFAULT) {
+    QUIC_RELOADABLE_FLAG_COUNT_N(quic_bbr2_fix_bw_lo_mode2, 2, 2);
+    // Clear bandwidth lo if it was set in PROBE_UP, because losses in PROBE_UP
+    // should not permanently change bandwidth_lo.
+    // It's possible for bandwidth_lo to be set during REFILL, but if that was
+    // a valid value, it'll quickly be rediscovered.
+    model_->clear_bandwidth_lo();
+  }
 
   // Pick probe wait time.
   cycle_.rounds_since_probe =
diff --git a/quic/core/quic_flags_list.h b/quic/core/quic_flags_list.h
index 0b9dd22..df0df50 100644
--- a/quic/core/quic_flags_list.h
+++ b/quic/core/quic_flags_list.h
@@ -17,6 +17,8 @@
 QUIC_FLAG(FLAGS_quic_restart_flag_quic_testonly_default_false, false)
 // A testonly restart flag that will always default to true.
 QUIC_FLAG(FLAGS_quic_restart_flag_quic_testonly_default_true, true)
+// Fix QUIC BBRv2\'s bandwidth_lo modes to better approximate packet conservation.
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_bbr2_fix_bw_lo_mode2, false)
 // If true, GFE will consider SNI values which do not contain dots (instead of throwing them away - see b/176998377).
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_and_tls_allow_sni_without_dots, true)
 // If true, QUIC BBRv2\'s PROBE_BW mode will not reduce cwnd below BDP+ack_height.