gfe-relnote: In QUIC, when RTO fires and there is no packet to be RTOed, let connection send data. Protected by gfe2_reloadable_flag_quic_fix_rto_retransmission.

PiperOrigin-RevId: 258417558
Change-Id: I75267afafee6834f7b6f4cd59d08bdc036c3bd58
diff --git a/quic/core/quic_connection.cc b/quic/core/quic_connection.cc
index 1ad2ddb..66b3dd4 100644
--- a/quic/core/quic_connection.cc
+++ b/quic/core/quic_connection.cc
@@ -2422,6 +2422,13 @@
 
 void QuicConnection::OnRetransmissionTimeout() {
   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.
@@ -2446,6 +2453,29 @@
     WriteIfNotBlocked();
   }
 
+  if (sent_packet_manager_.fix_rto_retransmission()) {
+    // Making sure at least one packet is created when retransmission timer
+    // fires in TLP, RTO or HANDSHAKE mode. It is possible that loss algorithm
+    // invokes timer based loss but the packet does not need to be
+    // retransmitted.
+    QUIC_BUG_IF(stats_.loss_timeout_count == previous_loss_timeout_count &&
+                packet_generator_.packet_number() ==
+                    previous_created_packet_number)
+        << "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();
+  }
+
   // Ensure the retransmission alarm is always set if there are unacked packets
   // and nothing waiting to be sent.
   // This happens if the loss algorithm invokes a timer based loss, but the
diff --git a/quic/core/quic_connection_test.cc b/quic/core/quic_connection_test.cc
index f04d142..6b5b943 100644
--- a/quic/core/quic_connection_test.cc
+++ b/quic/core/quic_connection_test.cc
@@ -4065,6 +4065,54 @@
   EXPECT_EQ(QuicPacketNumber(1u), stop_waiting()->least_unacked);
 }
 
+// Regression test of b/133771183.
+TEST_P(QuicConnectionTest, RtoWithNoDataToRetransmit) {
+  if (!connection_.session_decides_what_to_write()) {
+    return;
+  }
+  connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  connection_.SetMaxTailLossProbes(0);
+
+  SendStreamDataToPeer(3, "foo", 0, NO_FIN, nullptr);
+  // Connection is cwnd limited.
+  CongestionBlockWrites();
+  // Stream gets reset.
+  SendRstStream(3, QUIC_ERROR_PROCESSING_STREAM, 3);
+  // Simulate the retransmission alarm firing.
+  clock_.AdvanceTime(DefaultRetransmissionTime());
+  // RTO fires, but there is no packet to be RTOed.
+  if (GetQuicReloadableFlag(quic_fix_rto_retransmission)) {
+    EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(1);
+  } else {
+    EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(0);
+  }
+  connection_.GetRetransmissionAlarm()->Fire();
+  if (GetQuicReloadableFlag(quic_fix_rto_retransmission)) {
+    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_retransmission)) {
+    EXPECT_CALL(visitor_, WillingAndAbleToWrite())
+        .WillRepeatedly(Return(false));
+  } else {
+    EXPECT_CALL(visitor_, WillingAndAbleToWrite()).WillRepeatedly(Return(true));
+  }
+  if (GetQuicReloadableFlag(quic_fix_rto_retransmission)) {
+    EXPECT_CALL(visitor_, OnAckNeedsRetransmittableFrame()).Times(1);
+  } else {
+    // Since there is a buffered RST_STREAM, no retransmittable frame is bundled
+    // with ACKs.
+    EXPECT_CALL(visitor_, OnAckNeedsRetransmittableFrame()).Times(0);
+  }
+  // Receives packets 1 - 40.
+  for (size_t i = 1; i <= 40; ++i) {
+    ProcessDataPacket(i);
+  }
+}
+
 TEST_P(QuicConnectionTest, RetransmitWithSameEncryptionLevel) {
   use_tagging_decrypter();
 
diff --git a/quic/core/quic_sent_packet_manager.cc b/quic/core/quic_sent_packet_manager.cc
index a4aa08e..a93115b 100644
--- a/quic/core/quic_sent_packet_manager.cc
+++ b/quic/core/quic_sent_packet_manager.cc
@@ -120,10 +120,15 @@
       loss_removes_from_inflight_(
           GetQuicReloadableFlag(quic_loss_removes_from_inflight)),
       ignore_tlpr_if_no_pending_stream_data_(
-          GetQuicReloadableFlag(quic_ignore_tlpr_if_no_pending_stream_data)) {
+          GetQuicReloadableFlag(quic_ignore_tlpr_if_no_pending_stream_data)),
+      fix_rto_retransmission_(
+          GetQuicReloadableFlag(quic_fix_rto_retransmission)) {
   if (loss_removes_from_inflight_) {
     QUIC_RELOADABLE_FLAG_COUNT(quic_loss_removes_from_inflight);
   }
+  if (fix_rto_retransmission_) {
+    QUIC_RELOADABLE_FLAG_COUNT(quic_fix_rto_retransmission);
+  }
   SetSendAlgorithm(congestion_control_type);
 }
 
@@ -803,7 +808,7 @@
     if (session_decides_what_to_write()) {
       has_retransmissions = it->state != OUTSTANDING;
     }
-    if (it->in_flight && !has_retransmissions &&
+    if (!fix_rto_retransmission_ && it->in_flight && !has_retransmissions &&
         !unacked_packets_.HasRetransmittableFrames(*it)) {
       // Log only for non-retransmittable data.
       // Retransmittable data is marked as lost during loss detection, and will
@@ -825,6 +830,13 @@
     for (QuicPacketNumber retransmission : retransmissions) {
       MarkForRetransmission(retransmission, RTO_RETRANSMISSION);
     }
+    if (fix_rto_retransmission_ && retransmissions.empty()) {
+      QUIC_BUG_IF(pending_timer_transmission_count_ != 0);
+      // No packets to be RTO retransmitted, raise up a credit to allow
+      // connection to send.
+      QUIC_CODE_COUNT(no_packets_to_be_rto_retransmitted);
+      pending_timer_transmission_count_ = 1;
+    }
   }
 }
 
diff --git a/quic/core/quic_sent_packet_manager.h b/quic/core/quic_sent_packet_manager.h
index b90d977..4727724 100644
--- a/quic/core/quic_sent_packet_manager.h
+++ b/quic/core/quic_sent_packet_manager.h
@@ -389,6 +389,8 @@
     return ignore_tlpr_if_no_pending_stream_data_;
   }
 
+  bool fix_rto_retransmission() const { return fix_rto_retransmission_; }
+
  private:
   friend class test::QuicConnectionPeer;
   friend class test::QuicSentPacketManagerPeer;
@@ -634,6 +636,9 @@
 
   // 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_retransmission.
+  const bool fix_rto_retransmission_;
 };
 
 }  // namespace quic
diff --git a/quic/core/quic_sent_packet_manager_test.cc b/quic/core/quic_sent_packet_manager_test.cc
index 075690e..fb801d0 100644
--- a/quic/core/quic_sent_packet_manager_test.cc
+++ b/quic/core/quic_sent_packet_manager_test.cc
@@ -2868,6 +2868,37 @@
             manager_.OnAckFrameEnd(clock_.Now(), ENCRYPTION_INITIAL));
 }
 
+TEST_P(QuicSentPacketManagerTest, RtoFiresNoPacketToRetransmit) {
+  if (!manager_.session_decides_what_to_write()) {
+    return;
+  }
+  // Send 10 packets.
+  for (size_t i = 1; i <= 10; ++i) {
+    SendDataPacket(i);
+  }
+  EXPECT_CALL(notifier_, RetransmitFrames(_, _))
+      .Times(2)
+      .WillOnce(WithArgs<1>(Invoke(
+          [this](TransmissionType type) { RetransmitDataPacket(11, type); })))
+      .WillOnce(WithArgs<1>(Invoke(
+          [this](TransmissionType type) { RetransmitDataPacket(12, type); })));
+  manager_.OnRetransmissionTimeout();
+  EXPECT_EQ(1u, stats_.rto_count);
+  EXPECT_EQ(0u, manager_.pending_timer_transmission_count());
+
+  // RTO fires again, but there is no packet to be RTO retransmitted.
+  EXPECT_CALL(notifier_, IsFrameOutstanding(_)).WillRepeatedly(Return(false));
+  EXPECT_CALL(notifier_, RetransmitFrames(_, _)).Times(0);
+  manager_.OnRetransmissionTimeout();
+  EXPECT_EQ(2u, stats_.rto_count);
+  if (GetQuicReloadableFlag(quic_fix_rto_retransmission)) {
+    // Verify a credit is raised up.
+    EXPECT_EQ(1u, manager_.pending_timer_transmission_count());
+  } else {
+    EXPECT_EQ(0u, manager_.pending_timer_transmission_count());
+  }
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic