gfe-relnote: Add an EACK QUIC connection option to bundle a retransmittable frame with an ACK frame if the RTO or PTO timers have already fired.
This is expected to slightly reduce the number of TOO_MANY_RTOs server-side and recover faster from temporary network blackholes, such as NAT timeouts.
PiperOrigin-RevId: 294103126
Change-Id: I027aba540636ae4958c6615587d3dc85d70a2674
diff --git a/quic/core/crypto/crypto_protocol.h b/quic/core/crypto/crypto_protocol.h
index 70cd234..766fa0b 100644
--- a/quic/core/crypto/crypto_protocol.h
+++ b/quic/core/crypto/crypto_protocol.h
@@ -159,6 +159,8 @@
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 kEACK = TAG('E', 'A', 'C', 'K'); // Bundle ack-eliciting frame
+ // with an ACK after PTO/RTO
const QuicTag kILD0 = TAG('I', 'L', 'D', '0'); // IETF style loss detection
// (default with 1/8 RTT time
diff --git a/quic/core/quic_connection.cc b/quic/core/quic_connection.cc
index c4c862b..b064902 100644
--- a/quic/core/quic_connection.cc
+++ b/quic/core/quic_connection.cc
@@ -319,6 +319,7 @@
consecutive_num_packets_with_no_retransmittable_frames_(0),
max_consecutive_num_packets_with_no_retransmittable_frames_(
kMaxConsecutiveNonRetransmittablePackets),
+ bundle_retransmittable_with_pto_ack_(false),
fill_up_link_during_probing_(false),
probing_retransmission_pending_(false),
stateless_reset_token_received_(false),
@@ -475,6 +476,10 @@
framer_.set_process_timestamps(true);
uber_received_packet_manager_.set_save_timestamps(true);
}
+ if (GetQuicReloadableFlag(quic_bundle_retransmittable_with_pto_ack) &&
+ config.HasClientSentConnectionOption(kEACK, perspective_)) {
+ bundle_retransmittable_with_pto_ack_ = true;
+ }
if (config.HasReceivedMaxPacketSize()) {
peer_max_packet_size_ = config.ReceivedMaxPacketSize();
packet_creator_.SetMaxPacketLength(
@@ -2598,8 +2603,7 @@
return;
}
ResetAckStates();
- if (consecutive_num_packets_with_no_retransmittable_frames_ <
- max_consecutive_num_packets_with_no_retransmittable_frames_) {
+ if (!ShouldBundleRetransmittableFrameWithAck()) {
return;
}
consecutive_num_packets_with_no_retransmittable_frames_ = 0;
@@ -3916,8 +3920,7 @@
// Only try to bundle retransmittable data with ACK frame if default
// encryption level is forward secure.
if (encryption_level_ != ENCRYPTION_FORWARD_SECURE ||
- consecutive_num_packets_with_no_retransmittable_frames_ <
- max_consecutive_num_packets_with_no_retransmittable_frames_) {
+ !ShouldBundleRetransmittableFrameWithAck()) {
return;
}
consecutive_num_packets_with_no_retransmittable_frames_ = 0;
@@ -3930,6 +3933,22 @@
visitor_->OnAckNeedsRetransmittableFrame();
}
+bool QuicConnection::ShouldBundleRetransmittableFrameWithAck() const {
+ if (consecutive_num_packets_with_no_retransmittable_frames_ >=
+ max_consecutive_num_packets_with_no_retransmittable_frames_) {
+ return true;
+ }
+ if (bundle_retransmittable_with_pto_ack_ &&
+ (sent_packet_manager_.GetConsecutiveRtoCount() > 0 ||
+ sent_packet_manager_.GetConsecutivePtoCount() > 0)) {
+ QUIC_RELOADABLE_FLAG_COUNT(quic_bundle_retransmittable_with_pto_ack);
+ // Bundle a retransmittable frame with an ACK if the PTO or RTO has fired
+ // in order to recover more quickly in cases of temporary network outage.
+ return true;
+ }
+ return false;
+}
+
bool QuicConnection::FlushCoalescedPacket() {
ScopedCoalescedPacketClearer clearer(&coalesced_packet_);
if (!version().CanSendCoalescedPackets()) {
diff --git a/quic/core/quic_connection.h b/quic/core/quic_connection.h
index b932164..0b19905 100644
--- a/quic/core/quic_connection.h
+++ b/quic/core/quic_connection.h
@@ -1154,6 +1154,9 @@
// num_retransmittable_packets_received_since_last_ack_sent_ etc.
void ResetAckStates();
+ // Returns true if the ACK frame should be bundled with ACK-eliciting frame.
+ bool ShouldBundleRetransmittableFrameWithAck() const;
+
void PopulateStopWaitingFrame(QuicStopWaitingFrame* stop_waiting);
// Enables multiple packet number spaces support based on handshake protocol
@@ -1438,6 +1441,10 @@
// from the peer. Default to kMaxConsecutiveNonRetransmittablePackets.
size_t max_consecutive_num_packets_with_no_retransmittable_frames_;
+ // If true, bundle an ack-eliciting frame with an ACK if the PTO or RTO alarm
+ // have previously fired.
+ bool bundle_retransmittable_with_pto_ack_;
+
// If true, the connection will fill up the pipe with extra data whenever the
// congestion controller needs it in order to make a bandwidth estimate. This
// is useful if the application pesistently underutilizes the link, but still
diff --git a/quic/core/quic_connection_test.cc b/quic/core/quic_connection_test.cc
index 40f1895..d81bf2b 100644
--- a/quic/core/quic_connection_test.cc
+++ b/quic/core/quic_connection_test.cc
@@ -2855,6 +2855,49 @@
EXPECT_EQ(1u, writer_->ping_frames().size());
}
+TEST_P(QuicConnectionTest, AckNeedsRetransmittableFramesAfterPto) {
+ SetQuicReloadableFlag(quic_bundle_retransmittable_with_pto_ack, true);
+ // Disable TLP so the RTO fires immediately.
+ connection_.SetMaxTailLossProbes(0);
+ EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _));
+ QuicConfig config;
+ QuicTagVector connection_options;
+ connection_options.push_back(kEACK);
+ config.SetConnectionOptionsToSend(connection_options);
+ connection_.SetFromConfig(config);
+
+ connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
+ EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+ EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(10);
+
+ EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(4);
+ // Receive packets 1 - 9.
+ for (size_t i = 1; i <= 9; ++i) {
+ ProcessDataPacket(i);
+ }
+
+ // Send a ping and fire the retransmission alarm.
+ EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(2);
+ SendPing();
+ QuicTime retransmission_time =
+ connection_.GetRetransmissionAlarm()->deadline();
+ clock_.AdvanceTime(retransmission_time - clock_.Now());
+ connection_.GetRetransmissionAlarm()->Fire();
+ ASSERT_TRUE(manager_->GetConsecutiveRtoCount() > 0 ||
+ manager_->GetConsecutivePtoCount() > 0);
+
+ // Process a packet, which requests a retransmittable frame be bundled
+ // with the ACK.
+ EXPECT_CALL(visitor_, OnAckNeedsRetransmittableFrame())
+ .WillOnce(Invoke([this]() {
+ connection_.SendControlFrame(
+ QuicFrame(new QuicWindowUpdateFrame(1, 0, 0)));
+ }));
+ EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(1);
+ ProcessDataPacket(11);
+ EXPECT_EQ(1u, writer_->window_update_frames().size());
+}
+
TEST_P(QuicConnectionTest, LeastUnackedLower) {
if (VersionHasIetfInvariantHeader(GetParam().version.transport_version)) {
return;