gfe-relnote: In QUIC, enable IETF style loss detection. Protected by
gfe2_reloadable_flag_quic_enable_ietf_loss_detection.
PiperOrigin-RevId: 268674408
Change-Id: Idb598126b44838732b3b3a3f679b3aff80bc5beb
diff --git a/quic/core/congestion_control/general_loss_algorithm.cc b/quic/core/congestion_control/general_loss_algorithm.cc
index 805ee83..3502031 100644
--- a/quic/core/congestion_control/general_loss_algorithm.cc
+++ b/quic/core/congestion_control/general_loss_algorithm.cc
@@ -19,10 +19,9 @@
// triggers when a nack has been receieved for the packet.
static const size_t kMinLossDelayMs = 5;
-// Default fraction of an RTT the algorithm waits before determining a packet is
-// lost due to early retransmission by time based loss detection.
-static const int kDefaultLossDelayShift = 2;
-// Default fraction of an RTT when doing adaptive loss detection.
+// Default fraction (1/8) of an RTT when doing IETF loss detection.
+static const int kDefaultIetfLossDelayShift = 3;
+// Default fraction (1/16) of an RTT when doing adaptive loss detection.
static const int kDefaultAdaptiveLossDelayShift = 4;
} // namespace
@@ -42,9 +41,13 @@
loss_detection_timeout_ = QuicTime::Zero();
largest_sent_on_spurious_retransmit_.Clear();
loss_type_ = loss_type;
- reordering_shift_ = loss_type == kAdaptiveTime
- ? kDefaultAdaptiveLossDelayShift
- : kDefaultLossDelayShift;
+ if (loss_type == kAdaptiveTime) {
+ reordering_shift_ = kDefaultAdaptiveLossDelayShift;
+ } else if (loss_type == kIetfLossDetection) {
+ reordering_shift_ = kDefaultIetfLossDelayShift;
+ } else {
+ reordering_shift_ = kDefaultLossDelayShift;
+ }
largest_previously_acked_.Clear();
}
@@ -83,9 +86,14 @@
}
QuicTime::Delta max_rtt =
std::max(rtt_stats.previous_srtt(), rtt_stats.latest_rtt());
- QuicTime::Delta loss_delay =
- std::max(QuicTime::Delta::FromMilliseconds(kMinLossDelayMs),
- max_rtt + (max_rtt >> reordering_shift_));
+ if (loss_type_ == kIetfLossDetection) {
+ max_rtt = std::max(QuicTime::Delta::FromMilliseconds(1), max_rtt);
+ }
+ QuicTime::Delta loss_delay = max_rtt + (max_rtt >> reordering_shift_);
+ if (loss_type_ != kIetfLossDetection) {
+ loss_delay = std::max(QuicTime::Delta::FromMilliseconds(kMinLossDelayMs),
+ loss_delay);
+ }
QuicPacketNumber packet_number = unacked_packets.GetLeastUnacked();
auto it = unacked_packets.begin();
if (least_in_flight_.IsInitialized() && least_in_flight_ >= packet_number) {
@@ -113,8 +121,8 @@
continue;
}
- if (loss_type_ == kNack) {
- // FACK based loss detection.
+ if (loss_type_ == kNack || loss_type_ == kIetfLossDetection) {
+ // Packet threshold loss detection.
if (largest_newly_acked - packet_number >= reordering_threshold_) {
packets_lost->push_back(LostPacket(packet_number, it->bytes_sent));
continue;
@@ -132,17 +140,14 @@
}
}
- // Only early retransmit(RFC5827) when the last packet gets acked and
- // there are retransmittable packets in flight.
- // This also implements a timer-protected variant of FACK.
- QuicPacketNumber largest_sent_retransmittable_packet;
- // Use largest_sent_retransmittable_packet of corresponding packet number
- // space for timer based loss detection.
- largest_sent_retransmittable_packet =
+ // Time threshold loss detection. Also implements early retransmit(RFC5827)
+ // when time threshold is not used and the last packet gets acked.
+ QuicPacketNumber largest_sent_retransmittable_packet =
unacked_packets.GetLargestSentRetransmittableOfPacketNumberSpace(
packet_number_space_);
if (largest_sent_retransmittable_packet <= largest_newly_acked ||
- loss_type_ == kTime || loss_type_ == kAdaptiveTime) {
+ loss_type_ == kTime || loss_type_ == kAdaptiveTime ||
+ loss_type_ == kIetfLossDetection) {
QuicTime when_lost = it->sent_time + loss_delay;
if (time < when_lost) {
loss_detection_timeout_ = when_lost;
@@ -157,8 +162,10 @@
}
// NACK-based loss detection allows for a max reordering window of 1 RTT.
- if (it->sent_time + rtt_stats.smoothed_rtt() <
- unacked_packets.GetTransmissionInfo(largest_newly_acked).sent_time) {
+ if (loss_type_ != kIetfLossDetection &&
+ it->sent_time + rtt_stats.smoothed_rtt() <
+ unacked_packets.GetTransmissionInfo(largest_newly_acked)
+ .sent_time) {
packets_lost->push_back(LostPacket(packet_number, it->bytes_sent));
continue;
}
diff --git a/quic/core/congestion_control/general_loss_algorithm.h b/quic/core/congestion_control/general_loss_algorithm.h
index 3bde91b..c9da837 100644
--- a/quic/core/congestion_control/general_loss_algorithm.h
+++ b/quic/core/congestion_control/general_loss_algorithm.h
@@ -65,6 +65,14 @@
int reordering_shift() const { return reordering_shift_; }
+ void set_reordering_shift(int reordering_shift) {
+ reordering_shift_ = reordering_shift;
+ }
+
+ bool use_adaptive_reordering_threshold() const {
+ return use_adaptive_reordering_threshold_;
+ }
+
void enable_adaptive_reordering_threshold() {
use_adaptive_reordering_threshold_ = true;
}
diff --git a/quic/core/congestion_control/general_loss_algorithm_test.cc b/quic/core/congestion_control/general_loss_algorithm_test.cc
index 8c200cc..dba6d5b 100644
--- a/quic/core/congestion_control/general_loss_algorithm_test.cc
+++ b/quic/core/congestion_control/general_loss_algorithm_test.cc
@@ -595,6 +595,71 @@
packets_acked.clear();
}
+TEST_F(GeneralLossAlgorithmTest, DefaultIetfLossDetection) {
+ loss_algorithm_.SetLossDetectionType(kIetfLossDetection);
+ for (size_t i = 1; i <= 6; ++i) {
+ SendDataPacket(i);
+ }
+ // Packet threshold loss detection.
+ AckedPacketVector packets_acked;
+ // No loss on one ack.
+ unacked_packets_.RemoveFromInFlight(QuicPacketNumber(2));
+ packets_acked.push_back(AckedPacket(
+ QuicPacketNumber(2), kMaxOutgoingPacketSize, QuicTime::Zero()));
+ VerifyLosses(2, packets_acked, std::vector<uint64_t>{});
+ packets_acked.clear();
+ // No loss on two acks.
+ unacked_packets_.RemoveFromInFlight(QuicPacketNumber(3));
+ packets_acked.push_back(AckedPacket(
+ QuicPacketNumber(3), kMaxOutgoingPacketSize, QuicTime::Zero()));
+ VerifyLosses(3, packets_acked, std::vector<uint64_t>{});
+ packets_acked.clear();
+ // Loss on three acks.
+ unacked_packets_.RemoveFromInFlight(QuicPacketNumber(4));
+ packets_acked.push_back(AckedPacket(
+ QuicPacketNumber(4), kMaxOutgoingPacketSize, QuicTime::Zero()));
+ VerifyLosses(4, packets_acked, {1});
+ EXPECT_EQ(QuicTime::Zero(), loss_algorithm_.GetLossTimeout());
+ packets_acked.clear();
+
+ SendDataPacket(7);
+
+ // Time threshold loss detection.
+ unacked_packets_.RemoveFromInFlight(QuicPacketNumber(6));
+ packets_acked.push_back(AckedPacket(
+ QuicPacketNumber(6), kMaxOutgoingPacketSize, QuicTime::Zero()));
+ VerifyLosses(6, packets_acked, std::vector<uint64_t>{});
+ packets_acked.clear();
+ EXPECT_EQ(clock_.Now() + rtt_stats_.smoothed_rtt() +
+ (rtt_stats_.smoothed_rtt() >> 3),
+ loss_algorithm_.GetLossTimeout());
+ clock_.AdvanceTime(rtt_stats_.smoothed_rtt() +
+ (rtt_stats_.smoothed_rtt() >> 3));
+ VerifyLosses(6, packets_acked, {5});
+ EXPECT_EQ(QuicTime::Zero(), loss_algorithm_.GetLossTimeout());
+}
+
+TEST_F(GeneralLossAlgorithmTest, IetfLossDetectionWithOneFourthRttDelay) {
+ loss_algorithm_.SetLossDetectionType(kIetfLossDetection);
+ loss_algorithm_.set_reordering_shift(2);
+ SendDataPacket(1);
+ SendDataPacket(2);
+
+ AckedPacketVector packets_acked;
+ unacked_packets_.RemoveFromInFlight(QuicPacketNumber(2));
+ packets_acked.push_back(AckedPacket(
+ QuicPacketNumber(2), kMaxOutgoingPacketSize, QuicTime::Zero()));
+ VerifyLosses(2, packets_acked, std::vector<uint64_t>{});
+ packets_acked.clear();
+ EXPECT_EQ(clock_.Now() + rtt_stats_.smoothed_rtt() +
+ (rtt_stats_.smoothed_rtt() >> 2),
+ loss_algorithm_.GetLossTimeout());
+ clock_.AdvanceTime(rtt_stats_.smoothed_rtt() +
+ (rtt_stats_.smoothed_rtt() >> 2));
+ VerifyLosses(2, packets_acked, {1});
+ EXPECT_EQ(QuicTime::Zero(), loss_algorithm_.GetLossTimeout());
+}
+
} // namespace
} // namespace test
} // namespace quic
diff --git a/quic/core/congestion_control/uber_loss_algorithm.cc b/quic/core/congestion_control/uber_loss_algorithm.cc
index e3b526a..3b669e2 100644
--- a/quic/core/congestion_control/uber_loss_algorithm.cc
+++ b/quic/core/congestion_control/uber_loss_algorithm.cc
@@ -92,4 +92,16 @@
packet_number, previous_largest_acked);
}
+void UberLossAlgorithm::SetReorderingShift(int reordering_shift) {
+ for (int8_t i = INITIAL_DATA; i < NUM_PACKET_NUMBER_SPACES; ++i) {
+ general_loss_algorithms_[i].set_reordering_shift(reordering_shift);
+ }
+}
+
+void UberLossAlgorithm::EnableAdaptiveReorderingThreshold() {
+ for (int8_t i = INITIAL_DATA; i < NUM_PACKET_NUMBER_SPACES; ++i) {
+ general_loss_algorithms_[i].enable_adaptive_reordering_threshold();
+ }
+}
+
} // namespace quic
diff --git a/quic/core/congestion_control/uber_loss_algorithm.h b/quic/core/congestion_control/uber_loss_algorithm.h
index 558aaea..0d2b788 100644
--- a/quic/core/congestion_control/uber_loss_algorithm.h
+++ b/quic/core/congestion_control/uber_loss_algorithm.h
@@ -9,6 +9,12 @@
namespace quic {
+namespace test {
+
+class QuicSentPacketManagerPeer;
+
+} // namespace test
+
// This class comprises multiple loss algorithms, each per packet number space.
class QUIC_EXPORT_PRIVATE UberLossAlgorithm : public LossDetectionInterface {
public:
@@ -49,7 +55,15 @@
QuicPacketNumber packet_number,
QuicPacketNumber previous_largest_acked) override;
+ // Sets reordering_shift for all packet number spaces.
+ void SetReorderingShift(int reordering_shift);
+
+ // Enable adaptive reordering threshold of all packet number spaces.
+ void EnableAdaptiveReorderingThreshold();
+
private:
+ friend class test::QuicSentPacketManagerPeer;
+
LossDetectionType loss_type_;
// One loss algorithm per packet number space.
GeneralLossAlgorithm general_loss_algorithms_[NUM_PACKET_NUMBER_SPACES];
diff --git a/quic/core/crypto/crypto_protocol.h b/quic/core/crypto/crypto_protocol.h
index 812aae0..9b3559d 100644
--- a/quic/core/crypto/crypto_protocol.h
+++ b/quic/core/crypto/crypto_protocol.h
@@ -163,6 +163,19 @@
const QuicTag kLFAK = TAG('L', 'F', 'A', 'K'); // Don't invoke FACK on the
// first ack.
const QuicTag kSTMP = TAG('S', 'T', 'M', 'P'); // Send and process timestamps
+
+const QuicTag kILD0 = TAG('I', 'L', 'D', '0'); // IETF style loss detection
+ // (default with 1/8 RTT time
+ // threshold)
+const QuicTag kILD1 = TAG('I', 'L', 'D', '1'); // IETF style loss detection
+ // with 1/4 RTT time threshold
+const QuicTag kILD2 = TAG('I', 'L', 'D', '2'); // IETF style loss detection
+ // with adaptive packet
+ // threshold
+const QuicTag kILD3 = TAG('I', 'L', 'D', '3'); // IETF style loss detection
+ // with 1/4 RTT time threshold
+ // and adaptive packet
+ // threshold
// TODO(fayang): Remove this connection option when QUIC_VERSION_35, is removed
// Since MAX_HEADER_LIST_SIZE settings frame is supported instead.
const QuicTag kSMHL = TAG('S', 'M', 'H', 'L'); // Support MAX_HEADER_LIST_SIZE
diff --git a/quic/core/http/end_to_end_test.cc b/quic/core/http/end_to_end_test.cc
index 439d04e..1033b26 100644
--- a/quic/core/http/end_to_end_test.cc
+++ b/quic/core/http/end_to_end_test.cc
@@ -368,6 +368,9 @@
if (GetQuicReloadableFlag(quic_enable_pto)) {
copt.push_back(k2PTO);
}
+ if (VersionHasIetfQuicFrames(negotiated_version_.transport_version)) {
+ copt.push_back(kILD0);
+ }
client_config_.SetConnectionOptionsToSend(copt);
// Start the server first, because CreateQuicClient() attempts
diff --git a/quic/core/quic_constants.h b/quic/core/quic_constants.h
index cdee0f9..3dc462c 100644
--- a/quic/core/quic_constants.h
+++ b/quic/core/quic_constants.h
@@ -241,6 +241,10 @@
// Default initial rtt used before any samples are received.
const int kInitialRttMs = 100;
+// Default fraction (1/4) of an RTT the algorithm waits before determining a
+// packet is lost due to early retransmission by time based loss detection.
+static const int kDefaultLossDelayShift = 2;
+
// Packet number of first sending packet of a connection. Please note, this
// cannot be used as first received packet because peer can choose its starting
// packet number.
diff --git a/quic/core/quic_sent_packet_manager.cc b/quic/core/quic_sent_packet_manager.cc
index 7a413d0..ee7bb34 100644
--- a/quic/core/quic_sent_packet_manager.cc
+++ b/quic/core/quic_sent_packet_manager.cc
@@ -249,6 +249,30 @@
if (config.HasClientRequestedIndependentOption(kLFAK, perspective)) {
uber_loss_algorithm_.SetLossDetectionType(kLazyFack);
}
+ if (GetQuicReloadableFlag(quic_enable_ietf_loss_detection)) {
+ if (config.HasClientRequestedIndependentOption(kILD0, perspective)) {
+ QUIC_RELOADABLE_FLAG_COUNT_N(quic_enable_ietf_loss_detection, 1, 4);
+ uber_loss_algorithm_.SetLossDetectionType(kIetfLossDetection);
+ }
+ if (config.HasClientRequestedIndependentOption(kILD1, perspective)) {
+ QUIC_RELOADABLE_FLAG_COUNT_N(quic_enable_ietf_loss_detection, 2, 4);
+ uber_loss_algorithm_.SetLossDetectionType(kIetfLossDetection);
+ uber_loss_algorithm_.SetReorderingShift(kDefaultLossDelayShift);
+ }
+ if (GetQuicReloadableFlag(quic_detect_spurious_loss)) {
+ if (config.HasClientRequestedIndependentOption(kILD2, perspective)) {
+ QUIC_RELOADABLE_FLAG_COUNT_N(quic_enable_ietf_loss_detection, 3, 4);
+ uber_loss_algorithm_.SetLossDetectionType(kIetfLossDetection);
+ uber_loss_algorithm_.EnableAdaptiveReorderingThreshold();
+ }
+ if (config.HasClientRequestedIndependentOption(kILD3, perspective)) {
+ QUIC_RELOADABLE_FLAG_COUNT_N(quic_enable_ietf_loss_detection, 4, 4);
+ uber_loss_algorithm_.SetLossDetectionType(kIetfLossDetection);
+ uber_loss_algorithm_.SetReorderingShift(kDefaultLossDelayShift);
+ uber_loss_algorithm_.EnableAdaptiveReorderingThreshold();
+ }
+ }
+ }
if (config.HasClientSentConnectionOption(kCONH, perspective)) {
conservative_handshake_retransmits_ = true;
}
diff --git a/quic/core/quic_sent_packet_manager_test.cc b/quic/core/quic_sent_packet_manager_test.cc
index b0806e5..e787715 100644
--- a/quic/core/quic_sent_packet_manager_test.cc
+++ b/quic/core/quic_sent_packet_manager_test.cc
@@ -2159,6 +2159,101 @@
->GetLossDetectionType());
}
+TEST_P(QuicSentPacketManagerTest, NegotiateIetfLossDetectionFromOptions) {
+ SetQuicReloadableFlag(quic_enable_ietf_loss_detection, true);
+ EXPECT_EQ(kNack, QuicSentPacketManagerPeer::GetLossAlgorithm(&manager_)
+ ->GetLossDetectionType());
+
+ QuicConfig config;
+ QuicTagVector options;
+ options.push_back(kILD0);
+ QuicConfigPeer::SetReceivedConnectionOptions(&config, options);
+ EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _));
+ EXPECT_CALL(*network_change_visitor_, OnCongestionChange());
+ manager_.SetFromConfig(config);
+
+ EXPECT_EQ(kIetfLossDetection,
+ QuicSentPacketManagerPeer::GetLossAlgorithm(&manager_)
+ ->GetLossDetectionType());
+ EXPECT_EQ(3, QuicSentPacketManagerPeer::GetReorderingShift(&manager_));
+ EXPECT_FALSE(
+ QuicSentPacketManagerPeer::AdaptiveReorderingThresholdEnabled(&manager_));
+}
+
+TEST_P(QuicSentPacketManagerTest,
+ NegotiateIetfLossDetectionOneFourthRttFromOptions) {
+ SetQuicReloadableFlag(quic_enable_ietf_loss_detection, true);
+ EXPECT_EQ(kNack, QuicSentPacketManagerPeer::GetLossAlgorithm(&manager_)
+ ->GetLossDetectionType());
+
+ QuicConfig config;
+ QuicTagVector options;
+ options.push_back(kILD1);
+ QuicConfigPeer::SetReceivedConnectionOptions(&config, options);
+ EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _));
+ EXPECT_CALL(*network_change_visitor_, OnCongestionChange());
+ manager_.SetFromConfig(config);
+
+ EXPECT_EQ(kIetfLossDetection,
+ QuicSentPacketManagerPeer::GetLossAlgorithm(&manager_)
+ ->GetLossDetectionType());
+ EXPECT_EQ(kDefaultLossDelayShift,
+ QuicSentPacketManagerPeer::GetReorderingShift(&manager_));
+ EXPECT_FALSE(
+ QuicSentPacketManagerPeer::AdaptiveReorderingThresholdEnabled(&manager_));
+}
+
+TEST_P(QuicSentPacketManagerTest,
+ NegotiateIetfLossDetectionAdaptiveReorderingThreshold) {
+ SetQuicReloadableFlag(quic_enable_ietf_loss_detection, true);
+ SetQuicReloadableFlag(quic_detect_spurious_loss, true);
+ EXPECT_EQ(kNack, QuicSentPacketManagerPeer::GetLossAlgorithm(&manager_)
+ ->GetLossDetectionType());
+ EXPECT_FALSE(
+ QuicSentPacketManagerPeer::AdaptiveReorderingThresholdEnabled(&manager_));
+
+ QuicConfig config;
+ QuicTagVector options;
+ options.push_back(kILD2);
+ QuicConfigPeer::SetReceivedConnectionOptions(&config, options);
+ EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _));
+ EXPECT_CALL(*network_change_visitor_, OnCongestionChange());
+ manager_.SetFromConfig(config);
+
+ EXPECT_EQ(kIetfLossDetection,
+ QuicSentPacketManagerPeer::GetLossAlgorithm(&manager_)
+ ->GetLossDetectionType());
+ EXPECT_EQ(3, QuicSentPacketManagerPeer::GetReorderingShift(&manager_));
+ EXPECT_TRUE(
+ QuicSentPacketManagerPeer::AdaptiveReorderingThresholdEnabled(&manager_));
+}
+
+TEST_P(QuicSentPacketManagerTest,
+ NegotiateIetfLossDetectionAdaptiveReorderingThreshold2) {
+ SetQuicReloadableFlag(quic_enable_ietf_loss_detection, true);
+ SetQuicReloadableFlag(quic_detect_spurious_loss, true);
+ EXPECT_EQ(kNack, QuicSentPacketManagerPeer::GetLossAlgorithm(&manager_)
+ ->GetLossDetectionType());
+ EXPECT_FALSE(
+ QuicSentPacketManagerPeer::AdaptiveReorderingThresholdEnabled(&manager_));
+
+ QuicConfig config;
+ QuicTagVector options;
+ options.push_back(kILD3);
+ QuicConfigPeer::SetReceivedConnectionOptions(&config, options);
+ EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _));
+ EXPECT_CALL(*network_change_visitor_, OnCongestionChange());
+ manager_.SetFromConfig(config);
+
+ EXPECT_EQ(kIetfLossDetection,
+ QuicSentPacketManagerPeer::GetLossAlgorithm(&manager_)
+ ->GetLossDetectionType());
+ EXPECT_EQ(kDefaultLossDelayShift,
+ QuicSentPacketManagerPeer::GetReorderingShift(&manager_));
+ EXPECT_TRUE(
+ QuicSentPacketManagerPeer::AdaptiveReorderingThresholdEnabled(&manager_));
+}
+
TEST_P(QuicSentPacketManagerTest, NegotiateCongestionControlFromOptions) {
QuicConfig config;
QuicTagVector options;
diff --git a/quic/core/quic_types.h b/quic/core/quic_types.h
index 814b63c..bd7ff55 100644
--- a/quic/core/quic_types.h
+++ b/quic/core/quic_types.h
@@ -383,10 +383,11 @@
};
enum LossDetectionType : uint8_t {
- kNack, // Used to mimic TCP's loss detection.
- kTime, // Time based loss detection.
- kAdaptiveTime, // Adaptive time based loss detection.
- kLazyFack, // Nack based but with FACK disabled for the first ack.
+ kNack, // Used to mimic TCP's loss detection.
+ kTime, // Time based loss detection.
+ kAdaptiveTime, // Adaptive time based loss detection.
+ kLazyFack, // Nack based but with FACK disabled for the first ack.
+ kIetfLossDetection, // IETF style loss detection.
};
// EncryptionLevel enumerates the stages of encryption that a QUIC connection