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 {