Fix a bug in BBRv2 bw_lo experimental modes where the second ACK would cause the pacing rate to suddenly drop.  Also limits reduction of bandwidth_lo to 30% in a single round at the end of a round.

Protected by quic_reloadable_flag_quic_bbr2_fix_bw_lo_mode.

PiperOrigin-RevId: 366875020
Change-Id: If1ae0d5fbdbd0770c2b4d1a219de2fd64e83e0b0
diff --git a/quic/core/congestion_control/bbr2_misc.cc b/quic/core/congestion_control/bbr2_misc.cc
index a12cd5d..d24e50e 100644
--- a/quic/core/congestion_control/bbr2_misc.cc
+++ b/quic/core/congestion_control/bbr2_misc.cc
@@ -9,6 +9,7 @@
 #include "quic/core/quic_time.h"
 #include "quic/core/quic_types.h"
 #include "quic/platform/api/quic_flag_utils.h"
+#include "quic/platform/api/quic_flags.h"
 #include "quic/platform/api/quic_logging.h"
 
 namespace quic {
@@ -201,6 +202,10 @@
     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_ =
@@ -229,9 +234,10 @@
         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_, so this backs
-      // that multiplication out to allow the pacing rate to decrease,
-      // but not below bandwidth_latest_ * 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_ *
@@ -240,6 +246,15 @@
       // Ensure bandwidth_lo isn't lower than bandwidth_latest_.
       bandwidth_lo_ = std::max(bandwidth_lo_, bandwidth_latest_);
     }
+    // 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;
   }
diff --git a/quic/core/congestion_control/bbr2_misc.h b/quic/core/congestion_control/bbr2_misc.h
index 21ceaaf..8f5d418 100644
--- a/quic/core/congestion_control/bbr2_misc.h
+++ b/quic/core/congestion_control/bbr2_misc.h
@@ -535,6 +535,9 @@
   QuicBandwidth bandwidth_latest_ = QuicBandwidth::Zero();
   // Max bandwidth of recent rounds. Updated once per round.
   QuicBandwidth bandwidth_lo_ = bandwidth_lo_default();
+  // bandwidth_lo_ at the beginning of a round with loss. Only used when the
+  // bw_lo_mode is non-default.
+  QuicBandwidth prior_bandwidth_lo_ = QuicBandwidth::Zero();
 
   // Max inflight in the current round. Updated once per congestion event.
   QuicByteCount inflight_latest_ = 0;
diff --git a/quic/core/congestion_control/bbr2_sender.cc b/quic/core/congestion_control/bbr2_sender.cc
index 1589ebd..d0f6127 100644
--- a/quic/core/congestion_control/bbr2_sender.cc
+++ b/quic/core/congestion_control/bbr2_sender.cc
@@ -321,9 +321,17 @@
   }
 
   QuicBandwidth target_rate = model_.pacing_gain() * model_.BandwidthEstimate();
-  if (model_.full_bandwidth_reached() ||
-      params_.decrease_startup_pacing_at_end_of_round ||
-      params_.bw_lo_mode_ != Bbr2Params::DEFAULT) {
+  if (model_.full_bandwidth_reached()) {
+    pacing_rate_ = target_rate;
+    return;
+  }
+  if (params_.decrease_startup_pacing_at_end_of_round &&
+      model_.pacing_gain() < Params().startup_pacing_gain) {
+    pacing_rate_ = target_rate;
+    return;
+  }
+  if (params_.bw_lo_mode_ != Bbr2Params::DEFAULT &&
+      model_.loss_events_in_round() > 0) {
     pacing_rate_ = target_rate;
     return;
   }
diff --git a/quic/core/quic_flags_list.h b/quic/core/quic_flags_list.h
index 7ef70ec..cc42f32 100644
--- a/quic/core/quic_flags_list.h
+++ b/quic/core/quic_flags_list.h
@@ -13,6 +13,7 @@
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_alps_include_scheme_in_origin, true)
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_and_tls_allow_sni_without_dots, false)
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_bbr2_avoid_too_low_probe_bw_cwnd, false)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_bbr2_fix_bw_lo_mode, false)
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_can_send_ack_frequency, true)
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_close_connection_with_too_many_outstanding_packets, true)
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_connection_support_multiple_cids_v2, false)