In MaxAckHeightTracker, always starts a new ack aggregation epoch if a full round has passed since the start of the current epoch.  Enabled by the BBRA connection option.

Protected by FLAGS_quic_reloadable_flag_quic_bbr_start_new_aggregation_epoch_after_a_full_round.

PiperOrigin-RevId: 398548798
diff --git a/quic/core/congestion_control/bandwidth_sampler.cc b/quic/core/congestion_control/bandwidth_sampler.cc
index 657cad1..81f9e91 100644
--- a/quic/core/congestion_control/bandwidth_sampler.cc
+++ b/quic/core/congestion_control/bandwidth_sampler.cc
@@ -23,13 +23,31 @@
   return os;
 }
 
-QuicByteCount MaxAckHeightTracker::Update(QuicBandwidth bandwidth_estimate,
-                                          QuicRoundTripCount round_trip_count,
-                                          QuicTime ack_time,
-                                          QuicByteCount bytes_acked) {
-  if (aggregation_epoch_start_time_ == QuicTime::Zero()) {
+QuicByteCount MaxAckHeightTracker::Update(
+    QuicBandwidth bandwidth_estimate, QuicRoundTripCount round_trip_count,
+    QuicPacketNumber last_sent_packet_number,
+    QuicPacketNumber last_acked_packet_number, QuicTime ack_time,
+    QuicByteCount bytes_acked) {
+  bool force_new_epoch = false;
+
+  // If any packet sent after the start of the epoch has been acked, start a new
+  // epoch.
+  if (start_new_aggregation_epoch_after_full_round_ &&
+      last_sent_packet_number_before_epoch_.IsInitialized() &&
+      last_acked_packet_number.IsInitialized() &&
+      last_acked_packet_number > last_sent_packet_number_before_epoch_) {
+    QUIC_RELOADABLE_FLAG_COUNT(
+        quic_bbr_start_new_aggregation_epoch_after_a_full_round);
+    QUIC_DVLOG(3) << "Force starting a new aggregation epoch. "
+                     "last_sent_packet_number_before_epoch_:"
+                  << last_sent_packet_number_before_epoch_
+                  << ", last_acked_packet_number:" << last_acked_packet_number;
+    force_new_epoch = true;
+  }
+  if (aggregation_epoch_start_time_ == QuicTime::Zero() || force_new_epoch) {
     aggregation_epoch_bytes_ = bytes_acked;
     aggregation_epoch_start_time_ = ack_time;
+    last_sent_packet_number_before_epoch_ = last_sent_packet_number;
     ++num_ack_aggregation_epochs_;
     return 0;
   }
@@ -57,6 +75,7 @@
     // Reset to start measuring a new aggregation epoch.
     aggregation_epoch_bytes_ = bytes_acked;
     aggregation_epoch_start_time_ = ack_time;
+    last_sent_packet_number_before_epoch_ = last_sent_packet_number;
     ++num_ack_aggregation_epochs_;
     return 0;
   }
@@ -67,7 +86,7 @@
   QuicByteCount extra_bytes_acked =
       aggregation_epoch_bytes_ - expected_bytes_acked;
   QUIC_DVLOG(3) << "Updating MaxAckHeight. ack_time:" << ack_time
-                << ", round trip count:" << round_trip_count
+                << ", last sent packet:" << last_sent_packet_number
                 << ", bandwidth_estimate:" << bandwidth_estimate
                 << ", bytes_acked:" << bytes_acked
                 << ", expected_bytes_acked:" << expected_bytes_acked
@@ -105,6 +124,7 @@
       last_acked_packet_sent_time_(other.last_acked_packet_sent_time_),
       last_acked_packet_ack_time_(other.last_acked_packet_ack_time_),
       last_sent_packet_(other.last_sent_packet_),
+      last_acked_packet_(other.last_acked_packet_),
       is_app_limited_(other.is_app_limited_),
       end_of_app_limited_phase_(other.end_of_app_limited_phase_),
       connection_state_map_(other.connection_state_map_),
@@ -301,8 +321,8 @@
   total_bytes_acked_after_last_ack_event_ = total_bytes_acked_;
 
   QuicByteCount extra_acked = max_ack_height_tracker_.Update(
-      bandwidth_estimate, round_trip_count, last_acked_packet_ack_time_,
-      newly_acked_bytes);
+      bandwidth_estimate, round_trip_count, last_sent_packet_,
+      last_acked_packet_, last_acked_packet_ack_time_, newly_acked_bytes);
   // If |extra_acked| is zero, i.e. this ack event marks the start of a new ack
   // aggregation epoch, save LessRecentPoint, which is the last ack point of the
   // previous epoch, as a A0 candidate.
@@ -316,6 +336,7 @@
 BandwidthSample BandwidthSampler::OnPacketAcknowledged(
     QuicTime ack_time,
     QuicPacketNumber packet_number) {
+  last_acked_packet_ = packet_number;
   ConnectionStateOnSentPacket* sent_packet_pointer =
       connection_state_map_.GetEntry(packet_number);
   if (sent_packet_pointer == nullptr) {
diff --git a/quic/core/congestion_control/bandwidth_sampler.h b/quic/core/congestion_control/bandwidth_sampler.h
index 2d5924b..4c72939 100644
--- a/quic/core/congestion_control/bandwidth_sampler.h
+++ b/quic/core/congestion_control/bandwidth_sampler.h
@@ -9,6 +9,7 @@
 #include "quic/core/congestion_control/windowed_filter.h"
 #include "quic/core/packet_number_indexed_queue.h"
 #include "quic/core/quic_bandwidth.h"
+#include "quic/core/quic_packet_number.h"
 #include "quic/core/quic_packets.h"
 #include "quic/core/quic_time.h"
 #include "quic/core/quic_types.h"
@@ -104,8 +105,9 @@
 
   QuicByteCount Update(QuicBandwidth bandwidth_estimate,
                        QuicRoundTripCount round_trip_count,
-                       QuicTime ack_time,
-                       QuicByteCount bytes_acked);
+                       QuicPacketNumber last_sent_packet_number,
+                       QuicPacketNumber last_acked_packet_number,
+                       QuicTime ack_time, QuicByteCount bytes_acked);
 
   void SetFilterWindowLength(QuicRoundTripCount length) {
     max_ack_height_filter_.SetWindowLength(length);
@@ -119,6 +121,10 @@
     ack_aggregation_bandwidth_threshold_ = threshold;
   }
 
+  void SetStartNewAggregationEpochAfterFullRound(bool value) {
+    start_new_aggregation_epoch_after_full_round_ = value;
+  }
+
   double ack_aggregation_bandwidth_threshold() const {
     return ack_aggregation_bandwidth_threshold_;
   }
@@ -139,11 +145,14 @@
   // The time this aggregation started and the number of bytes acked during it.
   QuicTime aggregation_epoch_start_time_ = QuicTime::Zero();
   QuicByteCount aggregation_epoch_bytes_ = 0;
+  // The last sent packet number before the current aggregation epoch started.
+  QuicPacketNumber last_sent_packet_number_before_epoch_;
   // The number of ack aggregation epochs ever started, including the ongoing
   // one. Stats only.
   uint64_t num_ack_aggregation_epochs_ = 0;
   double ack_aggregation_bandwidth_threshold_ =
       GetQuicFlag(FLAGS_quic_ack_aggregation_bandwidth_threshold);
+  bool start_new_aggregation_epoch_after_full_round_ = false;
 };
 
 // An interface common to any class that can provide bandwidth samples from the
@@ -355,6 +364,10 @@
     max_ack_height_tracker_.Reset(new_height, new_time);
   }
 
+  void SetStartNewAggregationEpochAfterFullRound(bool value) {
+    max_ack_height_tracker_.SetStartNewAggregationEpochAfterFullRound(value);
+  }
+
   // AckPoint represents a point on the ack line.
   struct QUIC_NO_EXPORT AckPoint {
     QuicTime ack_time = QuicTime::Zero();
@@ -528,6 +541,9 @@
   // The most recently sent packet.
   QuicPacketNumber last_sent_packet_;
 
+  // The most recently acked packet.
+  QuicPacketNumber last_acked_packet_;
+
   // Indicates whether the bandwidth sampler is currently in an app-limited
   // phase.
   bool is_app_limited_;
diff --git a/quic/core/congestion_control/bandwidth_sampler_test.cc b/quic/core/congestion_control/bandwidth_sampler_test.cc
index 67dad1c..a371079 100644
--- a/quic/core/congestion_control/bandwidth_sampler_test.cc
+++ b/quic/core/congestion_control/bandwidth_sampler_test.cc
@@ -685,22 +685,23 @@
       QuicBandwidth::FromBytesAndTimeDelta(kRegularPacketSize,
                                            time_between_packets);
 
-  // Send and ack packet 1.
+  // Send packets 1 to 4 and ack packet 1.
   SendPacket(1);
   clock_.AdvanceTime(time_between_packets);
+  SendPacket(2);
+  SendPacket(3);
+  SendPacket(4);
   BandwidthSampler::CongestionEventSample sample = OnCongestionEvent({1}, {});
   EXPECT_EQ(first_packet_sending_rate, sample.sample_max_bandwidth);
   EXPECT_EQ(first_packet_sending_rate, max_bandwidth_);
 
-  // Send and ack packet 2, 3 and 4.
+  // Ack packet 2, 3 and 4, all of which uses S(1) to calculate ack rate since
+  // there were no acks at the time they were sent.
   round_trip_count_++;
   est_bandwidth_upper_bound_ = first_packet_sending_rate * 0.3;
-  SendPacket(2);
-  SendPacket(3);
-  SendPacket(4);
   clock_.AdvanceTime(time_between_packets);
   sample = OnCongestionEvent({2, 3, 4}, {});
-  EXPECT_EQ(first_packet_sending_rate * 3, sample.sample_max_bandwidth);
+  EXPECT_EQ(first_packet_sending_rate * 2, sample.sample_max_bandwidth);
   EXPECT_EQ(max_bandwidth_, sample.sample_max_bandwidth);
 
   EXPECT_LT(2 * kRegularPacketSize, sample.extra_acked);
@@ -710,6 +711,11 @@
  protected:
   MaxAckHeightTrackerTest() : tracker_(/*initial_filter_window=*/10) {
     tracker_.SetAckAggregationBandwidthThreshold(1.8);
+
+    if (GetQuicReloadableFlag(
+            quic_bbr_start_new_aggregation_epoch_after_a_full_round)) {
+      tracker_.SetStartNewAggregationEpochAfterFullRound(true);
+    }
   }
 
   // Run a full aggregation episode, which is one or more aggregated acks,
@@ -749,8 +755,9 @@
     QuicByteCount last_extra_acked = 0;
     for (QuicByteCount bytes = 0; bytes < aggregation_bytes;
          bytes += bytes_per_ack) {
-      QuicByteCount extra_acked =
-          tracker_.Update(bandwidth_, RoundTripCount(), now_, bytes_per_ack);
+      QuicByteCount extra_acked = tracker_.Update(
+          bandwidth_, RoundTripCount(), last_sent_packet_number_,
+          last_acked_packet_number_, now_, bytes_per_ack);
       QUIC_VLOG(1) << "T" << now_ << ": Update after " << bytes_per_ack
                    << " bytes acked, " << extra_acked << " extra bytes acked";
       // |extra_acked| should be 0 if either
@@ -784,6 +791,8 @@
   QuicBandwidth bandwidth_ = QuicBandwidth::FromBytesPerSecond(10 * 1000);
   QuicTime now_ = QuicTime::Zero() + QuicTime::Delta::FromMilliseconds(1);
   QuicTime::Delta rtt_ = QuicTime::Delta::FromMilliseconds(60);
+  QuicPacketNumber last_sent_packet_number_;
+  QuicPacketNumber last_acked_packet_number_;
 };
 
 TEST_F(MaxAckHeightTrackerTest, VeryAggregatedLargeAck) {
@@ -864,5 +873,25 @@
   EXPECT_LT(2u, tracker_.num_ack_aggregation_epochs());
 }
 
+TEST_F(MaxAckHeightTrackerTest, StartNewEpochAfterAFullRound) {
+  last_sent_packet_number_ = QuicPacketNumber(10);
+  AggregationEpisode(bandwidth_ * 2, QuicTime::Delta::FromMilliseconds(50), 100,
+                     true);
+
+  last_acked_packet_number_ = QuicPacketNumber(11);
+  // Update with a tiny bandwidth causes a very low expected bytes acked, which
+  // in turn causes the current epoch to continue if the |tracker_| doesn't
+  // check the packet numbers.
+  tracker_.Update(bandwidth_ * 0.1, RoundTripCount(), last_sent_packet_number_,
+                  last_acked_packet_number_, now_, 100);
+
+  if (GetQuicReloadableFlag(
+          quic_bbr_start_new_aggregation_epoch_after_a_full_round)) {
+    EXPECT_EQ(2u, tracker_.num_ack_aggregation_epochs());
+  } else {
+    EXPECT_EQ(1u, tracker_.num_ack_aggregation_epochs());
+  }
+}
+
 }  // namespace test
 }  // namespace quic
diff --git a/quic/core/congestion_control/bbr2_misc.h b/quic/core/congestion_control/bbr2_misc.h
index 8234999..d745b11 100644
--- a/quic/core/congestion_control/bbr2_misc.h
+++ b/quic/core/congestion_control/bbr2_misc.h
@@ -411,6 +411,10 @@
     return bandwidth_sampler_.num_ack_aggregation_epochs();
   }
 
+  void SetStartNewAggregationEpochAfterFullRound(bool value) {
+    bandwidth_sampler_.SetStartNewAggregationEpochAfterFullRound(value);
+  }
+
   bool MaybeExpireMinRtt(const Bbr2CongestionEvent& congestion_event);
 
   QuicBandwidth BandwidthEstimate() const {
diff --git a/quic/core/congestion_control/bbr2_sender.cc b/quic/core/congestion_control/bbr2_sender.cc
index 4ff7a6a..cf75e31 100644
--- a/quic/core/congestion_control/bbr2_sender.cc
+++ b/quic/core/congestion_control/bbr2_sender.cc
@@ -165,6 +165,11 @@
         quic_bbr2_check_cwnd_limited_before_aggregation_epoch);
     params_.probe_bw_check_cwnd_limited_before_aggregation_epoch = true;
   }
+  if (GetQuicReloadableFlag(
+          quic_bbr_start_new_aggregation_epoch_after_a_full_round) &&
+      ContainsQuicTag(connection_options, kBBRA)) {
+    model_.SetStartNewAggregationEpochAfterFullRound(true);
+  }
 }
 
 Limits<QuicByteCount> Bbr2Sender::GetCwndLimitsByMode() const {
diff --git a/quic/core/congestion_control/bbr2_simulator_test.cc b/quic/core/congestion_control/bbr2_simulator_test.cc
index 9157e1f..052e186 100644
--- a/quic/core/congestion_control/bbr2_simulator_test.cc
+++ b/quic/core/congestion_control/bbr2_simulator_test.cc
@@ -207,6 +207,10 @@
       // dedicated tests for this option.
       SetConnectionOption(kB201, sender);
     }
+    if (GetQuicReloadableFlag(
+            quic_bbr_start_new_aggregation_epoch_after_a_full_round)) {
+      SetConnectionOption(kBBRA, sender);
+    }
     QuicConnectionPeer::SetSendAlgorithm(endpoint->connection(), sender);
     endpoint->RecordTrace();
     return sender;
diff --git a/quic/core/congestion_control/bbr_sender.cc b/quic/core/congestion_control/bbr_sender.cc
index 940fe26..4d6ae70 100644
--- a/quic/core/congestion_control/bbr_sender.cc
+++ b/quic/core/congestion_control/bbr_sender.cc
@@ -278,6 +278,11 @@
   if (ContainsQuicTag(connection_options, kBSAO)) {
     sampler_.EnableOverestimateAvoidance();
   }
+  if (GetQuicReloadableFlag(
+          quic_bbr_start_new_aggregation_epoch_after_a_full_round) &&
+      ContainsQuicTag(connection_options, kBBRA)) {
+    sampler_.SetStartNewAggregationEpochAfterFullRound(true);
+  }
 }
 
 void BbrSender::AdjustNetworkParameters(const NetworkParams& params) {
diff --git a/quic/core/congestion_control/bbr_sender_test.cc b/quic/core/congestion_control/bbr_sender_test.cc
index 4a5cb0f..ca2adbd 100644
--- a/quic/core/congestion_control/bbr_sender_test.cc
+++ b/quic/core/congestion_control/bbr_sender_test.cc
@@ -10,6 +10,7 @@
 #include <utility>
 
 #include "quic/core/congestion_control/rtt_stats.h"
+#include "quic/core/crypto/crypto_protocol.h"
 #include "quic/core/quic_bandwidth.h"
 #include "quic/core/quic_packets.h"
 #include "quic/core/quic_types.h"
@@ -110,6 +111,10 @@
                               {&receiver_, &competing_receiver_}) {
     rtt_stats_ = bbr_sender_.connection()->sent_packet_manager().GetRttStats();
     sender_ = SetupBbrSender(&bbr_sender_);
+    if (GetQuicReloadableFlag(
+            quic_bbr_start_new_aggregation_epoch_after_a_full_round)) {
+      SetConnectionOption(kBBRA);
+    }
 
     clock_ = simulator_.GetClock();
   }
diff --git a/quic/core/crypto/crypto_protocol.h b/quic/core/crypto/crypto_protocol.h
index 13ec7fd..6de2419 100644
--- a/quic/core/crypto/crypto_protocol.h
+++ b/quic/core/crypto/crypto_protocol.h
@@ -99,6 +99,9 @@
 const QuicTag kBBR4 = TAG('B', 'B', 'R', '4');   // 20 RTT ack aggregation
 const QuicTag kBBR5 = TAG('B', 'B', 'R', '5');   // 40 RTT ack aggregation
 const QuicTag kBBR9 = TAG('B', 'B', 'R', '9');   // DEPRECATED
+const QuicTag kBBRA = TAG('B', 'B', 'R', 'A');   // Starts a new ack aggregation
+                                                 // epoch if a full round has
+                                                 // passed
 const QuicTag kBBRS = TAG('B', 'B', 'R', 'S');   // DEPRECATED
 const QuicTag kBBQ1 = TAG('B', 'B', 'Q', '1');   // BBR with lower 2.77 STARTUP
                                                  // pacing and CWND gain.
diff --git a/quic/core/quic_flags_list.h b/quic/core/quic_flags_list.h
index dfee754..f8fab11 100644
--- a/quic/core/quic_flags_list.h
+++ b/quic/core/quic_flags_list.h
@@ -37,6 +37,8 @@
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_add_missing_update_ack_timeout, true)
 // If true, allow client to enable BBRv2 on server via connection option \'B2ON\'.
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_allow_client_enabled_bbr_v2, false)
+// If true, always starts a new ack aggregation epoch if a full round has passed since the start of the current epoch.
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_bbr_start_new_aggregation_epoch_after_a_full_round, true)
 // If true, avoid calling reloadable flags in QuicVersionManager constructor by lazily initializing internal state.
 QUIC_FLAG(FLAGS_quic_restart_flag_quic_lazy_quic_version_manager, true)
 // If true, clear undecryptable packets on handshake complete.