For QUIC BBRv2, if connection option B2NE is set, do not exit STARTUP on loss if there are enough bandwidth growth in round.

Protected by FLAGS_quic_reloadable_flag_quic_bbr2_no_exit_startup_on_loss_with_bw_growth.

PiperOrigin-RevId: 337094247
Change-Id: I6d120e45004e4aa2cd894ea29a2f502d50ed8921
diff --git a/quic/core/congestion_control/bbr2_misc.h b/quic/core/congestion_control/bbr2_misc.h
index a6f710a..80a7423 100644
--- a/quic/core/congestion_control/bbr2_misc.h
+++ b/quic/core/congestion_control/bbr2_misc.h
@@ -92,6 +92,10 @@
   int64_t startup_full_loss_count =
       GetQuicFlag(FLAGS_quic_bbr2_default_startup_full_loss_count);
 
+  // If true, always exit STARTUP on loss, even if bandwidth exceeds threshold.
+  // If false, exit STARTUP on loss only if bandwidth is below threshold.
+  bool always_exit_startup_on_excess_loss = true;
+
   /*
    * DRAIN parameters.
    */
diff --git a/quic/core/congestion_control/bbr2_sender.cc b/quic/core/congestion_control/bbr2_sender.cc
index 3185cc0..4e57a9a 100644
--- a/quic/core/congestion_control/bbr2_sender.cc
+++ b/quic/core/congestion_control/bbr2_sender.cc
@@ -11,6 +11,7 @@
 #include "net/third_party/quiche/src/quic/core/congestion_control/bbr2_misc.h"
 #include "net/third_party/quiche/src/quic/core/crypto/crypto_protocol.h"
 #include "net/third_party/quiche/src/quic/core/quic_bandwidth.h"
+#include "net/third_party/quiche/src/quic/core/quic_tag.h"
 #include "net/third_party/quiche/src/quic/core/quic_types.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_flag_utils.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
@@ -156,6 +157,12 @@
       params_.drain_cwnd_gain = 2;
     }
   }
+  if (GetQuicReloadableFlag(quic_bbr2_no_exit_startup_on_loss_with_bw_growth) &&
+      ContainsQuicTag(connection_options, kB2NE)) {
+    QUIC_RELOADABLE_FLAG_COUNT(
+        quic_bbr2_no_exit_startup_on_loss_with_bw_growth);
+    params_.always_exit_startup_on_excess_loss = false;
+  }
   if (ContainsQuicTag(connection_options, kBSAO)) {
     model_.EnableOverestimateAvoidance();
   }
diff --git a/quic/core/congestion_control/bbr2_simulator_test.cc b/quic/core/congestion_control/bbr2_simulator_test.cc
index 4273667..5e5ffee 100644
--- a/quic/core/congestion_control/bbr2_simulator_test.cc
+++ b/quic/core/congestion_control/bbr2_simulator_test.cc
@@ -413,6 +413,25 @@
   EXPECT_APPROX_EQ(params.RTT(), rtt_stats()->smoothed_rtt(), 1.0f);
 }
 
+TEST_F(Bbr2DefaultTopologyTest, SimpleTransferB2NE) {
+  SetConnectionOption(kB2NE);
+  DefaultTopologyParams params;
+  CreateNetwork(params);
+
+  // Transfer 12MB.
+  DoSimpleTransfer(12 * 1024 * 1024, QuicTime::Delta::FromSeconds(35));
+  EXPECT_TRUE(Bbr2ModeIsOneOf({Bbr2Mode::PROBE_BW, Bbr2Mode::PROBE_RTT}));
+
+  EXPECT_APPROX_EQ(params.BottleneckBandwidth(),
+                   sender_->ExportDebugState().bandwidth_hi, 0.01f);
+
+  EXPECT_LE(sender_loss_rate_in_packets(), 0.05);
+  // The margin here is high, because the aggregation greatly increases
+  // smoothed rtt.
+  EXPECT_GE(params.RTT() * 4, rtt_stats()->smoothed_rtt());
+  EXPECT_APPROX_EQ(params.RTT(), rtt_stats()->min_rtt(), 0.2f);
+}
+
 TEST_F(Bbr2DefaultTopologyTest, SimpleTransferSmallBuffer) {
   DefaultTopologyParams params;
   params.switch_queue_capacity_in_bdp = 0.5;
diff --git a/quic/core/congestion_control/bbr2_startup.cc b/quic/core/congestion_control/bbr2_startup.cc
index 72ad66f..8947766 100644
--- a/quic/core/congestion_control/bbr2_startup.cc
+++ b/quic/core/congestion_control/bbr2_startup.cc
@@ -44,11 +44,13 @@
     const LostPacketVector& /*lost_packets*/,
     const Bbr2CongestionEvent& congestion_event) {
   if (!full_bandwidth_reached_ && congestion_event.end_of_round_trip) {
-    if (!congestion_event.last_sample_is_app_limited) {
-      CheckBandwidthGrowth(congestion_event);
-    }
+    // TCP BBR always exits upon excessive losses. QUIC BBRv1 does not exits
+    // upon excessive losses, if enough bandwidth growth is observed.
+    bool has_enough_bw_growth = CheckBandwidthGrowth(congestion_event);
 
-    CheckExcessiveLosses(congestion_event);
+    if (Params().always_exit_startup_on_excess_loss || !has_enough_bw_growth) {
+      CheckExcessiveLosses(congestion_event);
+    }
   }
 
   model_->set_pacing_gain(Params().startup_pacing_gain);
@@ -62,7 +64,11 @@
     const Bbr2CongestionEvent& congestion_event) {
   DCHECK(!full_bandwidth_reached_);
   DCHECK(congestion_event.end_of_round_trip);
-  DCHECK(!congestion_event.last_sample_is_app_limited);
+  if (congestion_event.last_sample_is_app_limited) {
+    // Return true such that when Params().always_exit_startup_on_excess_loss is
+    // false, we'll not check excess loss, which is the behavior of QUIC BBRv1.
+    return true;
+  }
 
   QuicBandwidth threshold =
       full_bandwidth_baseline_ * Params().startup_full_bw_threshold;
diff --git a/quic/core/crypto/crypto_protocol.h b/quic/core/crypto/crypto_protocol.h
index d272aeb..de9d9d3 100644
--- a/quic/core/crypto/crypto_protocol.h
+++ b/quic/core/crypto/crypto_protocol.h
@@ -118,6 +118,9 @@
 const QuicTag kB2ON = TAG('B', '2', 'O', 'N');   // Enable BBRv2
 const QuicTag kB2NA = TAG('B', '2', 'N', 'A');   // For BBRv2, do not add ack
                                                  // height to queueing threshold
+const QuicTag kB2NE = TAG('B', '2', 'N', 'E');   // For BBRv2, do not exit
+                                                 // STARTUP if there's enough
+                                                 // bandwidth growth
 const QuicTag kB2RP = TAG('B', '2', 'R', 'P');   // For BBRv2, run PROBE_RTT on
                                                  // the regular schedule
 const QuicTag kB2CL = TAG('B', '2', 'C', 'L');   // For BBRv2, allow PROBE_BW