Do not add lumpy tokens to QUIC's PacingSender when CWND limited.

Protected by quic_reloadable_flag_quic_fix_pacing_sender_bursts.

PiperOrigin-RevId: 378501542
diff --git a/quic/core/congestion_control/bbr2_simulator_test.cc b/quic/core/congestion_control/bbr2_simulator_test.cc
index 99acf2c..7216eea 100644
--- a/quic/core/congestion_control/bbr2_simulator_test.cc
+++ b/quic/core/congestion_control/bbr2_simulator_test.cc
@@ -477,7 +477,11 @@
   EXPECT_APPROX_EQ(params.BottleneckBandwidth(),
                    sender_->ExportDebugState().bandwidth_hi, 0.01f);
 
-  EXPECT_LE(sender_loss_rate_in_packets(), 0.05);
+  if (GetQuicReloadableFlag(quic_fix_pacing_sender_bursts)) {
+    EXPECT_EQ(sender_loss_rate_in_packets(), 0);
+  } else {
+    EXPECT_LE(sender_loss_rate_in_packets(), 0.05);
+  }
   // The margin here is high, because both link level aggregation and ack
   // decimation can greatly increase smoothed rtt.
   EXPECT_GE(params.RTT() * 5, rtt_stats()->smoothed_rtt());
@@ -558,6 +562,9 @@
       [this]() { return sender_endpoint_.bytes_to_transfer() == 0; },
       QuicTime::Delta::FromSeconds(50));
   EXPECT_TRUE(simulator_result);
+  // Ensure the full bandwidth is discovered.
+  EXPECT_APPROX_EQ(params.test_link.bandwidth,
+                   sender_->ExportDebugState().bandwidth_hi, 0.02f);
 }
 
 // Test the number of losses incurred by the startup phase in a situation when
diff --git a/quic/core/congestion_control/pacing_sender.cc b/quic/core/congestion_control/pacing_sender.cc
index a43601e..265a422 100644
--- a/quic/core/congestion_control/pacing_sender.cc
+++ b/quic/core/congestion_control/pacing_sender.cc
@@ -102,6 +102,12 @@
       // is about 10ms of queueing.
       lumpy_tokens_ = 1u;
     }
+    if (GetQuicReloadableFlag(quic_fix_pacing_sender_bursts) &&
+        (bytes_in_flight + bytes) >= sender_->GetCongestionWindow()) {
+      QUIC_RELOADABLE_FLAG_COUNT(quic_fix_pacing_sender_bursts);
+      // Don't add lumpy_tokens if the congestion controller is CWND limited.
+      lumpy_tokens_ = 1u;
+    }
   }
   --lumpy_tokens_;
   if (pacing_limited_) {
diff --git a/quic/core/congestion_control/pacing_sender_test.cc b/quic/core/congestion_control/pacing_sender_test.cc
index 9c28e9f..ec3119d 100644
--- a/quic/core/congestion_control/pacing_sender_test.cc
+++ b/quic/core/congestion_control/pacing_sender_test.cc
@@ -7,6 +7,7 @@
 #include <memory>
 #include <utility>
 
+#include "quic/core/quic_constants.h"
 #include "quic/core/quic_packets.h"
 #include "quic/platform/api/quic_flag_utils.h"
 #include "quic/platform/api/quic_flags.h"
@@ -96,7 +97,6 @@
                 OnPacketSent(clock_.Now(), prior_in_flight, packet_number_,
                              kMaxOutgoingPacketSize, retransmittable_data));
     EXPECT_CALL(*mock_sender_, GetCongestionWindow())
-        .Times(AtMost(1))
         .WillRepeatedly(Return(cwnd * kDefaultTCPMSS));
     EXPECT_CALL(*mock_sender_,
                 CanSend(prior_in_flight + kMaxOutgoingPacketSize))
@@ -459,7 +459,7 @@
 }
 
 TEST_F(PacingSenderTest, NoLumpyPacingForLowBandwidthFlows) {
-  // Set lumpy size to be 3, and cwnd faction to 0.5
+  // Set lumpy size to be 3, and cwnd fraction to 0.5
   SetQuicFlag(FLAGS_quic_lumpy_pacing_size, 3);
   SetQuicFlag(FLAGS_quic_lumpy_pacing_cwnd_fraction, 0.5f);
 
@@ -484,6 +484,43 @@
   }
 }
 
+// Regression test for b/184471302 to ensure that ACKs received back-to-back
+// don't cause bursts in sending.
+TEST_F(PacingSenderTest, NoBurstsForLumpyPacingWithAckAggregation) {
+  // Configure pacing rate of 1 packet per millisecond.
+  QuicTime::Delta inter_packet_delay = QuicTime::Delta::FromMilliseconds(1);
+  InitPacingRate(kInitialBurstPackets,
+                 QuicBandwidth::FromBytesAndTimeDelta(kMaxOutgoingPacketSize,
+                                                      inter_packet_delay));
+  UpdateRtt();
+
+  // Send kInitialBurstPackets packets, and verify that they are not paced.
+  for (int i = 0; i < kInitialBurstPackets; ++i) {
+    CheckPacketIsSentImmediately();
+  }
+  // The last packet of the burst causes the sender to be CWND limited.
+  CheckPacketIsSentImmediately(HAS_RETRANSMITTABLE_DATA,
+                               10 * kMaxOutgoingPacketSize, false, 10);
+
+  if (GetQuicReloadableFlag(quic_fix_pacing_sender_bursts)) {
+    // The last sent packet made the connection CWND limited, so no lumpy tokens
+    // should be available.
+    EXPECT_EQ(0u, pacing_sender_->lumpy_tokens());
+    CheckPacketIsSentImmediately(HAS_RETRANSMITTABLE_DATA,
+                                 10 * kMaxOutgoingPacketSize, false, 10);
+    EXPECT_EQ(0u, pacing_sender_->lumpy_tokens());
+    CheckPacketIsDelayed(2 * inter_packet_delay);
+  } else {
+    EXPECT_EQ(1u, pacing_sender_->lumpy_tokens());
+    // Repeatedly send single packets to make the sender CWND limited and
+    // observe that there's no pacing without the fix.
+    for (int i = 0; i < 10; ++i) {
+      CheckPacketIsSentImmediately(HAS_RETRANSMITTABLE_DATA,
+                                   10 * kMaxOutgoingPacketSize, false, 10);
+    }
+  }
+}
+
 TEST_F(PacingSenderTest, IdealNextPacketSendTimeWithLumpyPacing) {
   // Set lumpy size to be 3, and cwnd faction to 0.5
   SetQuicFlag(FLAGS_quic_lumpy_pacing_size, 3);
diff --git a/quic/core/quic_flags_list.h b/quic/core/quic_flags_list.h
index 6c6df06..0afbf73 100644
--- a/quic/core/quic_flags_list.h
+++ b/quic/core/quic_flags_list.h
@@ -131,6 +131,8 @@
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_h3_datagram, false)
 // When true, defaults to BBR congestion control instead of Cubic.
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_default_to_bbr, false)
+// When true, prevents QUIC\'s PacingSender from generating bursts when the congestion controller is CWND limited and not pacing limited.
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_fix_pacing_sender_bursts, false)
 // When true, set the initial congestion control window from connection options in QuicSentPacketManager rather than TcpCubicSenderBytes.
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_unified_iw_options, false)