gfe-relnote: In QUIC, add probe timeout mode, which unifies TLP and RTO. Protected by gfe2_reloadable_flag_quic_enable_pto. PTO mode is enabled on client side if gfe2_reloadable_flag_quic_enable_pto is true and client sends 1PTO or 2PTO. PTO mode is enabled on server side if gfe2_reloadable_flag_quic_enable_pto is true and server receives 1PTO or 2PTO from client. Connection is closed after 7 or 8 PTO depending on connection option 7PTO or 8PTO, respectively. PiperOrigin-RevId: 263574963 Change-Id: Id952a3e4640146c3fe72e3d6745cbac5ee16dcdc
diff --git a/quic/core/crypto/crypto_protocol.h b/quic/core/crypto/crypto_protocol.h index bbd0b18..812aae0 100644 --- a/quic/core/crypto/crypto_protocol.h +++ b/quic/core/crypto/crypto_protocol.h
@@ -170,6 +170,14 @@ const QuicTag kNSTP = TAG('N', 'S', 'T', 'P'); // No stop waiting frames. const QuicTag kNRTT = TAG('N', 'R', 'T', 'T'); // Ignore initial RTT +const QuicTag k1PTO = TAG('1', 'P', 'T', 'O'); // Send 1 packet upon PTO. +const QuicTag k2PTO = TAG('2', 'P', 'T', 'O'); // Send 2 packets upon PTO. + +const QuicTag k7PTO = TAG('7', 'P', 'T', 'O'); // Closes connection on 7 + // consecutive PTOs. +const QuicTag k8PTO = TAG('8', 'P', 'T', 'O'); // Closes connection on 8 + // consecutive PTOs. + // Optional support of truncated Connection IDs. If sent by a peer, the value // is the minimum number of bytes allowed for the connection ID sent to the // peer.
diff --git a/quic/core/http/end_to_end_test.cc b/quic/core/http/end_to_end_test.cc index d6c8486..4d7d1df 100644 --- a/quic/core/http/end_to_end_test.cc +++ b/quic/core/http/end_to_end_test.cc
@@ -362,7 +362,9 @@ copt.push_back(kTPCC); } copt.push_back(GetParam().priority_tag); - + if (GetQuicReloadableFlag(quic_enable_pto)) { + copt.push_back(k2PTO); + } client_config_.SetConnectionOptionsToSend(copt); // Start the server first, because CreateQuicClient() attempts
diff --git a/quic/core/quic_connection.cc b/quic/core/quic_connection.cc index 581709b..73ad66e 100644 --- a/quic/core/quic_connection.cc +++ b/quic/core/quic_connection.cc
@@ -321,7 +321,8 @@ processing_ack_frame_(false), supports_release_time_(false), release_time_into_future_(QuicTime::Delta::Zero()), - retry_has_been_parsed_(false) { + retry_has_been_parsed_(false), + max_consecutive_ptos_(0) { QUIC_DLOG(INFO) << ENDPOINT << "Created connection with server connection ID " << server_connection_id << " and version: " << ParsedQuicVersionToString(version()); @@ -419,6 +420,16 @@ if (config.HasClientSentConnectionOption(k5RTO, perspective_)) { close_connection_after_five_rtos_ = true; } + if (sent_packet_manager_.enable_pto()) { + if (config.HasClientSentConnectionOption(k7PTO, perspective_)) { + max_consecutive_ptos_ = 6; + QUIC_RELOADABLE_FLAG_COUNT_N(quic_enable_pto, 3, 4); + } + if (config.HasClientSentConnectionOption(k8PTO, perspective_)) { + max_consecutive_ptos_ = 7; + QUIC_RELOADABLE_FLAG_COUNT_N(quic_enable_pto, 4, 4); + } + } if (config.HasClientSentConnectionOption(kNSTP, perspective_)) { no_stop_waiting_frames_ = true; } @@ -2438,11 +2449,6 @@ DCHECK(!sent_packet_manager_.unacked_packets().empty()); const QuicPacketNumber previous_created_packet_number = packet_generator_.packet_number(); - const size_t previous_crypto_retransmit_count = - stats_.crypto_retransmit_count; - const size_t previous_loss_timeout_count = stats_.loss_timeout_count; - const size_t previous_tlp_count = stats_.tlp_count; - const size_t pervious_rto_count = stats_.rto_count; if (close_connection_after_five_rtos_ && sent_packet_manager_.GetConsecutiveRtoCount() >= 4) { // Close on the 5th consecutive RTO, so after 4 previous RTOs have occurred. @@ -2450,6 +2456,14 @@ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); return; } + if (sent_packet_manager_.enable_pto() && max_consecutive_ptos_ > 0 && + sent_packet_manager_.GetConsecutivePtoCount() >= max_consecutive_ptos_) { + CloseConnection(QUIC_TOO_MANY_RTOS, + QuicStrCat(max_consecutive_ptos_ + 1, + "consecutive retransmission timeouts"), + ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); + return; + } const auto retransmission_mode = sent_packet_manager_.OnRetransmissionTimeout(); @@ -2461,23 +2475,29 @@ return; } - // In the TLP case, the SentPacketManager gives the connection the opportunity - // to send new data before retransmitting. - if (sent_packet_manager_.MaybeRetransmitTailLossProbe()) { + // In the PTO and TLP cases, the SentPacketManager gives the connection the + // opportunity to send new data before retransmitting. + if (sent_packet_manager_.enable_pto()) { + sent_packet_manager_.MaybeSendProbePackets(); + } else if (sent_packet_manager_.MaybeRetransmitTailLossProbe()) { // Send the pending retransmission now that it's been queued. WriteIfNotBlocked(); } if (sent_packet_manager_.fix_rto_retransmission()) { if (packet_generator_.packet_number() == previous_created_packet_number && - retransmission_mode == QuicSentPacketManager::RTO_MODE && + (retransmission_mode == QuicSentPacketManager::RTO_MODE || + retransmission_mode == QuicSentPacketManager::PTO_MODE) && !visitor_->WillingAndAbleToWrite()) { - // Send PING if timer fires in RTO mode but there is no data to send. + // Send PING if timer fires in RTO or PTO mode but there is no data to + // send. DCHECK_LT(0u, sent_packet_manager_.pending_timer_transmission_count()); visitor_->SendPing(); } - if (retransmission_mode != QuicSentPacketManager::LOSS_MODE && - retransmission_mode != QuicSentPacketManager::HANDSHAKE_MODE) { + if (retransmission_mode == QuicSentPacketManager::PTO_MODE) { + sent_packet_manager_.AdjustPendingTimerTransmissions(); + } + if (retransmission_mode != QuicSentPacketManager::LOSS_MODE) { // When timer fires in TLP or RTO mode, ensure 1) at least one packet is // created, or there is data to send and available credit (such that // packets will be sent eventually). @@ -2485,21 +2505,13 @@ packet_generator_.packet_number() == previous_created_packet_number && (!visitor_->WillingAndAbleToWrite() || sent_packet_manager_.pending_timer_transmission_count() == 0u)) - << "previous_crypto_retransmit_count: " - << previous_crypto_retransmit_count - << ", crypto_retransmit_count: " << stats_.crypto_retransmit_count - << ", previous_loss_timeout_count: " << previous_loss_timeout_count - << ", loss_timeout_count: " << stats_.loss_timeout_count - << ", previous_tlp_count: " << previous_tlp_count - << ", tlp_count: " << stats_.tlp_count - << ", pervious_rto_count: " << pervious_rto_count - << ", rto_count: " << stats_.rto_count - << ", previous_created_packet_number: " - << previous_created_packet_number + << "retransmission_mode: " << retransmission_mode << ", packet_number: " << packet_generator_.packet_number() << ", session has data to write: " << visitor_->WillingAndAbleToWrite() - << ", writer is blocked: " << writer_->IsWriteBlocked(); + << ", writer is blocked: " << writer_->IsWriteBlocked() + << ", pending_timer_transmission_count: " + << sent_packet_manager_.pending_timer_transmission_count(); } }
diff --git a/quic/core/quic_connection.h b/quic/core/quic_connection.h index 118e13b..7637584 100644 --- a/quic/core/quic_connection.h +++ b/quic/core/quic_connection.h
@@ -1429,6 +1429,10 @@ // Indicates whether a RETRY packet has been parsed. bool retry_has_been_parsed_; + + // If max_consecutive_ptos_ > 0, close connection if consecutive PTOs is + // greater than max_consecutive_ptos. + size_t max_consecutive_ptos_; }; } // namespace quic
diff --git a/quic/core/quic_connection_stats.cc b/quic/core/quic_connection_stats.cc index 246b9f1..98859ac 100644 --- a/quic/core/quic_connection_stats.cc +++ b/quic/core/quic_connection_stats.cc
@@ -34,6 +34,7 @@ loss_timeout_count(0), tlp_count(0), rto_count(0), + pto_count(0), min_rtt_us(0), srtt_us(0), max_packet_size(0), @@ -77,6 +78,7 @@ os << " loss_timeout_count: " << s.loss_timeout_count; os << " tlp_count: " << s.tlp_count; os << " rto_count: " << s.rto_count; + os << " pto_count: " << s.pto_count; os << " min_rtt_us: " << s.min_rtt_us; os << " srtt_us: " << s.srtt_us; os << " max_packet_size: " << s.max_packet_size;
diff --git a/quic/core/quic_connection_stats.h b/quic/core/quic_connection_stats.h index 5317c7a..56bb510 100644 --- a/quic/core/quic_connection_stats.h +++ b/quic/core/quic_connection_stats.h
@@ -76,6 +76,7 @@ size_t loss_timeout_count; size_t tlp_count; size_t rto_count; // Count of times the rto timer fired. + size_t pto_count; int64_t min_rtt_us; // Minimum RTT in microseconds. int64_t srtt_us; // Smoothed RTT in microseconds.
diff --git a/quic/core/quic_connection_test.cc b/quic/core/quic_connection_test.cc index f90a9db..5546419 100644 --- a/quic/core/quic_connection_test.cc +++ b/quic/core/quic_connection_test.cc
@@ -8794,6 +8794,121 @@ EXPECT_EQ(1u, writer_->ping_frames().size()); } +TEST_P(QuicConnectionTest, ProbeTimeout) { + if (!connection_.session_decides_what_to_write() || + !GetQuicReloadableFlag(quic_fix_rto_retransmission3)) { + return; + } + SetQuicReloadableFlag(quic_enable_pto, true); + SetQuicReloadableFlag(quic_fix_rto_retransmission3, true); + QuicConfig config; + QuicTagVector connection_options; + connection_options.push_back(k2PTO); + config.SetConnectionOptionsToSend(connection_options); + EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)); + connection_.SetFromConfig(config); + EXPECT_FALSE(connection_.GetRetransmissionAlarm()->IsSet()); + + QuicStreamId stream_id = 2; + QuicPacketNumber last_packet; + SendStreamDataToPeer(stream_id, "foooooo", 0, NO_FIN, &last_packet); + SendStreamDataToPeer(stream_id, "foooooo", 7, NO_FIN, &last_packet); + EXPECT_TRUE(connection_.GetRetransmissionAlarm()->IsSet()); + + // Reset stream. + EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(1); + SendRstStream(stream_id, QUIC_ERROR_PROCESSING_STREAM, 3); + + // Fire the PTO and verify only the RST_STREAM is resent, not stream data. + EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(1); + connection_.GetRetransmissionAlarm()->Fire(); + EXPECT_EQ(0u, writer_->stream_frames().size()); + EXPECT_EQ(1u, writer_->rst_stream_frames().size()); + EXPECT_TRUE(connection_.GetRetransmissionAlarm()->IsSet()); +} + +TEST_P(QuicConnectionTest, CloseConnectionAfter7ClientPTOs) { + if (!connection_.session_decides_what_to_write() || + !GetQuicReloadableFlag(quic_fix_rto_retransmission3)) { + return; + } + SetQuicReloadableFlag(quic_enable_pto, true); + QuicConfig config; + QuicTagVector connection_options; + connection_options.push_back(k2PTO); + connection_options.push_back(k7PTO); + config.SetConnectionOptionsToSend(connection_options); + EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)); + connection_.SetFromConfig(config); + EXPECT_FALSE(connection_.GetRetransmissionAlarm()->IsSet()); + + // Send stream data. + SendStreamDataToPeer( + GetNthClientInitiatedStreamId(1, connection_.transport_version()), "foo", + 0, FIN, nullptr); + + // Fire the retransmission alarm 6 times. + for (int i = 0; i < 6; ++i) { + EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)); + connection_.GetRetransmissionAlarm()->Fire(); + EXPECT_TRUE(connection_.GetTimeoutAlarm()->IsSet()); + EXPECT_TRUE(connection_.connected()); + } + + EXPECT_EQ(0u, connection_.sent_packet_manager().GetConsecutiveTlpCount()); + EXPECT_EQ(0u, connection_.sent_packet_manager().GetConsecutiveRtoCount()); + EXPECT_EQ(6u, connection_.sent_packet_manager().GetConsecutivePtoCount()); + // Closes connection on 7th PTO. + EXPECT_CALL(visitor_, + OnConnectionClosed(_, ConnectionCloseSource::FROM_SELF)); + EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)); + connection_.GetRetransmissionAlarm()->Fire(); + EXPECT_FALSE(connection_.GetTimeoutAlarm()->IsSet()); + EXPECT_FALSE(connection_.connected()); + TestConnectionCloseQuicErrorCode(QUIC_TOO_MANY_RTOS); +} + +TEST_P(QuicConnectionTest, CloseConnectionAfter8ClientPTOs) { + if (!connection_.session_decides_what_to_write() || + !GetQuicReloadableFlag(quic_fix_rto_retransmission3)) { + return; + } + SetQuicReloadableFlag(quic_enable_pto, true); + QuicConfig config; + QuicTagVector connection_options; + connection_options.push_back(k2PTO); + connection_options.push_back(k8PTO); + config.SetConnectionOptionsToSend(connection_options); + EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)); + connection_.SetFromConfig(config); + EXPECT_FALSE(connection_.GetRetransmissionAlarm()->IsSet()); + + // Send stream data. + SendStreamDataToPeer( + GetNthClientInitiatedStreamId(1, connection_.transport_version()), "foo", + 0, FIN, nullptr); + + // Fire the retransmission alarm 7 times. + for (int i = 0; i < 7; ++i) { + EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)); + connection_.GetRetransmissionAlarm()->Fire(); + EXPECT_TRUE(connection_.GetTimeoutAlarm()->IsSet()); + EXPECT_TRUE(connection_.connected()); + } + + EXPECT_EQ(0u, connection_.sent_packet_manager().GetConsecutiveTlpCount()); + EXPECT_EQ(0u, connection_.sent_packet_manager().GetConsecutiveRtoCount()); + EXPECT_EQ(7u, connection_.sent_packet_manager().GetConsecutivePtoCount()); + // Closes connection on 8th PTO. + EXPECT_CALL(visitor_, + OnConnectionClosed(_, ConnectionCloseSource::FROM_SELF)); + EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)); + connection_.GetRetransmissionAlarm()->Fire(); + EXPECT_FALSE(connection_.GetTimeoutAlarm()->IsSet()); + EXPECT_FALSE(connection_.connected()); + TestConnectionCloseQuicErrorCode(QUIC_TOO_MANY_RTOS); +} + } // namespace } // namespace test } // namespace quic
diff --git a/quic/core/quic_sent_packet_manager.cc b/quic/core/quic_sent_packet_manager.cc index 54fbdfa..c1da218 100644 --- a/quic/core/quic_sent_packet_manager.cc +++ b/quic/core/quic_sent_packet_manager.cc
@@ -115,6 +115,9 @@ QuicTime::Delta::FromMilliseconds(kDefaultDelayedAckTimeMs)), rtt_updated_(false), acked_packets_iter_(last_ack_frame_.packets.rbegin()), + enable_pto_(false), + max_probe_packets_per_pto_(2), + consecutive_pto_count_(0), loss_removes_from_inflight_( GetQuicReloadableFlag(quic_loss_removes_from_inflight)), ignore_tlpr_if_no_pending_stream_data_( @@ -180,6 +183,18 @@ } } + if (GetQuicReloadableFlag(quic_enable_pto) && fix_rto_retransmission_) { + if (config.HasClientSentConnectionOption(k2PTO, perspective)) { + enable_pto_ = true; + QUIC_RELOADABLE_FLAG_COUNT_N(quic_enable_pto, 2, 4); + } + if (config.HasClientSentConnectionOption(k1PTO, perspective)) { + enable_pto_ = true; + max_probe_packets_per_pto_ = 1; + QUIC_RELOADABLE_FLAG_COUNT_N(quic_enable_pto, 1, 4); + } + } + // Configure congestion control. if (config.HasClientRequestedIndependentOption(kTBBR, perspective)) { SetSendAlgorithm(kBBR); @@ -340,6 +355,7 @@ // Reset all retransmit counters any time a new packet is acked. consecutive_rto_count_ = 0; consecutive_tlp_count_ = 0; + consecutive_pto_count_ = 0; consecutive_crypto_retransmission_count_ = 0; } @@ -470,7 +486,7 @@ << "transmission_type: " << QuicUtils::TransmissionTypeToString(transmission_type); // Handshake packets should never be sent as probing retransmissions. - DCHECK(!transmission_info->has_crypto_handshake || + DCHECK(enable_pto_ || !transmission_info->has_crypto_handshake || transmission_type != PROBING_RETRANSMISSION); if (!loss_removes_from_inflight_ && !RetransmissionLeavesBytesInFlight(transmission_type)) { @@ -741,6 +757,12 @@ ++stats_->rto_count; RetransmitRtoPackets(); return RTO_MODE; + case PTO_MODE: + QUIC_DVLOG(1) << ENDPOINT << "PTO mode"; + ++stats_->pto_count; + ++consecutive_pto_count_; + pending_timer_transmission_count_ = max_probe_packets_per_pto_; + return PTO_MODE; } } @@ -776,6 +798,7 @@ } bool QuicSentPacketManager::MaybeRetransmitTailLossProbe() { + DCHECK(!enable_pto_); if (pending_timer_transmission_count_ == 0) { return false; } @@ -804,6 +827,7 @@ } void QuicSentPacketManager::RetransmitRtoPackets() { + DCHECK(!enable_pto_); QUIC_BUG_IF(pending_timer_transmission_count_ > 0) << "Retransmissions already queued:" << pending_timer_transmission_count_; // Mark two packets for retransmission. @@ -859,6 +883,41 @@ } } +void QuicSentPacketManager::MaybeSendProbePackets() { + if (pending_timer_transmission_count_ == 0) { + return; + } + QuicPacketNumber packet_number = unacked_packets_.GetLeastUnacked(); + std::vector<QuicPacketNumber> probing_packets; + for (QuicUnackedPacketMap::const_iterator it = unacked_packets_.begin(); + it != unacked_packets_.end(); ++it, ++packet_number) { + if (it->state == OUTSTANDING && + unacked_packets_.HasRetransmittableFrames(*it)) { + probing_packets.push_back(packet_number); + if (probing_packets.size() == pending_timer_transmission_count_) { + break; + } + } + } + + for (QuicPacketNumber retransmission : probing_packets) { + QUIC_DVLOG(1) << ENDPOINT << "Marking " << retransmission + << " for probing retransmission"; + MarkForRetransmission(retransmission, PROBING_RETRANSMISSION); + } + // It is possible that there is not enough outstanding data for probing. +} + +void QuicSentPacketManager::AdjustPendingTimerTransmissions() { + if (pending_timer_transmission_count_ < max_probe_packets_per_pto_) { + // There are packets sent already, clear credit. + pending_timer_transmission_count_ = 0; + return; + } + // No packet gets sent, leave 1 credit to allow data to be write eventually. + pending_timer_transmission_count_ = 1; +} + QuicSentPacketManager::RetransmissionTimeoutMode QuicSentPacketManager::GetRetransmissionMode() const { DCHECK(unacked_packets_.HasInFlightPackets()); @@ -868,6 +927,9 @@ if (loss_algorithm_->GetLossTimeout() != QuicTime::Zero()) { return LOSS_MODE; } + if (enable_pto_) { + return PTO_MODE; + } if (consecutive_tlp_count_ < max_tail_loss_probes_) { if (unacked_packets_.HasUnackedRetransmittableFrames()) { return TLP_MODE; @@ -962,6 +1024,7 @@ case LOSS_MODE: return loss_algorithm_->GetLossTimeout(); case TLP_MODE: { + DCHECK(!enable_pto_); // TODO(ianswett): When CWND is available, it would be preferable to // set the timer based on the earliest retransmittable packet. // Base the updated timer on the send time of the last packet. @@ -971,6 +1034,7 @@ return std::max(clock_->ApproximateNow(), tlp_time); } case RTO_MODE: { + DCHECK(!enable_pto_); // The RTO is based on the first outstanding packet. const QuicTime sent_time = unacked_packets_.GetLastPacketSentTime(); QuicTime rto_time = sent_time + GetRetransmissionDelay(); @@ -979,6 +1043,12 @@ unacked_packets_.GetLastPacketSentTime() + GetTailLossProbeDelay(); return std::max(tlp_time, rto_time); } + case PTO_MODE: { + // Ensure PTO never gets set to a time in the past. + return std::max( + clock_->ApproximateNow(), + unacked_packets_.GetLastPacketSentTime() + GetProbeTimeoutDelay()); + } } DCHECK(false); return QuicTime::Zero(); @@ -1070,6 +1140,22 @@ return retransmission_delay; } +const QuicTime::Delta QuicSentPacketManager::GetProbeTimeoutDelay() const { + DCHECK(enable_pto_); + if (rtt_stats_.smoothed_rtt().IsZero()) { + if (rtt_stats_.initial_rtt().IsZero()) { + return QuicTime::Delta::FromSeconds(1); + } + return 2 * rtt_stats_.initial_rtt(); + } + const QuicTime::Delta pto_delay = + rtt_stats_.smoothed_rtt() + + std::max(4 * rtt_stats_.mean_deviation(), + QuicTime::Delta::FromMilliseconds(1)) + + peer_max_ack_delay_; + return pto_delay * (1 << consecutive_pto_count_); +} + QuicTime::Delta QuicSentPacketManager::GetSlowStartDuration() const { if (send_algorithm_->GetCongestionControlType() != kBBR) { return QuicTime::Delta::Infinite(); @@ -1124,6 +1210,7 @@ } consecutive_rto_count_ = 0; consecutive_tlp_count_ = 0; + consecutive_pto_count_ = 0; rtt_stats_.OnConnectionMigration(); send_algorithm_->OnConnectionMigration(); }
diff --git a/quic/core/quic_sent_packet_manager.h b/quic/core/quic_sent_packet_manager.h index 1373646..ee285e8 100644 --- a/quic/core/quic_sent_packet_manager.h +++ b/quic/core/quic_sent_packet_manager.h
@@ -103,6 +103,9 @@ // Re-invoke the loss detection when a packet is not acked before the // loss detection algorithm expects. LOSS_MODE, + // A probe timeout. At least one probe packet must be sent when timer + // expires. + PTO_MODE, }; QuicSentPacketManager(Perspective perspective, @@ -337,6 +340,8 @@ size_t GetConsecutiveTlpCount() const { return consecutive_tlp_count_; } + size_t GetConsecutivePtoCount() const { return consecutive_pto_count_; } + void OnApplicationLimited(); const SendAlgorithmInterface* GetSendAlgorithm() const { @@ -390,6 +395,12 @@ // Setting the send algorithm once the connection is underway is dangerous. void SetSendAlgorithm(SendAlgorithmInterface* send_algorithm); + // Sends up to max_probe_packets_per_pto_ probe packets. + void MaybeSendProbePackets(); + + // Called to adjust pending_timer_transmission_count_ accordingly. + void AdjustPendingTimerTransmissions(); + bool supports_multiple_packet_number_spaces() const { return unacked_packets_.supports_multiple_packet_number_spaces(); } @@ -400,6 +411,8 @@ bool fix_rto_retransmission() const { return fix_rto_retransmission_; } + bool enable_pto() const { return enable_pto_; } + private: friend class test::QuicConnectionPeer; friend class test::QuicSentPacketManagerPeer; @@ -445,6 +458,9 @@ return GetRetransmissionDelay(consecutive_rto_count_); } + // Returns the probe timeout. + const QuicTime::Delta GetProbeTimeoutDelay() const; + // Returns the newest transmission associated with a packet. QuicPacketNumber GetNewestRetransmission( QuicPacketNumber packet_number, @@ -623,6 +639,15 @@ // OnAckRangeStart, and gradually moves in OnAckRange.. PacketNumberQueue::const_reverse_iterator acked_packets_iter_; + // If true, enable PTO mode which unifies TLP and RTO modes. + bool enable_pto_; + + // Maximum number of probes to send when PTO fires. + size_t max_probe_packets_per_pto_; + + // Number of times the PTO timer has fired in a row without receiving an ack. + size_t consecutive_pto_count_; + // Latched value of quic_loss_removes_from_inflight. const bool loss_removes_from_inflight_;
diff --git a/quic/core/quic_sent_packet_manager_test.cc b/quic/core/quic_sent_packet_manager_test.cc index 56c3d8a..ae5722b 100644 --- a/quic/core/quic_sent_packet_manager_test.cc +++ b/quic/core/quic_sent_packet_manager_test.cc
@@ -357,6 +357,20 @@ pending.transmission_type, HAS_RETRANSMITTABLE_DATA); } + void EnablePto(QuicTag tag) { + SetQuicReloadableFlag(quic_fix_rto_retransmission3, true); + manager_.SetSessionDecideWhatToWrite(true); + SetQuicReloadableFlag(quic_enable_pto, true); + QuicConfig config; + QuicTagVector options; + options.push_back(tag); + QuicConfigPeer::SetReceivedConnectionOptions(&config, options); + EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)); + EXPECT_CALL(*network_change_visitor_, OnCongestionChange()); + manager_.SetFromConfig(config); + EXPECT_TRUE(manager_.enable_pto()); + } + QuicSentPacketManager manager_; MockClock clock_; QuicConnectionStats stats_; @@ -2980,6 +2994,107 @@ } } +TEST_P(QuicSentPacketManagerTest, ComputingProbeTimeout) { + EnablePto(k2PTO); + EXPECT_CALL(*send_algorithm_, PacingRate(_)) + .WillRepeatedly(Return(QuicBandwidth::Zero())); + EXPECT_CALL(*send_algorithm_, GetCongestionWindow()) + .WillRepeatedly(Return(10 * kDefaultTCPMSS)); + RttStats* rtt_stats = const_cast<RttStats*>(manager_.GetRttStats()); + rtt_stats->UpdateRtt(QuicTime::Delta::FromMilliseconds(100), + QuicTime::Delta::Zero(), QuicTime::Zero()); + QuicTime::Delta srtt = rtt_stats->smoothed_rtt(); + + SendDataPacket(1, ENCRYPTION_FORWARD_SECURE); + // Verify PTO is correctly set. + QuicTime::Delta expected_pto_delay = + srtt + 4 * rtt_stats->mean_deviation() + + QuicTime::Delta::FromMilliseconds(kDefaultDelayedAckTimeMs); + EXPECT_EQ(clock_.Now() + expected_pto_delay, + manager_.GetRetransmissionTime()); + + clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(10)); + SendDataPacket(2, ENCRYPTION_FORWARD_SECURE); + // Verify PTO is correctly set based on sent time of packet 2. + EXPECT_EQ(clock_.Now() + expected_pto_delay, + manager_.GetRetransmissionTime()); + EXPECT_EQ(0u, stats_.pto_count); + + // Invoke PTO. + clock_.AdvanceTime(expected_pto_delay); + manager_.OnRetransmissionTimeout(); + EXPECT_EQ(QuicTime::Delta::Zero(), manager_.TimeUntilSend(clock_.Now())); + EXPECT_EQ(1u, stats_.pto_count); + + // Verify two probe packets get sent. + EXPECT_CALL(notifier_, RetransmitFrames(_, _)) + .Times(2) + .WillOnce(WithArgs<1>(Invoke([this](TransmissionType type) { + RetransmitDataPacket(3, type, ENCRYPTION_FORWARD_SECURE); + }))) + .WillOnce(WithArgs<1>(Invoke([this](TransmissionType type) { + RetransmitDataPacket(4, type, ENCRYPTION_FORWARD_SECURE); + }))); + manager_.MaybeSendProbePackets(); + // Verify PTO period gets set to twice the current value. + QuicTime sent_time = clock_.Now(); + EXPECT_EQ(sent_time + expected_pto_delay * 2, + manager_.GetRetransmissionTime()); + + // Received ACK for packets 1 and 2. + uint64_t acked[] = {1, 2}; + ExpectAcksAndLosses(true, acked, QUIC_ARRAYSIZE(acked), nullptr, 0); + manager_.OnAckFrameStart(QuicPacketNumber(2), QuicTime::Delta::Infinite(), + clock_.Now()); + manager_.OnAckRange(QuicPacketNumber(1), QuicPacketNumber(3)); + EXPECT_EQ(PACKETS_NEWLY_ACKED, + manager_.OnAckFrameEnd(clock_.Now(), QuicPacketNumber(1), + ENCRYPTION_FORWARD_SECURE)); + expected_pto_delay = + rtt_stats->SmoothedOrInitialRtt() + + std::max(4 * rtt_stats->mean_deviation(), + QuicTime::Delta::FromMilliseconds(1)) + + QuicTime::Delta::FromMilliseconds(kDefaultDelayedAckTimeMs); + + // Verify PTO is correctly re-armed based on sent time of packet 4. + EXPECT_EQ(sent_time + expected_pto_delay, manager_.GetRetransmissionTime()); +} + +TEST_P(QuicSentPacketManagerTest, SendOneProbePacket) { + EnablePto(k1PTO); + EXPECT_CALL(*send_algorithm_, PacingRate(_)) + .WillRepeatedly(Return(QuicBandwidth::Zero())); + EXPECT_CALL(*send_algorithm_, GetCongestionWindow()) + .WillRepeatedly(Return(10 * kDefaultTCPMSS)); + + SendDataPacket(1, ENCRYPTION_FORWARD_SECURE); + clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(10)); + SendDataPacket(2, ENCRYPTION_FORWARD_SECURE); + + RttStats* rtt_stats = const_cast<RttStats*>(manager_.GetRttStats()); + rtt_stats->UpdateRtt(QuicTime::Delta::FromMilliseconds(100), + QuicTime::Delta::Zero(), QuicTime::Zero()); + QuicTime::Delta srtt = rtt_stats->smoothed_rtt(); + // Verify PTO period is correctly set. + QuicTime::Delta expected_pto_delay = + srtt + 4 * rtt_stats->mean_deviation() + + QuicTime::Delta::FromMilliseconds(kDefaultDelayedAckTimeMs); + EXPECT_EQ(clock_.Now() + expected_pto_delay, + manager_.GetRetransmissionTime()); + + // Invoke PTO. + clock_.AdvanceTime(expected_pto_delay); + manager_.OnRetransmissionTimeout(); + EXPECT_EQ(QuicTime::Delta::Zero(), manager_.TimeUntilSend(clock_.Now())); + + // Verify one probe packet gets sent. + EXPECT_CALL(notifier_, RetransmitFrames(_, _)) + .WillOnce(WithArgs<1>(Invoke([this](TransmissionType type) { + RetransmitDataPacket(3, type, ENCRYPTION_FORWARD_SECURE); + }))); + manager_.MaybeSendProbePackets(); +} + } // namespace } // namespace test } // namespace quic