gfe-relnote: (n/a) In QUIC BBRv2, avoid unnecessary PROBE_RTTs when coming out of quiescence. Protected by --gfe2_reloadable_flag_quic_bbr2_avoid_unnecessary_probe_rtt.

This changes BBRv2 behavior in 2 places:
1. If BBRv2 is in PROBE_RTT out of quiescence, immediately transition to PROBE_BW. This allows the first round after quiescence to send more data.
2. If BBRv2 is in PROBE_BW out of quiescence, postpone the time for the next PROBE_RTT by the duration of the quiescence.

PiperOrigin-RevId: 298492623
Change-Id: I13aa0db84da70b6dd84b3478309c4dd559cf531c
diff --git a/quic/core/congestion_control/bbr2_drain.h b/quic/core/congestion_control/bbr2_drain.h
index edaedd6..548db8b 100644
--- a/quic/core/congestion_control/bbr2_drain.h
+++ b/quic/core/congestion_control/bbr2_drain.h
@@ -35,6 +35,11 @@
 
   bool IsProbingForBandwidth() const override { return false; }
 
+  Bbr2Mode OnExitQuiescence(QuicTime /*now*/,
+                            QuicTime /*quiescence_start_time*/) override {
+    return Bbr2Mode::DRAIN;
+  }
+
   struct QUIC_EXPORT_PRIVATE DebugState {
     QuicByteCount drain_target;
   };
diff --git a/quic/core/congestion_control/bbr2_misc.h b/quic/core/congestion_control/bbr2_misc.h
index 6593b42..66702ee 100644
--- a/quic/core/congestion_control/bbr2_misc.h
+++ b/quic/core/congestion_control/bbr2_misc.h
@@ -327,6 +327,12 @@
 
   QuicTime MinRttTimestamp() const { return min_rtt_filter_.GetTimestamp(); }
 
+  // TODO(wub): If we do this too frequently, we can potentailly postpone
+  // PROBE_RTT indefinitely. Observe how it works in production and improve it.
+  void PostponeMinRttTimestamp(QuicTime::Delta duration) {
+    min_rtt_filter_.ForceUpdate(MinRtt(), MinRttTimestamp() + duration);
+  }
+
   QuicBandwidth MaxBandwidth() const { return max_bandwidth_filter_.Get(); }
 
   QuicByteCount MaxAckHeight() const {
@@ -508,6 +514,9 @@
 
   virtual bool IsProbingForBandwidth() const = 0;
 
+  virtual Bbr2Mode OnExitQuiescence(QuicTime now,
+                                    QuicTime quiescence_start_time) = 0;
+
  protected:
   const Bbr2Sender* const sender_;
   Bbr2NetworkModel* model_;
diff --git a/quic/core/congestion_control/bbr2_probe_bw.cc b/quic/core/congestion_control/bbr2_probe_bw.cc
index 24b73be..a4c419a 100644
--- a/quic/core/congestion_control/bbr2_probe_bw.cc
+++ b/quic/core/congestion_control/bbr2_probe_bw.cc
@@ -92,6 +92,15 @@
          cycle_.phase == CyclePhase::PROBE_UP;
 }
 
+Bbr2Mode Bbr2ProbeBwMode::OnExitQuiescence(QuicTime now,
+                                           QuicTime quiescence_start_time) {
+  QUIC_DVLOG(3) << sender_ << " Postponing min_rtt_timestamp("
+                << model_->MinRttTimestamp() << ") by "
+                << now - quiescence_start_time;
+  model_->PostponeMinRttTimestamp(now - quiescence_start_time);
+  return Bbr2Mode::PROBE_BW;
+}
+
 void Bbr2ProbeBwMode::UpdateProbeDown(
     QuicByteCount prior_in_flight,
     const Bbr2CongestionEvent& congestion_event) {
diff --git a/quic/core/congestion_control/bbr2_probe_bw.h b/quic/core/congestion_control/bbr2_probe_bw.h
index bb1104a..5c0224f 100644
--- a/quic/core/congestion_control/bbr2_probe_bw.h
+++ b/quic/core/congestion_control/bbr2_probe_bw.h
@@ -36,6 +36,9 @@
 
   bool IsProbingForBandwidth() const override;
 
+  Bbr2Mode OnExitQuiescence(QuicTime now,
+                            QuicTime quiescence_start_time) override;
+
   enum class CyclePhase : uint8_t {
     PROBE_NOT_STARTED,
     PROBE_UP,
diff --git a/quic/core/congestion_control/bbr2_probe_rtt.cc b/quic/core/congestion_control/bbr2_probe_rtt.cc
index 5a523b9..ec5cf7c 100644
--- a/quic/core/congestion_control/bbr2_probe_rtt.cc
+++ b/quic/core/congestion_control/bbr2_probe_rtt.cc
@@ -53,6 +53,15 @@
   return NoGreaterThan(std::min(inflight_upper_bound, InflightTarget()));
 }
 
+Bbr2Mode Bbr2ProbeRttMode::OnExitQuiescence(
+    QuicTime now,
+    QuicTime /*quiescence_start_time*/) {
+  if (now > exit_time_) {
+    return Bbr2Mode::PROBE_BW;
+  }
+  return Bbr2Mode::PROBE_RTT;
+}
+
 Bbr2ProbeRttMode::DebugState Bbr2ProbeRttMode::ExportDebugState() const {
   DebugState s;
   s.inflight_target = InflightTarget();
diff --git a/quic/core/congestion_control/bbr2_probe_rtt.h b/quic/core/congestion_control/bbr2_probe_rtt.h
index 07892fa..156fda5 100644
--- a/quic/core/congestion_control/bbr2_probe_rtt.h
+++ b/quic/core/congestion_control/bbr2_probe_rtt.h
@@ -33,6 +33,9 @@
 
   bool IsProbingForBandwidth() const override { return false; }
 
+  Bbr2Mode OnExitQuiescence(QuicTime now,
+                            QuicTime quiescence_start_time) override;
+
   struct QUIC_EXPORT_PRIVATE DebugState {
     QuicByteCount inflight_target;
     QuicTime exit_time = QuicTime::Zero();
diff --git a/quic/core/congestion_control/bbr2_sender.cc b/quic/core/congestion_control/bbr2_sender.cc
index e75e4bf..5b6f6e8 100644
--- a/quic/core/congestion_control/bbr2_sender.cc
+++ b/quic/core/congestion_control/bbr2_sender.cc
@@ -12,6 +12,7 @@
 #include "net/third_party/quiche/src/quic/core/crypto/crypto_protocol.h"
 #include "net/third_party/quiche/src/quic/core/quic_bandwidth.h"
 #include "net/third_party/quiche/src/quic/core/quic_types.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flag_utils.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
 
 namespace quic {
@@ -196,6 +197,10 @@
   model_.OnCongestionEventFinish(unacked_packets_->GetLeastUnacked(),
                                  congestion_event);
   last_sample_is_app_limited_ = congestion_event.last_sample_is_app_limited;
+  if (avoid_unnecessary_probe_rtt_ && congestion_event.bytes_in_flight == 0) {
+    QUIC_RELOADABLE_FLAG_COUNT_N(quic_bbr2_avoid_unnecessary_probe_rtt, 2, 2);
+    OnEnterQuiescence(event_time);
+  }
 
   QUIC_DVLOG(3)
       << this << " END CongestionEvent(acked:" << acked_packets
@@ -287,6 +292,10 @@
                 << ", total_acked:" << model_.total_bytes_acked()
                 << ", total_lost:" << model_.total_bytes_lost() << "  @ "
                 << sent_time;
+  if (avoid_unnecessary_probe_rtt_ && bytes_in_flight == 0) {
+    QUIC_RELOADABLE_FLAG_COUNT_N(quic_bbr2_avoid_unnecessary_probe_rtt, 1, 2);
+    OnExitQuiescence(sent_time);
+  }
   model_.OnPacketSent(sent_time, bytes_in_flight, packet_number, bytes,
                       is_retransmittable);
 }
@@ -332,6 +341,23 @@
   stats->num_ack_aggregation_epochs = model_.num_ack_aggregation_epochs();
 }
 
+void Bbr2Sender::OnEnterQuiescence(QuicTime now) {
+  last_quiescence_start_ = now;
+}
+
+void Bbr2Sender::OnExitQuiescence(QuicTime now) {
+  if (last_quiescence_start_ != QuicTime::Zero()) {
+    Bbr2Mode next_mode = BBR2_MODE_DISPATCH(
+        OnExitQuiescence(now, std::min(now, last_quiescence_start_)));
+    if (next_mode != mode_) {
+      BBR2_MODE_DISPATCH(Leave(now, nullptr));
+      mode_ = next_mode;
+      BBR2_MODE_DISPATCH(Enter(now, nullptr));
+    }
+    last_quiescence_start_ = QuicTime::Zero();
+  }
+}
+
 bool Bbr2Sender::ShouldSendProbingPacket() const {
   // TODO(wub): Implement ShouldSendProbingPacket properly.
   if (!BBR2_MODE_DISPATCH(IsProbingForBandwidth())) {
diff --git a/quic/core/congestion_control/bbr2_sender.h b/quic/core/congestion_control/bbr2_sender.h
index 64b6fcb..e63b2bd 100644
--- a/quic/core/congestion_control/bbr2_sender.h
+++ b/quic/core/congestion_control/bbr2_sender.h
@@ -19,6 +19,7 @@
 #include "net/third_party/quiche/src/quic/core/quic_bandwidth.h"
 #include "net/third_party/quiche/src/quic/core/quic_types.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
 
 namespace quic {
 
@@ -133,6 +134,8 @@
   void UpdatePacingRate(QuicByteCount bytes_acked);
   void UpdateCongestionWindow(QuicByteCount bytes_acked);
   QuicByteCount GetTargetCongestionWindow(float gain) const;
+  void OnEnterQuiescence(QuicTime now);
+  void OnExitQuiescence(QuicTime now);
 
   // Helper function for BBR2_MODE_DISPATCH.
   Bbr2ProbeRttMode& probe_rtt_or_die() {
@@ -176,6 +179,8 @@
   QuicByteCount cwnd_;
   QuicBandwidth pacing_rate_;
 
+  QuicTime last_quiescence_start_ = QuicTime::Zero();
+
   Bbr2StartupMode startup_;
   Bbr2DrainMode drain_;
   Bbr2ProbeBwMode probe_bw_;
@@ -188,6 +193,9 @@
   // Debug only.
   bool last_sample_is_app_limited_;
 
+  const bool avoid_unnecessary_probe_rtt_ =
+      GetQuicReloadableFlag(quic_bbr2_avoid_unnecessary_probe_rtt);
+
   friend class Bbr2StartupMode;
   friend class Bbr2DrainMode;
   friend class Bbr2ProbeBwMode;
diff --git a/quic/core/congestion_control/bbr2_simulator_test.cc b/quic/core/congestion_control/bbr2_simulator_test.cc
index 571ba57..e3af3dc 100644
--- a/quic/core/congestion_control/bbr2_simulator_test.cc
+++ b/quic/core/congestion_control/bbr2_simulator_test.cc
@@ -13,6 +13,7 @@
 #include "net/third_party/quiche/src/quic/core/quic_bandwidth.h"
 #include "net/third_party/quiche/src/quic/core/quic_packet_number.h"
 #include "net/third_party/quiche/src/quic/core/quic_types.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
 #include "net/third_party/quiche/src/quic/test_tools/quic_config_peer.h"
@@ -816,16 +817,95 @@
   EXPECT_LT(2 * kDefaultMaxPacketSize, inflight_hi);
 }
 
+// After quiescence, if the sender is in PROBE_RTT, it should transition to
+// PROBE_BW immediately on the first sent packet after quiescence.
+TEST_F(Bbr2DefaultTopologyTest, ProbeRttAfterQuiescenceImmediatelyExits) {
+  DefaultTopologyParams params;
+  CreateNetwork(params);
+
+  DriveOutOfStartup(params);
+
+  const QuicTime::Delta timeout = QuicTime::Delta::FromSeconds(15);
+  bool simulator_result;
+
+  // Keep sending until reach PROBE_RTT.
+  simulator_result = SendUntilOrTimeout(
+      [this]() {
+        return sender_->ExportDebugState().mode == Bbr2Mode::PROBE_RTT;
+      },
+      timeout);
+  ASSERT_TRUE(simulator_result);
+
+  // Wait for entering a quiescence of 5 seconds.
+  ASSERT_TRUE(simulator_.RunUntilOrTimeout(
+      [this]() {
+        return sender_unacked_map()->bytes_in_flight() == 0 &&
+               sender_->ExportDebugState().mode == Bbr2Mode::PROBE_RTT;
+      },
+      timeout));
+
+  simulator_.RunFor(QuicTime::Delta::FromSeconds(5));
+
+  // Send one packet to exit quiescence.
+  EXPECT_EQ(sender_->ExportDebugState().mode, Bbr2Mode::PROBE_RTT);
+  sender_->OnPacketSent(SimulatedNow(), /*bytes_in_flight=*/0,
+                        sender_unacked_map()->largest_sent_packet() + 1,
+                        kDefaultMaxPacketSize, HAS_RETRANSMITTABLE_DATA);
+  if (GetQuicReloadableFlag(quic_bbr2_avoid_unnecessary_probe_rtt)) {
+    EXPECT_EQ(sender_->ExportDebugState().mode, Bbr2Mode::PROBE_BW);
+  } else {
+    EXPECT_EQ(sender_->ExportDebugState().mode, Bbr2Mode::PROBE_RTT);
+  }
+}
+
+TEST_F(Bbr2DefaultTopologyTest, ProbeBwAfterQuiescencePostponeMinRttTimestamp) {
+  DefaultTopologyParams params;
+  CreateNetwork(params);
+
+  DriveOutOfStartup(params);
+
+  const QuicTime::Delta timeout = QuicTime::Delta::FromSeconds(5);
+  bool simulator_result;
+
+  // Keep sending until reach PROBE_REFILL.
+  simulator_result = SendUntilOrTimeout(
+      [this]() {
+        return sender_->ExportDebugState().probe_bw.phase ==
+               CyclePhase::PROBE_REFILL;
+      },
+      timeout);
+  ASSERT_TRUE(simulator_result);
+
+  const QuicTime min_rtt_timestamp_before_idle =
+      sender_->ExportDebugState().min_rtt_timestamp;
+
+  // Wait for entering a quiescence of 15 seconds.
+  ASSERT_TRUE(simulator_.RunUntilOrTimeout(
+      [this]() { return sender_unacked_map()->bytes_in_flight() == 0; },
+      params.RTT()));
+
+  simulator_.RunFor(QuicTime::Delta::FromSeconds(15));
+
+  // Send some data to exit quiescence.
+  SendBursts(params, 1, kDefaultTCPMSS, QuicTime::Delta::Zero());
+  const QuicTime min_rtt_timestamp_after_idle =
+      sender_->ExportDebugState().min_rtt_timestamp;
+  if (GetQuicReloadableFlag(quic_bbr2_avoid_unnecessary_probe_rtt)) {
+    EXPECT_LT(min_rtt_timestamp_before_idle + QuicTime::Delta::FromSeconds(14),
+              min_rtt_timestamp_after_idle);
+  } else {
+    EXPECT_EQ(min_rtt_timestamp_before_idle, min_rtt_timestamp_after_idle);
+  }
+}
+
 // All Bbr2MultiSenderTests uses the following network topology:
 //
 //   Sender 0  (A Bbr2Sender)
 //       |
 //       | <-- local_links[0]
 //       |
-//       |  Sender N (1 <= N < kNumLocalLinks) (May or may not be a Bbr2Sender)
-//       |      |
-//       |      | <-- local_links[N]
-//       |      |
+//       |  Sender N (1 <= N < kNumLocalLinks) (May or may not be a
+//       Bbr2Sender) |      | |      | <-- local_links[N] |      |
 //    Network switch
 //           *  <-- the bottleneck queue in the direction
 //           |          of the receiver
diff --git a/quic/core/congestion_control/bbr2_startup.h b/quic/core/congestion_control/bbr2_startup.h
index 103456b..e8f37ff 100644
--- a/quic/core/congestion_control/bbr2_startup.h
+++ b/quic/core/congestion_control/bbr2_startup.h
@@ -38,6 +38,11 @@
 
   bool IsProbingForBandwidth() const override { return true; }
 
+  Bbr2Mode OnExitQuiescence(QuicTime /*now*/,
+                            QuicTime /*quiescence_start_time*/) override {
+    return Bbr2Mode::STARTUP;
+  }
+
   bool FullBandwidthReached() const { return full_bandwidth_reached_; }
 
   struct QUIC_EXPORT_PRIVATE DebugState {