Adds the B207 connection option which causes QUIC BBR2 to exit STARTUP if bytes in flight doesn't go below 2*BDP at any point in the last round.

I ran all simulator tests with this default enabled and none failed in a concerning way.  For example, sometimes the loss rate was lower, which is to be expected.

Protected by quic_reloadable_flag_quic_bbr2_exit_startup_on_persistent_queue.

PiperOrigin-RevId: 404074041
diff --git a/quic/core/congestion_control/bbr2_misc.cc b/quic/core/congestion_control/bbr2_misc.cc
index 7bc09ae..78458cd 100644
--- a/quic/core/congestion_control/bbr2_misc.cc
+++ b/quic/core/congestion_control/bbr2_misc.cc
@@ -431,4 +431,15 @@
   return return_value;
 }
 
+void Bbr2NetworkModel::CheckPersistentQueue(
+    const Bbr2CongestionEvent& congestion_event) {
+  QUICHE_DCHECK(!full_bandwidth_reached_);
+  QUICHE_DCHECK(congestion_event.end_of_round_trip);
+  QUICHE_DCHECK_NE(0u, min_bytes_in_flight_in_round_);
+  if (min_bytes_in_flight_in_round_ >
+      (params_->startup_cwnd_gain * BDP() + QueueingThresholdExtraBytes())) {
+    full_bandwidth_reached_ = true;
+  }
+}
+
 }  // namespace quic
diff --git a/quic/core/congestion_control/bbr2_misc.h b/quic/core/congestion_control/bbr2_misc.h
index 648e4df..2a8bccf 100644
--- a/quic/core/congestion_control/bbr2_misc.h
+++ b/quic/core/congestion_control/bbr2_misc.h
@@ -95,10 +95,14 @@
   // If false, exit STARTUP on loss only if bandwidth is below threshold.
   bool always_exit_startup_on_excess_loss = false;
 
-  // If true, inclue extra acked during STARTUP and proactively reduce extra
+  // If true, include extra acked during STARTUP and proactively reduce extra
   // acked when bandwidth increases.
   bool startup_include_extra_acked = false;
 
+  // If true, exit STARTUP if bytes in flight has not gone below 2 * BDP at
+  // any point in the last round.
+  bool exit_startup_on_persistent_queue = false;
+
   /*
    * DRAIN parameters.
    */
@@ -397,6 +401,10 @@
     return bandwidth_sampler_.max_ack_height();
   }
 
+  QuicByteCount QueueingThresholdExtraBytes() const {
+    return 2 * kDefaultTCPMSS;
+  }
+
   bool cwnd_limited_before_aggregation_epoch() const {
     return cwnd_limited_before_aggregation_epoch_;
   }
@@ -465,6 +473,8 @@
   BandwidthGrowth CheckBandwidthGrowth(
       const Bbr2CongestionEvent& congestion_event);
 
+  void CheckPersistentQueue(const Bbr2CongestionEvent& congestion_event);
+
   QuicPacketNumber last_sent_packet() const {
     return round_trip_counter_.last_sent_packet();
   }
diff --git a/quic/core/congestion_control/bbr2_probe_bw.cc b/quic/core/congestion_control/bbr2_probe_bw.cc
index 579bdba..706ecd3 100644
--- a/quic/core/congestion_control/bbr2_probe_bw.cc
+++ b/quic/core/congestion_control/bbr2_probe_bw.cc
@@ -485,7 +485,8 @@
     //   HasPhaseLasted(model_->MinRtt(), congestion_event)
   } else if (cycle_.rounds_in_phase > 0) {
     const QuicByteCount bdp = model_->BDP();
-    QuicByteCount queuing_threshold_extra_bytes = 2 * kDefaultTCPMSS;
+    QuicByteCount queuing_threshold_extra_bytes =
+        model_->QueueingThresholdExtraBytes();
     if (Params().probe_up_dont_exit_if_no_queue_) {
       QUIC_RELOADABLE_FLAG_COUNT_N(quic_bbr2_no_probe_up_exit_if_no_queue, 1,
                                    2);
diff --git a/quic/core/congestion_control/bbr2_sender.cc b/quic/core/congestion_control/bbr2_sender.cc
index 23e15b8..b016ba1 100644
--- a/quic/core/congestion_control/bbr2_sender.cc
+++ b/quic/core/congestion_control/bbr2_sender.cc
@@ -194,6 +194,11 @@
     QUIC_RELOADABLE_FLAG_COUNT_N(quic_bbr2_startup_extra_acked, 2, 2);
     params_.startup_include_extra_acked = true;
   }
+  if (GetQuicReloadableFlag(quic_bbr2_exit_startup_on_persistent_queue) &&
+      ContainsQuicTag(connection_options, kB207)) {
+    params_.exit_startup_on_persistent_queue = true;
+  }
+
   if (GetQuicReloadableFlag(
           quic_bbr_start_new_aggregation_epoch_after_a_full_round) &&
       ContainsQuicTag(connection_options, kBBRA)) {
diff --git a/quic/core/congestion_control/bbr2_simulator_test.cc b/quic/core/congestion_control/bbr2_simulator_test.cc
index 5214601..0968bb1 100644
--- a/quic/core/congestion_control/bbr2_simulator_test.cc
+++ b/quic/core/congestion_control/bbr2_simulator_test.cc
@@ -390,6 +390,34 @@
   EXPECT_FALSE(sender_->ExportDebugState().last_sample_is_app_limited);
 }
 
+TEST_F(Bbr2DefaultTopologyTest, NormalStartupB207) {
+  SetQuicReloadableFlag(quic_bbr2_exit_startup_on_persistent_queue, true);
+  SetConnectionOption(kB207);
+  DefaultTopologyParams params;
+  CreateNetwork(params);
+
+  // Run until the full bandwidth is reached and check how many rounds it was.
+  sender_endpoint_.AddBytesToTransfer(12 * 1024 * 1024);
+  QuicRoundTripCount max_bw_round = 0;
+  QuicBandwidth max_bw(QuicBandwidth::Zero());
+  bool simulator_result = simulator_.RunUntilOrTimeout(
+      [this, &max_bw, &max_bw_round]() {
+        if (max_bw < sender_->ExportDebugState().bandwidth_hi) {
+          max_bw = sender_->ExportDebugState().bandwidth_hi;
+          max_bw_round = sender_->ExportDebugState().round_trip_count;
+        }
+        return sender_->ExportDebugState().startup.full_bandwidth_reached;
+      },
+      QuicTime::Delta::FromSeconds(5));
+  ASSERT_TRUE(simulator_result);
+  EXPECT_EQ(Bbr2Mode::DRAIN, sender_->ExportDebugState().mode);
+  EXPECT_EQ(0u, sender_->ExportDebugState().round_trip_count - max_bw_round);
+  EXPECT_EQ(
+      0u,
+      sender_->ExportDebugState().startup.round_trips_without_bandwidth_growth);
+  EXPECT_EQ(0u, sender_connection_stats().packets_lost);
+}
+
 // Test a simple long data transfer in the default setup.
 TEST_F(Bbr2DefaultTopologyTest, SimpleTransfer) {
   DefaultTopologyParams params;
@@ -483,6 +511,26 @@
   EXPECT_APPROX_EQ(params.RTT(), rtt_stats()->min_rtt(), 0.2f);
 }
 
+TEST_F(Bbr2DefaultTopologyTest, SimpleTransferB207) {
+  SetQuicReloadableFlag(quic_bbr2_exit_startup_on_persistent_queue, true);
+  SetConnectionOption(kB207);
+  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, SimpleTransferBBRB) {
   SetQuicReloadableFlag(quic_bbr_use_send_rate_in_max_ack_height_tracker, true);
   SetConnectionOption(kBBRB);
diff --git a/quic/core/congestion_control/bbr2_startup.cc b/quic/core/congestion_control/bbr2_startup.cc
index c276f36..47a1003 100644
--- a/quic/core/congestion_control/bbr2_startup.cc
+++ b/quic/core/congestion_control/bbr2_startup.cc
@@ -8,6 +8,7 @@
 #include "quic/core/congestion_control/bbr2_sender.h"
 #include "quic/core/quic_bandwidth.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"
 
@@ -46,8 +47,12 @@
     const AckedPacketVector& /*acked_packets*/,
     const LostPacketVector& /*lost_packets*/,
     const Bbr2CongestionEvent& congestion_event) {
+  if (Params().exit_startup_on_persistent_queue) {
+    QUIC_RELOADABLE_FLAG_COUNT(quic_bbr2_exit_startup_on_persistent_queue);
+    model_->CheckPersistentQueue(congestion_event);
+  }
   if (!model_->full_bandwidth_reached() && congestion_event.end_of_round_trip) {
-    // TCP BBR always exits upon excessive losses. QUIC BBRv1 does not exits
+    // TCP BBR always exits upon excessive losses. QUIC BBRv1 does not exit
     // upon excessive losses, if enough bandwidth growth is observed.
     Bbr2NetworkModel::BandwidthGrowth bw_growth =
         model_->CheckBandwidthGrowth(congestion_event);
diff --git a/quic/core/crypto/crypto_protocol.h b/quic/core/crypto/crypto_protocol.h
index f50061e..efe830a 100644
--- a/quic/core/crypto/crypto_protocol.h
+++ b/quic/core/crypto/crypto_protocol.h
@@ -165,11 +165,13 @@
                                                  // inflight dips below 1.25*BW.
 const QuicTag kB203 = TAG('B', '2', '0', '3');   // Ignore inflight_hi until
                                                  // PROBE_UP is exited.
-const QuicTag kB206 = TAG('B', '2', '0', '6');   // Exit STARTUP after 2 losses.
 const QuicTag kB204 = TAG('B', '2', '0', '4');   // Reduce extra acked when
                                                  // MaxBW incrases.
 const QuicTag kB205 = TAG('B', '2', '0', '5');   // Add extra acked to CWND in
                                                  // STARTUP.
+const QuicTag kB206 = TAG('B', '2', '0', '6');   // Exit STARTUP after 2 losses.
+const QuicTag kB207 = TAG('B', '2', '0', '7');   // Exit STARTUP on persistent
+                                                 // queue
 const QuicTag kNTLP = TAG('N', 'T', 'L', 'P');   // No tail loss probe
 const QuicTag k1TLP = TAG('1', 'T', 'L', 'P');   // 1 tail loss probe
 const QuicTag k1RTO = TAG('1', 'R', 'T', 'O');   // Send 1 packet upon RTO
diff --git a/quic/core/quic_flags_list.h b/quic/core/quic_flags_list.h
index 1ecde55..02032a5 100644
--- a/quic/core/quic_flags_list.h
+++ b/quic/core/quic_flags_list.h
@@ -141,6 +141,8 @@
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_bbr2_ignore_inflight_hi_in_probe_up, true)
 // When true, the B204 connection option enables extra acked in STARTUP, but also adds new logic to decrease it whenever max bandwidth increases.
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_bbr2_startup_extra_acked, true)
+// When true, the B207 connection option causes BBR2 to exit STARTUP if a persistent queue of 2*BDP has existed for the entire round.
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_bbr2_exit_startup_on_persistent_queue, true)
 // When true, the BBQ0 connection option causes QUIC BBR2 to add bytes_acked to probe_up_acked if the connection hasn\'t been app-limited since inflight_hi was utilized.
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_bbr2_add_bytes_acked_after_inflight_hi_limited, true)
 // When true, the BBR4 copt sets the extra_acked window to 20 RTTs and BBR5 sets it to 40 RTTs.