Update BBR3 to more closely align with the BBRv3 draft. BBR3 now uses a PROBE_UP cwnd gain of 2.25 and the Drain state is limited to 3 round trips. Removes the B201 connection option from tests, because it's been deprecated. PiperOrigin-RevId: 909174463
diff --git a/quiche/quic/core/congestion_control/bbr2_misc.h b/quiche/quic/core/congestion_control/bbr2_misc.h index 83ee77b..e9ee27b 100644 --- a/quiche/quic/core/congestion_control/bbr2_misc.h +++ b/quiche/quic/core/congestion_control/bbr2_misc.h
@@ -154,6 +154,7 @@ float probe_bw_default_pacing_gain = 1.0; float probe_bw_cwnd_gain = kDerivedDefaultCwndGain; + float probe_up_cwnd_gain = 2.25; // For BBR3. /* * PROBE_UP parameters.
diff --git a/quiche/quic/core/congestion_control/bbr3_sender.cc b/quiche/quic/core/congestion_control/bbr3_sender.cc index 7067b28..1f248f6 100644 --- a/quiche/quic/core/congestion_control/bbr3_sender.cc +++ b/quiche/quic/core/congestion_control/bbr3_sender.cc
@@ -59,6 +59,8 @@ } // The BBR IETF draft uses 0.5 as the drain pacing gain. params_.drain_pacing_gain = 0.5f; + // The BBR IETF draft uses 0.9 as the PROBE_DOWN pacing gain. + params_.probe_bw_probe_down_pacing_gain = 0.9f; // STARTUP has a shorter MaxAckHeightTracker window of 1 round. params_.startup_include_extra_acked = true; model_.SetMaxAckHeightTrackerWindowLength(1); @@ -520,6 +522,7 @@ model_.clear_bandwidth_lo(); // Increase the max_ack_height_tracker window when exiting STARTUP from 1. model_.SetMaxAckHeightTrackerWindowLength(max_ack_height_window_length_); + drain_rounds_ = 0; } void Bbr3Sender::OnExitQuiescence(QuicTime now) { @@ -689,12 +692,17 @@ QUICHE_DCHECK_EQ(model_.cwnd_gain(), params_.drain_cwnd_gain); model_.set_cwnd_gain(params_.drain_cwnd_gain); + if (congestion_event.end_of_round_trip) { + ++drain_rounds_; + } + QuicByteCount drain_target = DrainTarget(); - if (congestion_event.bytes_in_flight <= drain_target) { + if (congestion_event.bytes_in_flight <= drain_target || drain_rounds_ > 3) { QUIC_DVLOG(3) << this << " Exiting DRAIN. bytes_in_flight:" << congestion_event.bytes_in_flight << ", bdp:" << model_.BDP() - << ", drain_target:" << drain_target << " @ " + << ", drain_target:" << drain_target + << ", drain_rounds:" << drain_rounds_ << " @ " << congestion_event.event_time; return Bbr2Mode::PROBE_BW; } @@ -744,12 +752,17 @@ // Do not need to set the gains if switching to PROBE_RTT, they will be set // when Bbr2ProbeRttMode::Enter is called. - if (!switch_to_probe_rtt) { - model_.set_pacing_gain(PacingGainForPhase(probe_bw_.phase)); + if (switch_to_probe_rtt) { + return Bbr2Mode::PROBE_RTT; + } + model_.set_pacing_gain(PacingGainForPhase(probe_bw_.phase)); + if (probe_bw_.phase == ProbePhase::PROBE_UP) { + model_.set_cwnd_gain(params_.probe_up_cwnd_gain); + } else { model_.set_cwnd_gain(params_.probe_bw_cwnd_gain); } - return switch_to_probe_rtt ? Bbr2Mode::PROBE_RTT : Bbr2Mode::PROBE_BW; + return Bbr2Mode::PROBE_BW; } void Bbr3Sender::UpdateProbeDown(QuicByteCount prior_in_flight,
diff --git a/quiche/quic/core/congestion_control/bbr3_sender.h b/quiche/quic/core/congestion_control/bbr3_sender.h index 88b0855..a3aea6a 100644 --- a/quiche/quic/core/congestion_control/bbr3_sender.h +++ b/quiche/quic/core/congestion_control/bbr3_sender.h
@@ -214,6 +214,9 @@ // Startup state. QuicBandwidth startup_max_bw_at_round_beginning_ = QuicBandwidth::Zero(); + // Drain state. + uint64_t drain_rounds_ = 0; + // Probe BW state. struct ProbeBWState { QuicTime cycle_start_time = QuicTime::Zero();
diff --git a/quiche/quic/core/congestion_control/bbr3_simulator_test.cc b/quiche/quic/core/congestion_control/bbr3_simulator_test.cc index d220de5..9818b12 100644 --- a/quiche/quic/core/congestion_control/bbr3_simulator_test.cc +++ b/quiche/quic/core/congestion_control/bbr3_simulator_test.cc
@@ -1350,14 +1350,13 @@ // Get the queue at the bottleneck, which is the outgoing queue at the port to // which the receiver is connected. const simulator::Queue* queue = switch_->port_queue(2); - bool simulator_result; // We have no intention of ever finishing this transfer. sender_endpoint_.AddBytesToTransfer(100 * 1024 * 1024); // Run the startup, and verify that it fills up the queue. ASSERT_EQ(Bbr2Mode::STARTUP, sender_->ExportDebugState().mode); - simulator_result = simulator_.RunUntilOrTimeout( + bool simulator_result = simulator_.RunUntilOrTimeout( [this]() { return sender_->ExportDebugState().mode != Bbr2Mode::STARTUP; }, @@ -1405,6 +1404,62 @@ EXPECT_APPROX_EQ(params.RTT(), rtt_stats()->smoothed_rtt(), 0.1f); } +// Verify the flow exits DRAIN after 3 rounds no matter what. +TEST_F(Bbr3DefaultTopologyTest, DrainExitDueTo3RoundLimit) { + DefaultTopologyParams params; + CreateNetwork(params); + + const QuicTime::Delta timeout = QuicTime::Delta::FromSeconds(10); + // Get the queue at the bottleneck, which is the outgoing queue at the port to + // which the receiver is connected. + const simulator::Queue* queue = switch_->port_queue(2); + + // We have no intention of ever finishing this transfer. + sender_endpoint_.AddBytesToTransfer(100 * 1024 * 1024); + + // Run the startup, and verify that it fills up the queue. + ASSERT_EQ(Bbr2Mode::STARTUP, sender_->ExportDebugState().mode); + bool simulator_result = simulator_.RunUntilOrTimeout( + [this]() { + return sender_->ExportDebugState().mode != Bbr2Mode::STARTUP; + }, + timeout); + ASSERT_TRUE(simulator_result); + ASSERT_EQ(Bbr2Mode::DRAIN, sender_->ExportDebugState().mode); + EXPECT_APPROX_EQ(sender_->BandwidthEstimate() * 0.5f, sender_->PacingRate(0), + 0.01f); + + // BBR uses CWND gain of 2 during STARTUP, hence it will fill the buffer with + // approximately 1 BDP. Here, we use 0.95 to give some margin for error. + EXPECT_GE(queue->bytes_queued(), 0.95 * params.BDP()); + + // Observe increased RTT due to bufferbloat. + const QuicTime::Delta queueing_delay = + params.test_link.bandwidth.TransferTime(queue->bytes_queued()); + EXPECT_APPROX_EQ(params.RTT() + queueing_delay, rtt_stats()->latest_rtt(), + 0.1f); + + // Transition to the drain phase and verify that it makes the queue + // have at most a BDP worth of packets. + const QuicRoundTripCount round_count_at_drain_entry = + sender_->ExportDebugState().round_trip_count; + // Half the bottleneck bandwidth so we never drain the queue. + params.test_link.bandwidth = params.BottleneckBandwidth() * 0.5f; + TestLink()->set_bandwidth(params.test_link.bandwidth); + + simulator_result = simulator_.RunUntilOrTimeout( + [this]() { return sender_->ExportDebugState().mode != Bbr2Mode::DRAIN; }, + timeout); + ASSERT_TRUE(simulator_result); + ASSERT_EQ(Bbr2Mode::PROBE_BW, sender_->ExportDebugState().mode); + // Verify we are exiting DRAIN with bytes_in_flight > DrainTarget(), which + // means we exited due to drain_rounds > 3. + EXPECT_GT(sender_unacked_map()->bytes_in_flight(), + sender_->ExportDebugState().drain.drain_target); + EXPECT_EQ(sender_->ExportDebugState().round_trip_count, + round_count_at_drain_entry + 3); +} + // Ensure that a connection that is app-limited and is at sufficiently low // bandwidth will not exit high gain phase, and similarly ensure that the // connection will exit low gain early if the number of bytes in flight is low.