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