gfe-relnote: Change QUIC BBRv2 to reduce bandwidth_lo when the BBQ6, BBQ7, BBQ8, and BBQ9 connection options are present.

Protected by quic_reloadable_flag_quic_bbr_bw_startup.

PiperOrigin-RevId: 347676357
Change-Id: Ica5cb228663c826594c9c35a49e0a18f191ec004
diff --git a/quic/core/congestion_control/bbr2_misc.cc b/quic/core/congestion_control/bbr2_misc.cc
index 0be3253..eb29983 100644
--- a/quic/core/congestion_control/bbr2_misc.cc
+++ b/quic/core/congestion_control/bbr2_misc.cc
@@ -168,6 +168,7 @@
     inflight_latest_ = sample.sample_max_inflight;
   }
 
+  // Adapt lower bounds(bandwidth_lo and inflight_lo).
   AdaptLowerBounds(*congestion_event);
 
   if (!congestion_event->end_of_round_trip) {
@@ -185,6 +186,63 @@
 
 void Bbr2NetworkModel::AdaptLowerBounds(
     const Bbr2CongestionEvent& congestion_event) {
+  if (Params().bw_lo_mode_ != Bbr2Params::DEFAULT) {
+    DCHECK(Params().bw_startup);
+    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();
+    }
+    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 << "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.
+      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_);
+    }
+    // This early return ignores inflight_lo as well.
+    return;
+  }
   if (!congestion_event.end_of_round_trip ||
       congestion_event.is_probing_for_bandwidth) {
     return;
diff --git a/quic/core/congestion_control/bbr2_misc.h b/quic/core/congestion_control/bbr2_misc.h
index c26d2f9..f02e22d 100644
--- a/quic/core/congestion_control/bbr2_misc.h
+++ b/quic/core/congestion_control/bbr2_misc.h
@@ -195,6 +195,23 @@
 
   // Can be disabled by connection option 'B2RC'.
   bool enable_reno_coexistence = true;
+
+  // For experimentation to improve fast convergence upon loss.
+  enum QuicBandwidthLoMode : uint8_t {
+    DEFAULT = 0,
+    MIN_RTT_REDUCTION = 1,   // 'BBQ7'
+    INFLIGHT_REDUCTION = 2,  // 'BBQ8'
+    CWND_REDUCTION = 3,      // 'BBQ9'
+  };
+
+  // Different modes change bandwidth_lo_ differently upon loss.
+  QuicBandwidthLoMode bw_lo_mode_ = QuicBandwidthLoMode::DEFAULT;
+
+  // Set the pacing gain to 25% larger than the recent BW increase in STARTUP.
+  bool decrease_startup_pacing_at_end_of_round = false;
+
+  // Latch the flag for quic_bbr2_bw_startup.
+  const bool bw_startup = GetQuicReloadableFlag(quic_bbr2_bw_startup);
 };
 
 class QUIC_EXPORT_PRIVATE RoundTripCounter {
diff --git a/quic/core/congestion_control/bbr2_sender.cc b/quic/core/congestion_control/bbr2_sender.cc
index c12c4e2..b27196b 100644
--- a/quic/core/congestion_control/bbr2_sender.cc
+++ b/quic/core/congestion_control/bbr2_sender.cc
@@ -133,6 +133,9 @@
   if (ContainsQuicTag(connection_options, kBBQ2)) {
     params_.startup_cwnd_gain = 2.885;
     params_.drain_cwnd_gain = 2.885;
+    if (params_.bw_startup) {
+      model_.set_cwnd_gain(params_.startup_cwnd_gain);
+    }
   }
   if (ContainsQuicTag(connection_options, kB2NE)) {
     params_.always_exit_startup_on_excess_loss = false;
@@ -156,6 +159,22 @@
   if (ContainsQuicTag(connection_options, kBSAO)) {
     model_.EnableOverestimateAvoidance();
   }
+  if (params_.bw_startup && ContainsQuicTag(connection_options, kBBQ6)) {
+    QUIC_RELOADABLE_FLAG_COUNT_N(quic_bbr2_bw_startup, 1, 4);
+    params_.decrease_startup_pacing_at_end_of_round = true;
+  }
+  if (params_.bw_startup && ContainsQuicTag(connection_options, kBBQ7)) {
+    QUIC_RELOADABLE_FLAG_COUNT_N(quic_bbr2_bw_startup, 2, 4);
+    params_.bw_lo_mode_ = Bbr2Params::QuicBandwidthLoMode::MIN_RTT_REDUCTION;
+  }
+  if (params_.bw_startup && ContainsQuicTag(connection_options, kBBQ8)) {
+    QUIC_RELOADABLE_FLAG_COUNT_N(quic_bbr2_bw_startup, 3, 4);
+    params_.bw_lo_mode_ = Bbr2Params::QuicBandwidthLoMode::INFLIGHT_REDUCTION;
+  }
+  if (params_.bw_startup && ContainsQuicTag(connection_options, kBBQ9)) {
+    QUIC_RELOADABLE_FLAG_COUNT_N(quic_bbr2_bw_startup, 4, 4);
+    params_.bw_lo_mode_ = Bbr2Params::QuicBandwidthLoMode::CWND_REDUCTION;
+  }
 }
 
 Limits<QuicByteCount> Bbr2Sender::GetCwndLimitsByMode() const {
@@ -303,11 +322,14 @@
   }
 
   QuicBandwidth target_rate = model_.pacing_gain() * model_.BandwidthEstimate();
-  if (model_.full_bandwidth_reached()) {
+  if (model_.full_bandwidth_reached() ||
+      params_.decrease_startup_pacing_at_end_of_round ||
+      params_.bw_lo_mode_ != Bbr2Params::DEFAULT) {
     pacing_rate_ = target_rate;
     return;
   }
 
+  // By default, the pacing rate never decreases in STARTUP.
   if (target_rate > pacing_rate_) {
     pacing_rate_ = target_rate;
   }
diff --git a/quic/core/congestion_control/bbr2_simulator_test.cc b/quic/core/congestion_control/bbr2_simulator_test.cc
index 5aec9cb..f681650 100644
--- a/quic/core/congestion_control/bbr2_simulator_test.cc
+++ b/quic/core/congestion_control/bbr2_simulator_test.cc
@@ -121,7 +121,11 @@
 
 class Bbr2SimulatorTest : public QuicTest {
  protected:
-  Bbr2SimulatorTest() : simulator_(&random_) {}
+  Bbr2SimulatorTest() : simulator_(&random_) {
+    // Enable this for all tests because it moves where cwnd and pacing gain
+    // are initialized.
+    SetQuicReloadableFlag(quic_bbr2_bw_startup, true);
+  }
 
   void SetUp() override {
     if (GetQuicFlag(FLAGS_quic_bbr2_test_regression_mode) == "regress") {
@@ -583,7 +587,72 @@
   CreateNetwork(params);
 
   DriveOutOfStartup(params);
-  EXPECT_LE(sender_loss_rate_in_packets(), 0.20);
+  // Packet loss is smaller with a CWND gain of 2 than 2.889.
+  EXPECT_LE(sender_loss_rate_in_packets(), 0.05);
+}
+
+// Test the number of losses decreases with packet-conservation pacing.
+TEST_F(Bbr2DefaultTopologyTest, PacketLossBBQ6SmallBufferStartup) {
+  SetQuicReloadableFlag(quic_bbr2_bw_startup, true);
+  SetConnectionOption(kBBQ2);  // Increase CWND gain.
+  SetConnectionOption(kBBQ6);
+  DefaultTopologyParams params;
+  params.switch_queue_capacity_in_bdp = 0.5;
+  CreateNetwork(params);
+
+  DriveOutOfStartup(params);
+  EXPECT_LE(sender_loss_rate_in_packets(), 0.0575);
+  // bandwidth_lo is cleared exiting STARTUP.
+  EXPECT_EQ(sender_->ExportDebugState().bandwidth_lo,
+            QuicBandwidth::Infinite());
+}
+
+// Test the number of losses decreases with min_rtt packet-conservation pacing.
+TEST_F(Bbr2DefaultTopologyTest, PacketLossBBQ7SmallBufferStartup) {
+  SetQuicReloadableFlag(quic_bbr2_bw_startup, true);
+  SetConnectionOption(kBBQ2);  // Increase CWND gain.
+  SetConnectionOption(kBBQ7);
+  DefaultTopologyParams params;
+  params.switch_queue_capacity_in_bdp = 0.5;
+  CreateNetwork(params);
+
+  DriveOutOfStartup(params);
+  EXPECT_LE(sender_loss_rate_in_packets(), 0.06);
+  // bandwidth_lo is cleared exiting STARTUP.
+  EXPECT_EQ(sender_->ExportDebugState().bandwidth_lo,
+            QuicBandwidth::Infinite());
+}
+
+// Test the number of losses decreases with Inflight packet-conservation pacing.
+TEST_F(Bbr2DefaultTopologyTest, PacketLossBBQ8SmallBufferStartup) {
+  SetQuicReloadableFlag(quic_bbr2_bw_startup, true);
+  SetConnectionOption(kBBQ2);  // Increase CWND gain.
+  SetConnectionOption(kBBQ8);
+  DefaultTopologyParams params;
+  params.switch_queue_capacity_in_bdp = 0.5;
+  CreateNetwork(params);
+
+  DriveOutOfStartup(params);
+  EXPECT_LE(sender_loss_rate_in_packets(), 0.065);
+  // bandwidth_lo is cleared exiting STARTUP.
+  EXPECT_EQ(sender_->ExportDebugState().bandwidth_lo,
+            QuicBandwidth::Infinite());
+}
+
+// Test the number of losses decreases with CWND packet-conservation pacing.
+TEST_F(Bbr2DefaultTopologyTest, PacketLossBBQ9SmallBufferStartup) {
+  SetQuicReloadableFlag(quic_bbr2_bw_startup, true);
+  SetConnectionOption(kBBQ2);  // Increase CWND gain.
+  SetConnectionOption(kBBQ9);
+  DefaultTopologyParams params;
+  params.switch_queue_capacity_in_bdp = 0.5;
+  CreateNetwork(params);
+
+  DriveOutOfStartup(params);
+  EXPECT_LE(sender_loss_rate_in_packets(), 0.065);
+  // bandwidth_lo is cleared exiting STARTUP.
+  EXPECT_EQ(sender_->ExportDebugState().bandwidth_lo,
+            QuicBandwidth::Infinite());
 }
 
 // Verify the behavior of the algorithm in the case when the connection sends
diff --git a/quic/core/congestion_control/bbr2_startup.cc b/quic/core/congestion_control/bbr2_startup.cc
index 5d02de7..9486eb1 100644
--- a/quic/core/congestion_control/bbr2_startup.cc
+++ b/quic/core/congestion_control/bbr2_startup.cc
@@ -8,6 +8,7 @@
 #include "net/third_party/quiche/src/quic/core/congestion_control/bbr2_sender.h"
 #include "net/third_party/quiche/src/quic/core/quic_bandwidth.h"
 #include "net/third_party/quiche/src/quic/core/quic_types.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
 
 namespace quic {
@@ -22,6 +23,11 @@
   sender_->connection_stats_->slowstart_count = 1;
   sender_->connection_stats_->slowstart_duration = QuicTimeAccumulator();
   sender_->connection_stats_->slowstart_duration.Start(now);
+  if (sender->Params().bw_startup) {
+    // Enter() is never called for Startup, so the gains needs to be set here.
+    model_->set_pacing_gain(Params().startup_pacing_gain);
+    model_->set_cwnd_gain(Params().startup_cwnd_gain);
+  }
 }
 
 void Bbr2StartupMode::Enter(QuicTime /*now*/,
@@ -32,6 +38,8 @@
 void Bbr2StartupMode::Leave(QuicTime now,
                             const Bbr2CongestionEvent* /*congestion_event*/) {
   sender_->connection_stats_->slowstart_duration.Stop(now);
+  // Clear bandwidth_lo if it's set during STARTUP.
+  model_->clear_bandwidth_lo();
 }
 
 Bbr2Mode Bbr2StartupMode::OnCongestionEvent(
@@ -50,8 +58,42 @@
     }
   }
 
-  model_->set_pacing_gain(Params().startup_pacing_gain);
-  model_->set_cwnd_gain(Params().startup_cwnd_gain);
+  if (Params().decrease_startup_pacing_at_end_of_round) {
+    DCHECK_GT(model_->pacing_gain(), 0);
+    DCHECK(Params().bw_startup);
+    if (congestion_event.end_of_round_trip &&
+        !congestion_event.last_sample_is_app_limited) {
+      // Multiply by startup_pacing_gain, so if the bandwidth doubles,
+      // the pacing gain will be the full startup_pacing_gain.
+      if (max_bw_at_round_beginning_ > QuicBandwidth::Zero()) {
+        const float bandwidth_ratio =
+            std::max(1., model_->MaxBandwidth().ToBitsPerSecond() /
+                             static_cast<double>(
+                                 max_bw_at_round_beginning_.ToBitsPerSecond()));
+        // Even when bandwidth isn't increasing, use a gain large enough to
+        // cause a startup_full_bw_threshold increase.
+        const float new_gain =
+            ((bandwidth_ratio - 1) * (Params().startup_pacing_gain -
+                                      Params().startup_full_bw_threshold)) +
+            Params().startup_full_bw_threshold;
+        // Allow the pacing gain to decrease.
+        model_->set_pacing_gain(
+            std::min(Params().startup_pacing_gain, new_gain));
+        // Clear bandwidth_lo if it's less than the pacing rate.
+        // This avoids a constantly app-limited flow from having it's pacing
+        // gain effectively decreased below 1.25.
+        if (model_->bandwidth_lo() <
+            model_->MaxBandwidth() * model_->pacing_gain()) {
+          model_->clear_bandwidth_lo();
+        }
+      }
+      max_bw_at_round_beginning_ = model_->MaxBandwidth();
+    }
+  } else if (!Params().bw_startup) {
+    // When the flag is enabled, set these in the constructor.
+    model_->set_pacing_gain(Params().startup_pacing_gain);
+    model_->set_cwnd_gain(Params().startup_cwnd_gain);
+  }
 
   // TODO(wub): Maybe implement STARTUP => PROBE_RTT.
   return model_->full_bandwidth_reached() ? Bbr2Mode::DRAIN : Bbr2Mode::STARTUP;
diff --git a/quic/core/congestion_control/bbr2_startup.h b/quic/core/congestion_control/bbr2_startup.h
index f753a1e..6b44ae8 100644
--- a/quic/core/congestion_control/bbr2_startup.h
+++ b/quic/core/congestion_control/bbr2_startup.h
@@ -57,6 +57,8 @@
   const Bbr2Params& Params() const;
 
   void CheckExcessiveLosses(const Bbr2CongestionEvent& congestion_event);
+  // Used when the pacing gain can decrease in STARTUP.
+  QuicBandwidth max_bw_at_round_beginning_ = QuicBandwidth::Zero();
 };
 
 QUIC_EXPORT_PRIVATE std::ostream& operator<<(
diff --git a/quic/core/crypto/crypto_protocol.h b/quic/core/crypto/crypto_protocol.h
index d0420cc..acf44a4 100644
--- a/quic/core/crypto/crypto_protocol.h
+++ b/quic/core/crypto/crypto_protocol.h
@@ -108,6 +108,14 @@
 const QuicTag kBBQ5 = TAG('B', 'B', 'Q', '5');   // Expire ack aggregation upon
                                                  // bandwidth increase in
                                                  // STARTUP.
+const QuicTag kBBQ6 = TAG('B', 'B', 'Q', '6');   // Reduce STARTUP gain to 25%
+                                                 // more than BW increase.
+const QuicTag kBBQ7 = TAG('B', 'B', 'Q', '7');   // Reduce bw_lo by
+                                                 // bytes_lost/min_rtt.
+const QuicTag kBBQ8 = TAG('B', 'B', 'Q', '8');   // Reduce bw_lo by
+                                                 // bw_lo * bytes_lost/inflight
+const QuicTag kBBQ9 = TAG('B', 'B', 'Q', '9');   // Reduce bw_lo by
+                                                 // bw_lo * bytes_lost/cwnd
 const QuicTag kRENO = TAG('R', 'E', 'N', 'O');   // Reno Congestion Control
 const QuicTag kTPCC = TAG('P', 'C', 'C', '\0');  // Performance-Oriented
                                                  // Congestion Control
diff --git a/quic/core/quic_flags_list.h b/quic/core/quic_flags_list.h
index 50b6c9e..912455f 100644
--- a/quic/core/quic_flags_list.h
+++ b/quic/core/quic_flags_list.h
@@ -11,6 +11,7 @@
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_allocate_stream_sequencer_buffer_blocks_on_demand, false)
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_allow_client_enabled_bbr_v2, false)
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_bbr2_avoid_too_low_probe_bw_cwnd, false)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_bbr2_bw_startup, false)
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_bbr2_disable_reno_coexistence, false)
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_bbr2_fewer_startup_round_trips, false)
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_bbr2_use_bytes_delivered, false)