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;