ACK non-consecutive QUIC CE packets immediately. The arrival of a Congestion Experienced-marked packet currently has no effect on the ACK timeout. RFC 9000 recommends sending an ACK after every CE. draft-ietf-quic-ack-frequency-10 recommends sending after every non-consecutive CE, which is better advice. This CL implements the latter. Protected by FLAGS_quic_reloadable_flag_quic_ack_ce_immediately. PiperOrigin-RevId: 712679547
diff --git a/quiche/common/quiche_feature_flags_list.h b/quiche/common/quiche_feature_flags_list.h index 065699e..2b2bee8 100755 --- a/quiche/common/quiche_feature_flags_list.h +++ b/quiche/common/quiche_feature_flags_list.h
@@ -9,6 +9,7 @@ #if defined(QUICHE_FLAG) QUICHE_FLAG(bool, quiche_reloadable_flag_enable_h3_origin_frame, false, true, "If true, enables support for parsing HTTP/3 ORIGIN frames.") +QUICHE_FLAG(bool, quiche_reloadable_flag_quic_ack_ce_immediately, false, false, "When true, acks a CE-marked packet immediately, unless the previous packet was also CE-marked.") QUICHE_FLAG(bool, quiche_reloadable_flag_quic_act_upon_invalid_header, true, true, "If true, reject or send error response code upon receiving invalid request or response headers.") QUICHE_FLAG(bool, quiche_reloadable_flag_quic_add_stream_info_to_idle_close_detail, false, true, "If true, include stream information in idle timeout connection close detail.") QUICHE_FLAG(bool, quiche_reloadable_flag_quic_allow_client_enabled_bbr_v2, true, true, "If true, allow client to enable BBRv2 on server via connection option 'B2ON'.")
diff --git a/quiche/quic/core/quic_received_packet_manager.cc b/quiche/quic/core/quic_received_packet_manager.cc index 8d56d51..fa0f64b 100644 --- a/quiche/quic/core/quic_received_packet_manager.cc +++ b/quiche/quic/core/quic_received_packet_manager.cc
@@ -124,6 +124,13 @@ } } + if (GetQuicReloadableFlag(quic_ack_ce_immediately)) { + if (ecn == ECN_CE && !last_packet_was_ce_marked_) { + QUIC_RELOADABLE_FLAG_COUNT_N(quic_ack_ce_immediately, 1, 2); + changed_to_ce_marked_ = true; + } + last_packet_was_ce_marked_ = ecn == ECN_CE; + } if (ecn != ECN_NOT_ECT) { if (!ack_frame_.ecn_counters.has_value()) { ack_frame_.ecn_counters = QuicEcnCounts(); @@ -291,6 +298,15 @@ return; } + if (changed_to_ce_marked_) { + // changed_to_ce_marked_ is always false if quic_ack_ce_immediately is + // false, so there's no need to check the feature flag here. + ack_timeout_ = now; + changed_to_ce_marked_ = false; + QUIC_RELOADABLE_FLAG_COUNT_N(quic_ack_ce_immediately, 2, 2); + return; + } + if (!should_last_packet_instigate_acks) { return; }
diff --git a/quiche/quic/core/quic_received_packet_manager.h b/quiche/quic/core/quic_received_packet_manager.h index 1d24651..c08bb70 100644 --- a/quiche/quic/core/quic_received_packet_manager.h +++ b/quiche/quic/core/quic_received_packet_manager.h
@@ -206,6 +206,12 @@ // Whether the most recent packet was missing before it was received. bool was_last_packet_missing_; + // Was the previous received packet CE-marked? + bool last_packet_was_ce_marked_ = false; + // The current packet is CE-marked, and the previous packet was not. This + // condition should trigger an immediate ACK. + bool changed_to_ce_marked_ = false; + // Last sent largest acked, which gets updated when ACK was successfully sent. QuicPacketNumber last_sent_largest_acked_;
diff --git a/quiche/quic/core/quic_received_packet_manager_test.cc b/quiche/quic/core/quic_received_packet_manager_test.cc index ffc8862..da189ac 100644 --- a/quiche/quic/core/quic_received_packet_manager_test.cc +++ b/quiche/quic/core/quic_received_packet_manager_test.cc
@@ -6,13 +6,12 @@ #include <algorithm> #include <cstddef> -#include <ostream> -#include <vector> #include "quiche/quic/core/congestion_control/rtt_stats.h" #include "quiche/quic/core/crypto/crypto_protocol.h" #include "quiche/quic/core/quic_connection_stats.h" #include "quiche/quic/core/quic_constants.h" +#include "quiche/quic/core/quic_time.h" #include "quiche/quic/core/quic_types.h" #include "quiche/quic/platform/api/quic_expect_bug.h" #include "quiche/quic/platform/api/quic_flags.h" @@ -709,6 +708,37 @@ EXPECT_EQ(ack.ack_frame->ecn_counters->ce, 1); } +TEST_F(QuicReceivedPacketManagerTest, NewCeTriggersImmediateAck) { + SetQuicReloadableFlag(quic_ack_ce_immediately, true); + EXPECT_FALSE(HasPendingAck()); + RecordPacketReceipt(3, QuicTime::Zero(), ECN_ECT1); + MaybeUpdateAckTimeout(kInstigateAck, 3); + EXPECT_TRUE(HasPendingAck()); + EXPECT_GT(received_manager_.ack_timeout(), clock_.ApproximateNow()); + // Ack is triggered by kDefaultRetransmittablePacketsBeforeAck. + RecordPacketReceipt(4, QuicTime::Zero(), ECN_ECT1); + MaybeUpdateAckTimeout(kInstigateAck, 4); + CheckAckTimeout(clock_.ApproximateNow()); + // New CE triggers immediate ACK. + RecordPacketReceipt(5, QuicTime::Zero(), ECN_CE); + MaybeUpdateAckTimeout(kInstigateAck, 5); + CheckAckTimeout(clock_.ApproximateNow()); + // Do not ack consecutive CE. + RecordPacketReceipt(6, QuicTime::Zero(), ECN_CE); + MaybeUpdateAckTimeout(kInstigateAck, 6); + EXPECT_TRUE(HasPendingAck()); + CheckAckTimeout(clock_.ApproximateNow() + kDelayedAckTime); + // Non-CE packet is second, triggers ACK. + RecordPacketReceipt(7, QuicTime::Zero(), ECN_ECT1); + MaybeUpdateAckTimeout(kInstigateAck, 7); + CheckAckTimeout(clock_.ApproximateNow()); + // New non-consecutive CE triggers immediate ACK. + RecordPacketReceipt(8, QuicTime::Zero(), ECN_CE); + MaybeUpdateAckTimeout(kInstigateAck, 8); + CheckAckTimeout(clock_.ApproximateNow()); +} + } // namespace + } // namespace test } // namespace quic