If B2SL option is set in QUIC BBR2,  when STARTUP exits due to loss, it will set the inflight_hi to the max of bdp and max_bytes_delivered_in_round.

Protected by FLAGS_quic_reloadable_flag_quic_bbr2_startup_loss_exit_use_max_delivered.

PiperOrigin-RevId: 338737724
Change-Id: I2e7923606b6210af7f198e10687d99132dc23b07
diff --git a/quic/core/congestion_control/bbr2_misc.cc b/quic/core/congestion_control/bbr2_misc.cc
index 9f6744e..3e3937a 100644
--- a/quic/core/congestion_control/bbr2_misc.cc
+++ b/quic/core/congestion_control/bbr2_misc.cc
@@ -148,6 +148,18 @@
     loss_events_in_round_++;
   }
 
+  if (GetQuicReloadableFlag(quic_bbr2_startup_loss_exit_use_max_delivered) &&
+      congestion_event->bytes_acked > 0 &&
+      congestion_event->last_packet_send_state.is_valid &&
+      total_bytes_acked() >
+          congestion_event->last_packet_send_state.total_bytes_acked) {
+    QuicByteCount bytes_delivered =
+        total_bytes_acked() -
+        congestion_event->last_packet_send_state.total_bytes_acked;
+    max_bytes_delivered_in_round_ =
+        std::max(max_bytes_delivered_in_round_, bytes_delivered);
+  }
+
   // |bandwidth_latest_| and |inflight_latest_| only increased within a round.
   if (sample.sample_max_bandwidth > bandwidth_latest_) {
     bandwidth_latest_ = sample.sample_max_bandwidth;
@@ -282,6 +294,9 @@
 void Bbr2NetworkModel::RestartRound() {
   bytes_lost_in_round_ = 0;
   loss_events_in_round_ = 0;
+  if (GetQuicReloadableFlag(quic_bbr2_startup_loss_exit_use_max_delivered)) {
+    max_bytes_delivered_in_round_ = 0;
+  }
   round_trip_counter_.RestartRound();
 }
 
diff --git a/quic/core/congestion_control/bbr2_misc.h b/quic/core/congestion_control/bbr2_misc.h
index 80a7423..f544273 100644
--- a/quic/core/congestion_control/bbr2_misc.h
+++ b/quic/core/congestion_control/bbr2_misc.h
@@ -192,6 +192,9 @@
 
   // Can be enabled by connection optoin 'B2HI'.
   bool limit_inflight_hi_by_cwnd = false;
+
+  // Can be enabled by connection option 'B2SL'.
+  bool startup_loss_exit_use_max_delivered_for_inflight_hi = false;
 };
 
 class QUIC_EXPORT_PRIVATE RoundTripCounter {
@@ -421,6 +424,10 @@
 
   int64_t loss_events_in_round() const { return loss_events_in_round_; }
 
+  QuicByteCount max_bytes_delivered_in_round() const {
+    return max_bytes_delivered_in_round_;
+  }
+
   QuicPacketNumber end_of_app_limited_phase() const {
     return bandwidth_sampler_.end_of_app_limited_phase();
   }
@@ -473,6 +480,12 @@
   // Number of loss marking events in the current round.
   int64_t loss_events_in_round_ = 0;
 
+  // A max of bytes delivered among all congestion events in the current round.
+  // A congestions event's bytes delivered is the total bytes acked between time
+  // Ts and Ta, which is the time when the largest acked packet(within the
+  // congestion event) was sent and acked, respectively.
+  QuicByteCount max_bytes_delivered_in_round_ = 0;
+
   // Max bandwidth in the current round. Updated once per congestion event.
   QuicBandwidth bandwidth_latest_ = QuicBandwidth::Zero();
   // Max bandwidth of recent rounds. Updated once per round.
diff --git a/quic/core/congestion_control/bbr2_sender.cc b/quic/core/congestion_control/bbr2_sender.cc
index 4e57a9a..1c53323 100644
--- a/quic/core/congestion_control/bbr2_sender.cc
+++ b/quic/core/congestion_control/bbr2_sender.cc
@@ -163,6 +163,10 @@
         quic_bbr2_no_exit_startup_on_loss_with_bw_growth);
     params_.always_exit_startup_on_excess_loss = false;
   }
+  if (GetQuicReloadableFlag(quic_bbr2_startup_loss_exit_use_max_delivered) &&
+      ContainsQuicTag(connection_options, kB2SL)) {
+    params_.startup_loss_exit_use_max_delivered_for_inflight_hi = true;
+  }
   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 5e5ffee..3485c11 100644
--- a/quic/core/congestion_control/bbr2_simulator_test.cc
+++ b/quic/core/congestion_control/bbr2_simulator_test.cc
@@ -734,6 +734,44 @@
   EXPECT_FALSE(sender_->ExportDebugState().last_sample_is_app_limited);
 }
 
+// Test exiting STARTUP earlier upon loss due to loss when connection option
+// B2SL is used.
+TEST_F(Bbr2DefaultTopologyTest, ExitStartupDueToLossB2SL) {
+  SetConnectionOption(kB2SL);
+  DefaultTopologyParams params;
+  params.switch_queue_capacity_in_bdp = 0.5;
+  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_GE(2u, sender_->ExportDebugState().round_trip_count - max_bw_round);
+  EXPECT_EQ(
+      1u,
+      sender_->ExportDebugState().startup.round_trips_without_bandwidth_growth);
+  EXPECT_NE(0u, sender_connection_stats().packets_lost);
+  EXPECT_FALSE(sender_->ExportDebugState().last_sample_is_app_limited);
+
+  if (GetQuicReloadableFlag(quic_bbr2_startup_loss_exit_use_max_delivered)) {
+    EXPECT_GT(sender_->ExportDebugState().inflight_hi, 1.2f * params.BDP());
+  } else {
+    EXPECT_APPROX_EQ(sender_->ExportDebugState().inflight_hi, params.BDP(),
+                     0.1f);
+  }
+}
+
 TEST_F(Bbr2DefaultTopologyTest, SenderPoliced) {
   DefaultTopologyParams params;
   params.sender_policer_params = TrafficPolicerParams();
diff --git a/quic/core/congestion_control/bbr2_startup.cc b/quic/core/congestion_control/bbr2_startup.cc
index 8947766..5c47adb 100644
--- a/quic/core/congestion_control/bbr2_startup.cc
+++ b/quic/core/congestion_control/bbr2_startup.cc
@@ -107,11 +107,21 @@
   // At the end of a round trip. Check if loss is too high in this round.
   if (model_->IsInflightTooHigh(congestion_event,
                                 Params().startup_full_loss_count)) {
-    const QuicByteCount bdp = model_->BDP(model_->MaxBandwidth());
-    QUIC_DVLOG(3) << sender_
-                  << " Exiting STARTUP due to loss. inflight_hi:" << bdp;
+    QuicByteCount new_inflight_hi = model_->BDP(model_->MaxBandwidth());
+    if (Params().startup_loss_exit_use_max_delivered_for_inflight_hi) {
+      if (new_inflight_hi < model_->max_bytes_delivered_in_round()) {
+        QUIC_RELOADABLE_FLAG_COUNT_N(
+            quic_bbr2_startup_loss_exit_use_max_delivered, 1, 2);
+        new_inflight_hi = model_->max_bytes_delivered_in_round();
+      } else {
+        QUIC_RELOADABLE_FLAG_COUNT_N(
+            quic_bbr2_startup_loss_exit_use_max_delivered, 2, 2);
+      }
+    }
+    QUIC_DVLOG(3) << sender_ << " Exiting STARTUP due to loss. inflight_hi:"
+                  << new_inflight_hi;
     // TODO(ianswett): Add a shared method to set inflight_hi in the model.
-    model_->set_inflight_hi(bdp);
+    model_->set_inflight_hi(new_inflight_hi);
 
     full_bandwidth_reached_ = true;
     sender_->connection_stats_->bbr_exit_startup_due_to_loss = true;
diff --git a/quic/core/crypto/crypto_protocol.h b/quic/core/crypto/crypto_protocol.h
index 50fa103..248d37c 100644
--- a/quic/core/crypto/crypto_protocol.h
+++ b/quic/core/crypto/crypto_protocol.h
@@ -131,6 +131,10 @@
 const QuicTag kB2HI = TAG('B', '2', 'H', 'I');   // Limit inflight_hi reduction
                                                  // based on CWND.
 const QuicTag kB2HR = TAG('B', '2', 'H', 'R');   // 15% inflight_hi headroom.
+const QuicTag kB2SL = TAG('B', '2', 'S', 'L');   // When exiting STARTUP due to
+                                                 // loss, set inflight_hi to the
+                                                 // max of bdp and max bytes
+                                                 // delivered in round.
 const QuicTag kBSAO = TAG('B', 'S', 'A', 'O');   // Avoid Overestimation in
                                                  // Bandwidth Sampler with ack
                                                  // aggregation