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
diff --git a/quic/test_tools/quic_sent_packet_manager_peer.cc b/quic/test_tools/quic_sent_packet_manager_peer.cc index c48c090..b680f66 100644 --- a/quic/test_tools/quic_sent_packet_manager_peer.cc +++ b/quic/test_tools/quic_sent_packet_manager_peer.cc
@@ -209,5 +209,19 @@ sent_packet_manager->pacing_sender_.ideal_next_packet_send_time_ = time; } +// static +int QuicSentPacketManagerPeer::GetReorderingShift( + QuicSentPacketManager* sent_packet_manager) { + return sent_packet_manager->uber_loss_algorithm_.general_loss_algorithms_[0] + .reordering_shift(); +} + +// static +bool QuicSentPacketManagerPeer::AdaptiveReorderingThresholdEnabled( + QuicSentPacketManager* sent_packet_manager) { + return sent_packet_manager->uber_loss_algorithm_.general_loss_algorithms_[0] + .use_adaptive_reordering_threshold(); +} + } // namespace test } // namespace quic
diff --git a/quic/test_tools/quic_sent_packet_manager_peer.h b/quic/test_tools/quic_sent_packet_manager_peer.h index 6dcce0c..6be8b46 100644 --- a/quic/test_tools/quic_sent_packet_manager_peer.h +++ b/quic/test_tools/quic_sent_packet_manager_peer.h
@@ -93,6 +93,11 @@ static void SetNextPacedPacketTime(QuicSentPacketManager* sent_packet_manager, QuicTime time); + + static int GetReorderingShift(QuicSentPacketManager* sent_packet_manager); + + static bool AdaptiveReorderingThresholdEnabled( + QuicSentPacketManager* sent_packet_manager); }; } // namespace test