gfe-relnote: In QUIC, schedule RTO whenever there is bytes in flight (rather than pending retransmittable frames). When timer fires in RTO_MODE and there is no data to send, force to send PING. Protected by gfe2_reloadable_flag_quic_fix_rto_retransmission3 which replaces gfe2_reloadable_flag_quic_fix_rto_retransmission2.

PiperOrigin-RevId: 263450880
Change-Id: Iccf125ee87c59ce49d53edc38c6460f120008a17
diff --git a/quic/core/quic_connection.cc b/quic/core/quic_connection.cc
index ce60171..581709b 100644
--- a/quic/core/quic_connection.cc
+++ b/quic/core/quic_connection.cc
@@ -2451,7 +2451,8 @@
     return;
   }
 
-  sent_packet_manager_.OnRetransmissionTimeout();
+  const auto retransmission_mode =
+      sent_packet_manager_.OnRetransmissionTimeout();
   WriteIfNotBlocked();
 
   // A write failure can result in the connection being closed, don't attempt to
@@ -2468,27 +2469,38 @@
   }
 
   if (sent_packet_manager_.fix_rto_retransmission()) {
-    // Making sure at least one packet is created when retransmission timer
-    // fires in TLP. It is possible that loss algorithm invokes timer based loss
-    // but the packet does not need to be retransmitted. And no packets will be
-    // sent if timer fires in HANDSHAKE or RTO mode but writer is blocked.
-    QUIC_BUG_IF(packet_generator_.packet_number() ==
-                    previous_created_packet_number &&
-                stats_.tlp_count != previous_tlp_count)
-        << "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
-        << ", packet_number: " << packet_generator_.packet_number()
-        << ", session has data to write: " << visitor_->WillingAndAbleToWrite()
-        << ", writer is blocked: " << writer_->IsWriteBlocked();
+    if (packet_generator_.packet_number() == previous_created_packet_number &&
+        retransmission_mode == QuicSentPacketManager::RTO_MODE &&
+        !visitor_->WillingAndAbleToWrite()) {
+      // Send PING if timer fires in RTO 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) {
+      // 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).
+      QUIC_BUG_IF(
+          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
+          << ", packet_number: " << packet_generator_.packet_number()
+          << ", session has data to write: "
+          << visitor_->WillingAndAbleToWrite()
+          << ", writer is blocked: " << writer_->IsWriteBlocked();
+    }
   }
 
   // Ensure the retransmission alarm is always set if there are unacked packets
diff --git a/quic/core/quic_connection_test.cc b/quic/core/quic_connection_test.cc
index 26c70f1..f90a9db 100644
--- a/quic/core/quic_connection_test.cc
+++ b/quic/core/quic_connection_test.cc
@@ -3452,7 +3452,12 @@
   // Ensure that the data is still in flight, but the retransmission alarm is no
   // longer set.
   EXPECT_GT(manager_->GetBytesInFlight(), 0u);
-  EXPECT_FALSE(connection_.GetRetransmissionAlarm()->IsSet());
+  if (QuicConnectionPeer::GetSentPacketManager(&connection_)
+          ->fix_rto_retransmission()) {
+    EXPECT_TRUE(connection_.GetRetransmissionAlarm()->IsSet());
+  } else {
+    EXPECT_FALSE(connection_.GetRetransmissionAlarm()->IsSet());
+  }
 }
 
 TEST_P(QuicConnectionTest, RetransmitForQuicRstStreamNoErrorOnRTO) {
@@ -3695,7 +3700,12 @@
 
   writer_->SetWritable();
   connection_.OnCanWrite();
-  EXPECT_FALSE(connection_.GetRetransmissionAlarm()->IsSet());
+  if (QuicConnectionPeer::GetSentPacketManager(&connection_)
+          ->fix_rto_retransmission()) {
+    EXPECT_TRUE(connection_.GetRetransmissionAlarm()->IsSet());
+  } else {
+    EXPECT_FALSE(connection_.GetRetransmissionAlarm()->IsSet());
+  }
   EXPECT_FALSE(QuicConnectionPeer::HasRetransmittableFrames(&connection_, 2));
 }
 
@@ -4159,25 +4169,25 @@
   // Simulate the retransmission alarm firing.
   clock_.AdvanceTime(DefaultRetransmissionTime());
   // RTO fires, but there is no packet to be RTOed.
-  if (GetQuicReloadableFlag(quic_fix_rto_retransmission2)) {
+  if (GetQuicReloadableFlag(quic_fix_rto_retransmission3)) {
     EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(1);
   } else {
     EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(0);
   }
   connection_.GetRetransmissionAlarm()->Fire();
-  if (GetQuicReloadableFlag(quic_fix_rto_retransmission2)) {
+  if (GetQuicReloadableFlag(quic_fix_rto_retransmission3)) {
     EXPECT_EQ(1u, writer_->rst_stream_frames().size());
   }
 
   EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(40);
   EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(20);
-  if (GetQuicReloadableFlag(quic_fix_rto_retransmission2)) {
+  if (GetQuicReloadableFlag(quic_fix_rto_retransmission3)) {
     EXPECT_CALL(visitor_, WillingAndAbleToWrite())
         .WillRepeatedly(Return(false));
   } else {
     EXPECT_CALL(visitor_, WillingAndAbleToWrite()).WillRepeatedly(Return(true));
   }
-  if (GetQuicReloadableFlag(quic_fix_rto_retransmission2)) {
+  if (GetQuicReloadableFlag(quic_fix_rto_retransmission3)) {
     EXPECT_CALL(visitor_, OnAckNeedsRetransmittableFrame()).Times(1);
   } else {
     // Since there is a buffered RST_STREAM, no retransmittable frame is bundled
@@ -8704,6 +8714,9 @@
   // Cancel the stream.
   EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(0);
   EXPECT_CALL(visitor_, OnWriteBlocked()).Times(AtLeast(1));
+  EXPECT_CALL(visitor_, WillingAndAbleToWrite())
+      .WillRepeatedly(
+          Invoke(&notifier_, &SimpleSessionNotifier::WillingToWrite));
   SendRstStream(stream_id, QUIC_ERROR_PROCESSING_STREAM, 3);
 
   // Retransmission timer fires in RTO mode.
@@ -8741,6 +8754,46 @@
   EXPECT_EQ(1u, connection_.NumQueuedPackets());
 }
 
+// Regresstion test for b/139375344.
+TEST_P(QuicConnectionTest, RtoForcesSendingPing) {
+  if (!QuicConnectionPeer::GetSentPacketManager(&connection_)
+           ->fix_rto_retransmission()) {
+    return;
+  }
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  connection_.SetMaxTailLossProbes(2);
+  EXPECT_EQ(0u, connection_.GetStats().tlp_count);
+  EXPECT_EQ(0u, connection_.GetStats().rto_count);
+
+  SendStreamDataToPeer(2, "foo", 0, NO_FIN, nullptr);
+  QuicTime retransmission_time =
+      connection_.GetRetransmissionAlarm()->deadline();
+  EXPECT_NE(QuicTime::Zero(), retransmission_time);
+  // TLP fires.
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, QuicPacketNumber(2), _, _));
+  clock_.AdvanceTime(retransmission_time - clock_.Now());
+  connection_.GetRetransmissionAlarm()->Fire();
+  EXPECT_EQ(1u, connection_.GetStats().tlp_count);
+  EXPECT_EQ(0u, connection_.GetStats().rto_count);
+  EXPECT_TRUE(connection_.GetRetransmissionAlarm()->IsSet());
+
+  // Packet 1 gets acked.
+  QuicAckFrame frame = InitAckFrame(1);
+  EXPECT_CALL(*send_algorithm_, OnCongestionEvent(_, _, _, _, _));
+  ProcessAckPacket(1, &frame);
+  EXPECT_TRUE(connection_.GetRetransmissionAlarm()->IsSet());
+  retransmission_time = connection_.GetRetransmissionAlarm()->deadline();
+
+  // RTO fires, verify a PING packet gets sent because there is no data to send.
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, QuicPacketNumber(3), _, _));
+  EXPECT_CALL(visitor_, SendPing()).WillOnce(Invoke([this]() { SendPing(); }));
+  clock_.AdvanceTime(retransmission_time - clock_.Now());
+  connection_.GetRetransmissionAlarm()->Fire();
+  EXPECT_EQ(1u, connection_.GetStats().tlp_count);
+  EXPECT_EQ(1u, connection_.GetStats().rto_count);
+  EXPECT_EQ(1u, writer_->ping_frames().size());
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic
diff --git a/quic/core/quic_sent_packet_manager.cc b/quic/core/quic_sent_packet_manager.cc
index 43f530e..54fbdfa 100644
--- a/quic/core/quic_sent_packet_manager.cc
+++ b/quic/core/quic_sent_packet_manager.cc
@@ -708,7 +708,8 @@
   return in_flight;
 }
 
-void QuicSentPacketManager::OnRetransmissionTimeout() {
+QuicSentPacketManager::RetransmissionTimeoutMode
+QuicSentPacketManager::OnRetransmissionTimeout() {
   DCHECK(unacked_packets_.HasInFlightPackets());
   DCHECK_EQ(0u, pending_timer_transmission_count_);
   // Handshake retransmission, timer based loss detection, TLP, and RTO are
@@ -720,14 +721,14 @@
     case HANDSHAKE_MODE:
       ++stats_->crypto_retransmit_count;
       RetransmitCryptoPackets();
-      return;
+      return HANDSHAKE_MODE;
     case LOSS_MODE: {
       ++stats_->loss_timeout_count;
       QuicByteCount prior_in_flight = unacked_packets_.bytes_in_flight();
       const QuicTime now = clock_->Now();
       InvokeLossDetection(now);
       MaybeInvokeCongestionEvent(false, prior_in_flight, now);
-      return;
+      return LOSS_MODE;
     }
     case TLP_MODE:
       ++stats_->tlp_count;
@@ -735,11 +736,11 @@
       pending_timer_transmission_count_ = 1;
       // TLPs prefer sending new data instead of retransmitting data, so
       // give the connection a chance to write before completing the TLP.
-      return;
+      return TLP_MODE;
     case RTO_MODE:
       ++stats_->rto_count;
       RetransmitRtoPackets();
-      return;
+      return RTO_MODE;
   }
 }
 
@@ -950,7 +951,8 @@
       pending_timer_transmission_count_ > 0) {
     return QuicTime::Zero();
   }
-  if (!unacked_packets_.HasUnackedRetransmittableFrames()) {
+  if (!fix_rto_retransmission_ &&
+      !unacked_packets_.HasUnackedRetransmittableFrames()) {
     return QuicTime::Zero();
   }
   switch (GetRetransmissionMode()) {
@@ -1285,10 +1287,10 @@
 
 void QuicSentPacketManager::SetSessionDecideWhatToWrite(
     bool session_decides_what_to_write) {
-  if (GetQuicReloadableFlag(quic_fix_rto_retransmission2) &&
+  if (GetQuicReloadableFlag(quic_fix_rto_retransmission3) &&
       session_decides_what_to_write) {
     fix_rto_retransmission_ = true;
-    QUIC_RELOADABLE_FLAG_COUNT(quic_fix_rto_retransmission2);
+    QUIC_RELOADABLE_FLAG_COUNT(quic_fix_rto_retransmission3);
   }
   unacked_packets_.SetSessionDecideWhatToWrite(session_decides_what_to_write);
 }
diff --git a/quic/core/quic_sent_packet_manager.h b/quic/core/quic_sent_packet_manager.h
index 98eb33e..1373646 100644
--- a/quic/core/quic_sent_packet_manager.h
+++ b/quic/core/quic_sent_packet_manager.h
@@ -91,6 +91,20 @@
     virtual void OnPathMtuIncreased(QuicPacketLength packet_size) = 0;
   };
 
+  // The retransmission timer is a single timer which switches modes depending
+  // upon connection state.
+  enum RetransmissionTimeoutMode {
+    // A conventional TCP style RTO.
+    RTO_MODE,
+    // A tail loss probe.  By default, QUIC sends up to two before RTOing.
+    TLP_MODE,
+    // Retransmission of handshake packets prior to handshake completion.
+    HANDSHAKE_MODE,
+    // Re-invoke the loss detection when a packet is not acked before the
+    // loss detection algorithm expects.
+    LOSS_MODE,
+  };
+
   QuicSentPacketManager(Perspective perspective,
                         const QuicClock* clock,
                         QuicRandom* random,
@@ -183,8 +197,9 @@
                     TransmissionType transmission_type,
                     HasRetransmittableData has_retransmittable_data);
 
-  // Called when the retransmission timer expires.
-  void OnRetransmissionTimeout();
+  // Called when the retransmission timer expires and returns the retransmission
+  // mode.
+  RetransmissionTimeoutMode OnRetransmissionTimeout();
 
   // Calculate the time until we can send the next packet to the wire.
   // Note 1: When kUnknownWaitTime is returned, there is no need to poll
@@ -389,20 +404,6 @@
   friend class test::QuicConnectionPeer;
   friend class test::QuicSentPacketManagerPeer;
 
-  // The retransmission timer is a single timer which switches modes depending
-  // upon connection state.
-  enum RetransmissionTimeoutMode {
-    // A conventional TCP style RTO.
-    RTO_MODE,
-    // A tail loss probe.  By default, QUIC sends up to two before RTOing.
-    TLP_MODE,
-    // Retransmission of handshake packets prior to handshake completion.
-    HANDSHAKE_MODE,
-    // Re-invoke the loss detection when a packet is not acked before the
-    // loss detection algorithm expects.
-    LOSS_MODE,
-  };
-
   typedef QuicLinkedHashMap<QuicPacketNumber,
                             TransmissionType,
                             QuicPacketNumberHash>
@@ -628,7 +629,7 @@
   // Latched value of quic_ignore_tlpr_if_no_pending_stream_data.
   const bool ignore_tlpr_if_no_pending_stream_data_;
 
-  // Latched value of quic_fix_rto_retransmission2 and
+  // Latched value of quic_fix_rto_retransmission3 and
   // session_decides_what_to_write.
   bool fix_rto_retransmission_;
 };
diff --git a/quic/core/quic_sent_packet_manager_test.cc b/quic/core/quic_sent_packet_manager_test.cc
index e1ab409..56c3d8a 100644
--- a/quic/core/quic_sent_packet_manager_test.cc
+++ b/quic/core/quic_sent_packet_manager_test.cc
@@ -2972,7 +2972,7 @@
   EXPECT_CALL(notifier_, RetransmitFrames(_, _)).Times(0);
   manager_.OnRetransmissionTimeout();
   EXPECT_EQ(2u, stats_.rto_count);
-  if (GetQuicReloadableFlag(quic_fix_rto_retransmission2)) {
+  if (GetQuicReloadableFlag(quic_fix_rto_retransmission3)) {
     // Verify a credit is raised up.
     EXPECT_EQ(1u, manager_.pending_timer_transmission_count());
   } else {