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