gfe-relnote: For QUIC BBRv2, always count the number of loss marking events in a round and only exit PROBE_UP if that number is above a threshold. Protected by --gfe2_reloadable_flag_quic_bbr2_always_count_loss_events.

The loss event threshold for exiting PROBE_UP is controlled by protocol flag --quic_bbr2_default_probe_bw_full_loss_count, with a default value of 2.

PiperOrigin-RevId: 289924952
Change-Id: Ia7779b1c80302affec2fcfd98d6cdf8e48ce38dd
diff --git a/quic/core/congestion_control/bbr2_misc.cc b/quic/core/congestion_control/bbr2_misc.cc
index 6f6757b..352e7e6 100644
--- a/quic/core/congestion_control/bbr2_misc.cc
+++ b/quic/core/congestion_control/bbr2_misc.cc
@@ -185,7 +185,11 @@
       congestion_event->bytes_in_flight = 0;
     }
   }
-  bytes_lost_in_round_ += congestion_event->bytes_lost;
+
+  if (congestion_event->bytes_lost > 0) {
+    bytes_lost_in_round_ += congestion_event->bytes_lost;
+    loss_events_in_round_++;
+  }
 
   bandwidth_sampler_.OnAckEventEnd(BandwidthEstimate(), RoundTripCount());
 
@@ -251,7 +255,10 @@
     congestion_event->bytes_in_flight = 0;
   }
 
-  bytes_lost_in_round_ += congestion_event->bytes_lost;
+  if (congestion_event->bytes_lost > 0) {
+    bytes_lost_in_round_ += congestion_event->bytes_lost;
+    loss_events_in_round_++;
+  }
 
   // |bandwidth_latest_| and |inflight_latest_| only increased within a round.
   if (sample.sample_max_bandwidth > bandwidth_latest_) {
@@ -316,6 +323,7 @@
     }
 
     bytes_lost_in_round_ = 0;
+    loss_events_in_round_ = 0;
   }
 
   bandwidth_sampler_.RemoveObsoletePackets(least_unacked_packet);
@@ -391,6 +399,7 @@
 
 void Bbr2NetworkModel::RestartRound() {
   bytes_lost_in_round_ = 0;
+  loss_events_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 5c8c0e2..1d6fb35 100644
--- a/quic/core/congestion_control/bbr2_misc.h
+++ b/quic/core/congestion_control/bbr2_misc.h
@@ -119,6 +119,10 @@
       QuicTime::Delta::FromMilliseconds(
           GetQuicFlag(FLAGS_quic_bbr2_default_probe_bw_max_rand_duration_ms));
 
+  // The minimum number of loss marking events to exit the PROBE_UP phase.
+  int64_t probe_bw_full_loss_count =
+      GetQuicFlag(FLAGS_quic_bbr2_default_probe_bw_full_loss_count);
+
   // Multiplier to get target inflight (as multiple of BDP) for PROBE_UP phase.
   float probe_bw_probe_inflight_gain = 1.25;
 
@@ -372,6 +376,8 @@
   bool IsCongestionWindowLimited(
       const Bbr2CongestionEvent& congestion_event) const;
 
+  // TODO(wub): Replace this by a new version which takes two thresholds, one
+  // is the number of loss events, the other is the percentage of bytes lost.
   bool IsInflightTooHigh(const Bbr2CongestionEvent& congestion_event) const;
 
   QuicPacketNumber last_sent_packet() const {
@@ -395,6 +401,10 @@
     return total_bytes_sent() - total_bytes_acked() - total_bytes_lost();
   }
 
+  int64_t loss_events_in_round() const { return loss_events_in_round_; }
+
+  bool always_count_loss_events() const { return always_count_loss_events_; }
+
   QuicPacketNumber end_of_app_limited_phase() const {
     return bandwidth_sampler_.end_of_app_limited_phase();
   }
@@ -452,6 +462,11 @@
 
   // Bytes lost in the current round. Updated once per congestion event.
   QuicByteCount bytes_lost_in_round_ = 0;
+  // Number of loss marking events in the current round.
+  int64_t loss_events_in_round_ = 0;
+  // Latched value of --quic_bbr2_always_count_loss_events.
+  const bool always_count_loss_events_ =
+      GetQuicReloadableFlag(quic_bbr2_always_count_loss_events);
 
   // Max bandwidth in the current round. Updated once per congestion event.
   QuicBandwidth bandwidth_latest_ = QuicBandwidth::Zero();
diff --git a/quic/core/congestion_control/bbr2_probe_bw.cc b/quic/core/congestion_control/bbr2_probe_bw.cc
index 11b389c..781e218 100644
--- a/quic/core/congestion_control/bbr2_probe_bw.cc
+++ b/quic/core/congestion_control/bbr2_probe_bw.cc
@@ -159,7 +159,14 @@
     return NOT_ADAPTED_INVALID_SAMPLE;
   }
 
-  if (model_->IsInflightTooHigh(congestion_event)) {
+  bool has_enough_loss_events = true;
+  if (model_->always_count_loss_events()) {
+    QUIC_RELOADABLE_FLAG_COUNT_N(quic_bbr2_always_count_loss_events, 2, 2);
+    has_enough_loss_events =
+        model_->loss_events_in_round() >= Params().probe_bw_full_loss_count;
+  }
+
+  if (has_enough_loss_events && model_->IsInflightTooHigh(congestion_event)) {
     if (cycle_.is_sample_from_probing) {
       cycle_.is_sample_from_probing = false;
 
diff --git a/quic/core/congestion_control/bbr2_startup.cc b/quic/core/congestion_control/bbr2_startup.cc
index 8591d71..090dd40 100644
--- a/quic/core/congestion_control/bbr2_startup.cc
+++ b/quic/core/congestion_control/bbr2_startup.cc
@@ -98,6 +98,15 @@
     ++loss_events_in_round_;
   }
 
+  if (model_->always_count_loss_events()) {
+    DCHECK_EQ(loss_events_in_round_, model_->loss_events_in_round());
+    QUIC_RELOADABLE_FLAG_COUNT_N(quic_bbr2_always_count_loss_events, 1, 2);
+  }
+
+  const int64_t loss_events_in_round = model_->always_count_loss_events()
+                                           ? model_->loss_events_in_round()
+                                           : loss_events_in_round_;
+
   // TODO(wub): In TCP, loss based exit only happens at end of a loss round, in
   // QUIC we use the end of the normal round here. It is possible to exit after
   // any congestion event, using information of the "rolling round".
@@ -107,13 +116,13 @@
 
   QUIC_DVLOG(3)
       << sender_
-      << " CheckExcessiveLosses at end of round. loss_events_in_round_:"
-      << loss_events_in_round_
+      << " CheckExcessiveLosses at end of round. loss_events_in_round:"
+      << loss_events_in_round
       << ", threshold:" << Params().startup_full_loss_count << "  @ "
       << congestion_event.event_time;
 
   // At the end of a round trip. Check if loss is too high in this round.
-  if (loss_events_in_round_ >= Params().startup_full_loss_count &&
+  if (loss_events_in_round >= Params().startup_full_loss_count &&
       model_->IsInflightTooHigh(congestion_event)) {
     const QuicByteCount bdp = model_->BDP(model_->MaxBandwidth());
     QUIC_DVLOG(3) << sender_
diff --git a/quic/core/congestion_control/bbr2_startup.h b/quic/core/congestion_control/bbr2_startup.h
index 80539d9..6477957 100644
--- a/quic/core/congestion_control/bbr2_startup.h
+++ b/quic/core/congestion_control/bbr2_startup.h
@@ -59,6 +59,7 @@
   QuicRoundTripCount rounds_without_bandwidth_growth_;
 
   // Number of loss events in the current round trip.
+  // TODO(wub): Remove when deprecating --quic_bbr2_always_count_loss_events.
   int64_t loss_events_in_round_;
 };