Support reordering_threshold > 1.
Protected by FLAGS_quic_reloadable_flag_quic_receive_ack_frequency.
PiperOrigin-RevId: 777678089
diff --git a/quiche/quic/core/frames/quic_ack_frame.cc b/quiche/quic/core/frames/quic_ack_frame.cc
index b2f7a58..b274672 100644
--- a/quiche/quic/core/frames/quic_ack_frame.cc
+++ b/quiche/quic/core/frames/quic_ack_frame.cc
@@ -7,10 +7,9 @@
#include <ostream>
#include <utility>
-#include "quiche/quic/core/quic_constants.h"
#include "quiche/quic/core/quic_interval.h"
+#include "quiche/quic/core/quic_packet_number.h"
#include "quiche/quic/platform/api/quic_bug_tracker.h"
-#include "quiche/quic/platform/api/quic_flag_utils.h"
namespace quic {
@@ -150,6 +149,11 @@
return packet_number_intervals_.rend();
}
+PacketNumberQueue::const_iterator PacketNumberQueue::LowerBound(
+ QuicPacketNumber packet_number) const {
+ return packet_number_intervals_.LowerBound(packet_number);
+}
+
QuicPacketCount PacketNumberQueue::LastIntervalLength() const {
QUICHE_DCHECK(!Empty());
return packet_number_intervals_.rbegin()->Length();
diff --git a/quiche/quic/core/frames/quic_ack_frame.h b/quiche/quic/core/frames/quic_ack_frame.h
index bafc755..d4ccb66 100644
--- a/quiche/quic/core/frames/quic_ack_frame.h
+++ b/quiche/quic/core/frames/quic_ack_frame.h
@@ -7,11 +7,10 @@
#include <ostream>
-#include "quiche/quic/core/quic_interval.h"
#include "quiche/quic/core/quic_interval_set.h"
+#include "quiche/quic/core/quic_packet_number.h"
#include "quiche/quic/core/quic_types.h"
-#include "quiche/quic/platform/api/quic_export.h"
-#include "quiche/quic/platform/api/quic_flags.h"
+#include "quiche/common/platform/api/quiche_export.h"
namespace quic {
@@ -80,6 +79,9 @@
const_iterator end() const;
const_reverse_iterator rbegin() const;
const_reverse_iterator rend() const;
+ // Returns the iterator to the first interval that contains or is greater than
+ // |packet_number|.
+ const_iterator LowerBound(QuicPacketNumber packet_number) const;
friend QUICHE_EXPORT std::ostream& operator<<(std::ostream& os,
const PacketNumberQueue& q);
diff --git a/quiche/quic/core/quic_received_packet_manager.cc b/quiche/quic/core/quic_received_packet_manager.cc
index 5e18cab..6bca5a9 100644
--- a/quiche/quic/core/quic_received_packet_manager.cc
+++ b/quiche/quic/core/quic_received_packet_manager.cc
@@ -6,12 +6,15 @@
#include <algorithm>
#include <limits>
+#include <optional>
#include <utility>
#include "quiche/quic/core/congestion_control/rtt_stats.h"
#include "quiche/quic/core/crypto/crypto_protocol.h"
#include "quiche/quic/core/quic_config.h"
#include "quiche/quic/core/quic_connection_stats.h"
+#include "quiche/quic/core/quic_constants.h"
+#include "quiche/quic/core/quic_packet_number.h"
#include "quiche/quic/core/quic_time.h"
#include "quiche/quic/core/quic_types.h"
#include "quiche/quic/platform/api/quic_bug_tracker.h"
@@ -292,9 +295,11 @@
return;
}
- // TODO(martinduke): If a missing packet is received, do we send it when
- // reordering_threshold_ == 0?
- if (was_last_packet_missing_ && last_sent_largest_acked_.IsInitialized() &&
+ // Limiting this to reordering_threshold_ > 0 is not compliant with
+ // draft-ietf-quic-ack-frequency-11, but there is an issue to add this
+ // behavior.
+ if (reordering_threshold_ > 0 && was_last_packet_missing_ &&
+ last_sent_largest_acked_.IsInitialized() &&
last_received_packet_number < last_sent_largest_acked_) {
// Ack immediately if an ACK frame was sent with a larger largest acked than
// the newly received packet number.
@@ -321,9 +326,25 @@
return;
}
- if (reordering_threshold_ == 1 && HasNewMissingPackets()) { // Fast path.
- ack_timeout_ = now;
- return;
+ if (reordering_threshold_ == 1) {
+ // Default behavior, not updated by ACK_FREQUENCY.
+ if (HasNewMissingPackets()) {
+ ack_timeout_ = now;
+ return;
+ }
+ } else {
+ if (ack_frame_.packets.NumIntervals() == max_ack_ranges_ &&
+ (!last_sent_largest_acked_.IsInitialized() ||
+ last_sent_largest_acked_ < ack_frame_.packets.begin()->max() - 1)) {
+ // If the lowest ACK range has not yet been reported, and might be trimmed
+ // on the next packet arrival, send an ACK.
+ ack_timeout_ = now;
+ return;
+ }
+ if (ReorderingExceedsThreshold()) {
+ ack_timeout_ = now;
+ return;
+ }
}
const QuicTime updated_ack_time = std::max(
@@ -360,6 +381,54 @@
ack_frame_.packets.LastIntervalLength() <= kMaxPacketsAfterNewMissing;
}
+bool QuicReceivedPacketManager::ReorderingExceedsThreshold() const {
+ if (reordering_threshold_ <= 1) { // flag is not enabled, or there is no
+ // threshold.
+ return false;
+ }
+ if (!HasMissingPackets() ||
+ GetLargestObserved() < QuicPacketNumber(reordering_threshold_)) {
+ return false;
+ }
+ // Find the next missing packet.
+ QuicPacketNumber smallest_unreported_missing;
+ if (last_sent_largest_acked_.IsInitialized() &&
+ last_sent_largest_acked_ >= QuicPacketNumber(reordering_threshold_)) {
+ smallest_unreported_missing =
+ last_sent_largest_acked_ - reordering_threshold_ + 1;
+ }
+ // All ACK ranges before peer_least_packet_awaiting_ack_ have already been
+ // deleted, so this is the lowest packet number that has receive state.
+ if (peer_least_packet_awaiting_ack_.IsInitialized() &&
+ (!smallest_unreported_missing.IsInitialized() ||
+ smallest_unreported_missing < peer_least_packet_awaiting_ack_)) {
+ smallest_unreported_missing = peer_least_packet_awaiting_ack_;
+ if (!least_unacked_plus_1_) {
+ ++smallest_unreported_missing;
+ }
+ }
+ if (smallest_unreported_missing.IsInitialized()) {
+ auto it = ack_frame_.packets.LowerBound(smallest_unreported_missing);
+ if (it == ack_frame_.packets.end()) {
+ QUIC_BUG(quic_bug_764939479)
+ << "Checking reordering with improper ACK state";
+ return false;
+ }
+ if (it->Contains(smallest_unreported_missing)) {
+ smallest_unreported_missing = it->max();
+ }
+ } else {
+ // No ACK has been sent. Since HasMissingPackets() is true, there must be at
+ // least two ranges. Per RFC9000, ignore the range from zero to the first
+ // observed packet. Smallest_unreported_missing is therefore the max of the
+ // first range.
+ QUICHE_DCHECK(ack_frame_.packets.NumIntervals() > 1);
+ smallest_unreported_missing = ack_frame_.packets.begin()->max();
+ }
+ return smallest_unreported_missing <=
+ (GetLargestObserved() - reordering_threshold_);
+}
+
bool QuicReceivedPacketManager::ack_frame_updated() const {
return ack_frame_updated_;
}
diff --git a/quiche/quic/core/quic_received_packet_manager.h b/quiche/quic/core/quic_received_packet_manager.h
index 404d46d..221b16a 100644
--- a/quiche/quic/core/quic_received_packet_manager.h
+++ b/quiche/quic/core/quic_received_packet_manager.h
@@ -11,6 +11,7 @@
#include "quiche/quic/core/frames/quic_ack_frequency_frame.h"
#include "quiche/quic/core/quic_config.h"
#include "quiche/quic/core/quic_constants.h"
+#include "quiche/quic/core/quic_packet_number.h"
#include "quiche/quic/core/quic_packets.h"
#include "quiche/quic/core/quic_time.h"
#include "quiche/quic/core/quic_types.h"
@@ -86,6 +87,13 @@
// packets of the largest observed.
virtual bool HasNewMissingPackets() const;
+ // Returns true if the lowest packet number beyond largest_acked_ is more
+ // than reordering_threshold_ behind largest_unacked. Used only when
+ // reordering_threshold_ > 1.
+ // TODO(martinduke): This code can be used for reordering_threshold_ == 1
+ // as well, once we have full confidence in it.
+ bool ReorderingExceedsThreshold() const;
+
virtual bool ack_frame_updated() const;
QuicPacketNumber GetLargestObserved() const;
@@ -225,6 +233,9 @@
// should set the ack timeout to now.
bool ack_now_ = false;
+ // Latch for the flag.
+ bool least_unacked_plus_1_ = GetQuicReloadableFlag(quic_least_unacked_plus_1);
+
// 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 97f00c5..352614b 100644
--- a/quiche/quic/core/quic_received_packet_manager_test.cc
+++ b/quiche/quic/core/quic_received_packet_manager_test.cc
@@ -11,6 +11,7 @@
#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_packet_number.h"
#include "quiche/quic/core/quic_time.h"
#include "quiche/quic/core/quic_types.h"
#include "quiche/quic/platform/api/quic_expect_bug.h"
@@ -578,18 +579,18 @@
RecordPacketReceipt(3, clock_.ApproximateNow());
MaybeUpdateAckTimeout(kInstigateAck, 3);
- // Ack, since missing packets are always acknowledged.
- CheckAckTimeout(clock_.ApproximateNow());
-
- RecordPacketReceipt(7, clock_.ApproximateNow());
- MaybeUpdateAckTimeout(kInstigateAck, 10);
- // No ack, as the frame says to ignore ordering.
+ // Don't ack as ignore_order is set by AckFrequencyFrame.
CheckAckTimeout(clock_.ApproximateNow() + kDelayedAckTime);
- RecordPacketReceipt(1, clock_.ApproximateNow());
- MaybeUpdateAckTimeout(kInstigateAck, 11);
- // It's the second packet in a row, ack it.
+ RecordPacketReceipt(2, clock_.ApproximateNow());
+ MaybeUpdateAckTimeout(kInstigateAck, 2);
+ // Immediate ack is sent as this is the 2nd packet of every two packets.
CheckAckTimeout(clock_.ApproximateNow());
+
+ RecordPacketReceipt(1, clock_.ApproximateNow());
+ MaybeUpdateAckTimeout(kInstigateAck, 1);
+ // Don't ack as ignore_order is set by AckFrequencyFrame.
+ CheckAckTimeout(clock_.ApproximateNow() + kDelayedAckTime);
}
TEST_F(QuicReceivedPacketManagerTest,
@@ -627,6 +628,100 @@
}
TEST_F(QuicReceivedPacketManagerTest,
+ AckFrequencyFrameChangesReorderingThreshold) {
+ EXPECT_FALSE(HasPendingAck());
+ QuicConfig config;
+ received_manager_.SetFromConfig(config, Perspective::IS_CLIENT);
+
+ QuicAckFrequencyFrame frame;
+ frame.sequence_number = 0;
+ frame.requested_max_ack_delay = kDelayedAckTime;
+ frame.ack_eliciting_threshold = 10; // No non-reordering acks.
+ frame.reordering_threshold = 2;
+ received_manager_.OnAckFrequencyFrame(frame);
+ RecordPacketReceipt(0, clock_.ApproximateNow());
+ MaybeUpdateAckTimeout(kInstigateAck, 0);
+ CheckAckTimeout(clock_.ApproximateNow() + kDelayedAckTime);
+ // No ack after gap.
+ RecordPacketReceipt(2, clock_.ApproximateNow());
+ MaybeUpdateAckTimeout(kInstigateAck, 2);
+ CheckAckTimeout(clock_.ApproximateNow() + kDelayedAckTime);
+ // Hit the reordering threshold, send ACK.
+ RecordPacketReceipt(3, clock_.ApproximateNow());
+ MaybeUpdateAckTimeout(kInstigateAck, 3);
+ CheckAckTimeout(clock_.ApproximateNow());
+ // Already reported, do not ack again.
+ RecordPacketReceipt(4, clock_.ApproximateNow());
+ MaybeUpdateAckTimeout(kInstigateAck, 4);
+ CheckAckTimeout(clock_.ApproximateNow() + kDelayedAckTime);
+ // Filling the hole is acked.
+ RecordPacketReceipt(1, clock_.ApproximateNow());
+ MaybeUpdateAckTimeout(kInstigateAck, 1);
+ CheckAckTimeout(clock_.ApproximateNow());
+}
+
+// There is a different code path if there is no ACK range before the missing
+// packet.
+TEST_F(QuicReceivedPacketManagerTest, ReorderingThresholdNoRangeBeforeMissing) {
+ EXPECT_FALSE(HasPendingAck());
+ QuicConfig config;
+ received_manager_.SetFromConfig(config, Perspective::IS_CLIENT);
+
+ QuicAckFrequencyFrame frame;
+ frame.sequence_number = 0;
+ frame.requested_max_ack_delay = kDelayedAckTime;
+ frame.ack_eliciting_threshold = 10; // No non-reordering acks.
+ frame.reordering_threshold = 2;
+ received_manager_.OnAckFrequencyFrame(frame);
+ if (GetQuicReloadableFlag(quic_least_unacked_plus_1)) {
+ received_manager_.DontWaitForPacketsBefore(QuicPacketNumber(2));
+ } else {
+ received_manager_.DontWaitForPacketsBefore(QuicPacketNumber(1));
+ }
+ // No ack after gap.
+ RecordPacketReceipt(3, clock_.ApproximateNow());
+ MaybeUpdateAckTimeout(kInstigateAck, 3);
+ CheckAckTimeout(clock_.ApproximateNow() + kDelayedAckTime);
+ // Hit the reordering threshold, send ACK.
+ RecordPacketReceipt(4, clock_.ApproximateNow());
+ MaybeUpdateAckTimeout(kInstigateAck, 4);
+ CheckAckTimeout(clock_.ApproximateNow());
+ // Second packet ignored.
+ RecordPacketReceipt(5, clock_.ApproximateNow());
+ MaybeUpdateAckTimeout(kInstigateAck, 5);
+ CheckAckTimeout(clock_.ApproximateNow() + kDelayedAckTime);
+ // Filling the hole is acked.
+ RecordPacketReceipt(2, clock_.ApproximateNow());
+ MaybeUpdateAckTimeout(kInstigateAck, 2);
+ CheckAckTimeout(clock_.ApproximateNow());
+}
+
+TEST_F(QuicReceivedPacketManagerTest, MaxAckRangesPromptsAck) {
+ EXPECT_FALSE(HasPendingAck());
+ QuicConfig config;
+ received_manager_.SetFromConfig(config, Perspective::IS_CLIENT);
+
+ QuicAckFrequencyFrame frame;
+ frame.sequence_number = 0;
+ frame.requested_max_ack_delay = kDelayedAckTime;
+ frame.ack_eliciting_threshold = 1000;
+ frame.reordering_threshold = 1000;
+ received_manager_.OnAckFrequencyFrame(frame);
+ const int max_ack_ranges = 10;
+ received_manager_.set_max_ack_ranges(max_ack_ranges);
+ // create ack ranges
+ for (int i = 0; i < (max_ack_ranges - 1); ++i) {
+ RecordPacketReceipt(2 * i, clock_.ApproximateNow());
+ MaybeUpdateAckTimeout(kInstigateAck, 2 * i);
+ CheckAckTimeout(clock_.ApproximateNow() + kDelayedAckTime);
+ }
+ // Send the max, forcing an ack.
+ RecordPacketReceipt(max_ack_ranges * 2, clock_.ApproximateNow());
+ MaybeUpdateAckTimeout(kInstigateAck, 2 * max_ack_ranges - 1);
+ CheckAckTimeout(clock_.ApproximateNow());
+}
+
+TEST_F(QuicReceivedPacketManagerTest,
AckDecimationDisabledWhenAckFrequencyFrameIsReceived) {
EXPECT_FALSE(HasPendingAck());