| // Copyright (c) 2024 The Chromium Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "quiche/quic/core/congestion_control/prague_sender.h" | |
| #include <algorithm> | |
| #include "quiche/quic/core/congestion_control/rtt_stats.h" | |
| #include "quiche/quic/core/congestion_control/tcp_cubic_sender_bytes.h" | |
| #include "quiche/quic/core/quic_clock.h" | |
| #include "quiche/quic/core/quic_connection_stats.h" | |
| #include "quiche/quic/core/quic_time.h" | |
| #include "quiche/quic/core/quic_types.h" | |
| namespace quic { | |
| PragueSender::PragueSender(const QuicClock* clock, const RttStats* rtt_stats, | |
| QuicPacketCount initial_tcp_congestion_window, | |
| QuicPacketCount max_congestion_window, | |
| QuicConnectionStats* stats) | |
| : TcpCubicSenderBytes(clock, rtt_stats, false, | |
| initial_tcp_congestion_window, max_congestion_window, | |
| stats), | |
| connection_start_time_(clock->Now()), | |
| last_alpha_update_(connection_start_time_) {} | |
| void PragueSender::OnCongestionEvent(bool rtt_updated, | |
| QuicByteCount prior_in_flight, | |
| QuicTime event_time, | |
| const AckedPacketVector& acked_packets, | |
| const LostPacketVector& lost_packets, | |
| QuicPacketCount num_ect, | |
| QuicPacketCount num_ce) { | |
| if (!ect1_enabled_) { | |
| TcpCubicSenderBytes::OnCongestionEvent(rtt_updated, prior_in_flight, | |
| event_time, acked_packets, | |
| lost_packets, num_ect, num_ce); | |
| return; | |
| } | |
| // Update Prague-specific variables. | |
| if (rtt_updated) { | |
| rtt_virt_ = std::max(rtt_stats()->smoothed_rtt(), kPragueRttVirtMin); | |
| } | |
| if (prague_alpha_.has_value()) { | |
| ect_count_ += num_ect; | |
| ce_count_ += num_ce; | |
| if (event_time - last_alpha_update_ > rtt_virt_) { | |
| // Update alpha once per virtual RTT. | |
| float frac = static_cast<float>(ce_count_) / | |
| static_cast<float>(ect_count_ + ce_count_); | |
| prague_alpha_ = | |
| (1 - kPragueEwmaGain) * *prague_alpha_ + kPragueEwmaGain * frac; | |
| last_alpha_update_ = event_time; | |
| ect_count_ = 0; | |
| ce_count_ = 0; | |
| } | |
| } else if (num_ce > 0) { | |
| last_alpha_update_ = event_time; | |
| prague_alpha_ = 1.0; | |
| ect_count_ = num_ect; | |
| ce_count_ = num_ce; | |
| } | |
| if (!lost_packets.empty() && last_congestion_response_time_.has_value() && | |
| (event_time - *last_congestion_response_time_ < rtt_virt_)) { | |
| // Give credit for recent ECN cwnd reductions if there is a packet loss. | |
| QuicByteCount previous_reduction = last_congestion_response_size_; | |
| last_congestion_response_time_.reset(); | |
| set_congestion_window(GetCongestionWindow() + previous_reduction); | |
| } | |
| // Due to shorter RTTs with L4S, and the longer virtual RTT, after 500 RTTs | |
| // congestion avoidance should grow slower than in Cubic. | |
| if (!reduce_rtt_dependence_) { | |
| reduce_rtt_dependence_ = | |
| !InSlowStart() && lost_packets.empty() && | |
| (event_time - connection_start_time_) > | |
| kRoundsBeforeReducedRttDependence * rtt_stats()->smoothed_rtt(); | |
| } | |
| float congestion_avoidance_deflator; | |
| if (reduce_rtt_dependence_) { | |
| congestion_avoidance_deflator = | |
| static_cast<float>(rtt_stats()->smoothed_rtt().ToMicroseconds()) / | |
| static_cast<float>(rtt_virt_.ToMicroseconds()); | |
| congestion_avoidance_deflator *= congestion_avoidance_deflator; | |
| } else { | |
| congestion_avoidance_deflator = 1.0f; | |
| } | |
| QuicByteCount original_cwnd = GetCongestionWindow(); | |
| if (num_ce == 0 || !lost_packets.empty()) { | |
| // Fast path. No ECN specific logic except updating stats, adjusting for | |
| // previous CE responses, and reduced RTT dependence. | |
| TcpCubicSenderBytes::OnCongestionEvent(rtt_updated, prior_in_flight, | |
| event_time, acked_packets, | |
| lost_packets, num_ect, num_ce); | |
| if (lost_packets.empty() && reduce_rtt_dependence_ && | |
| original_cwnd < GetCongestionWindow()) { | |
| QuicByteCount cwnd_increase = GetCongestionWindow() - original_cwnd; | |
| set_congestion_window(original_cwnd + | |
| cwnd_increase * congestion_avoidance_deflator); | |
| } | |
| return; | |
| } | |
| // num_ce > 0 and lost_packets is empty. | |
| if (InSlowStart()) { | |
| ExitSlowstart(); | |
| } | |
| // Estimate bytes that were CE marked | |
| QuicByteCount bytes_acked = 0; | |
| for (auto packet : acked_packets) { | |
| bytes_acked += packet.bytes_acked; | |
| } | |
| float ce_fraction = | |
| static_cast<float>(num_ce) / static_cast<float>(num_ect + num_ce); | |
| QuicByteCount bytes_ce = bytes_acked * ce_fraction; | |
| QuicPacketCount ce_packets_remaining = num_ce; | |
| bytes_acked -= bytes_ce; | |
| if (!last_congestion_response_time_.has_value() || | |
| event_time - *last_congestion_response_time_ > rtt_virt_) { | |
| last_congestion_response_time_ = event_time; | |
| // Create a synthetic loss to trigger a loss response. The packet number | |
| // needs to be large enough to not be before the last loss response, which | |
| // should be easy since acked packet numbers should be higher than lost | |
| // packet numbers, due to the delay in detecting loss. | |
| while (ce_packets_remaining > 0) { | |
| OnPacketLost(acked_packets.back().packet_number, bytes_ce, | |
| prior_in_flight); | |
| bytes_ce = 0; | |
| ce_packets_remaining--; | |
| } | |
| QuicByteCount cwnd_reduction = original_cwnd - GetCongestionWindow(); | |
| last_congestion_response_size_ = cwnd_reduction * *prague_alpha_; | |
| set_congestion_window(original_cwnd - last_congestion_response_size_); | |
| set_slowstart_threshold(GetCongestionWindow()); | |
| ExitRecovery(); | |
| } | |
| if (num_ect == 0) { | |
| return; | |
| } | |
| for (const AckedPacket& acked : acked_packets) { | |
| // Timing matters so report all of the packets faithfully, but reduce the | |
| // size to reflect that some bytes were marked CE. | |
| OnPacketAcked( | |
| acked.packet_number, | |
| acked.bytes_acked * (1 - ce_fraction) * congestion_avoidance_deflator, | |
| prior_in_flight, event_time); | |
| } | |
| } | |
| CongestionControlType PragueSender::GetCongestionControlType() const { | |
| return kPragueCubic; | |
| } | |
| bool PragueSender::EnableECT1() { | |
| ect1_enabled_ = true; | |
| return true; | |
| } | |
| } // namespace quic |