gfe-relnote: In QUIC, send PING when TLP fires and no packet is created. Protected by existing gfe2_reloadable_flag_quic_fix_rto_retransmission3. PiperOrigin-RevId: 266418710 Change-Id: I1979a67e966adee15a2926f1628ff5175ef5707d
diff --git a/quic/core/quic_connection.cc b/quic/core/quic_connection.cc index cc99744..257ef29 100644 --- a/quic/core/quic_connection.cc +++ b/quic/core/quic_connection.cc
@@ -2586,11 +2586,19 @@ if (sent_packet_manager_.fix_rto_retransmission()) { if (packet_generator_.packet_number() == previous_created_packet_number && - (retransmission_mode == QuicSentPacketManager::RTO_MODE || + (retransmission_mode == QuicSentPacketManager::TLP_MODE || + retransmission_mode == QuicSentPacketManager::RTO_MODE || retransmission_mode == QuicSentPacketManager::PTO_MODE) && !visitor_->WillingAndAbleToWrite()) { // Send PING if timer fires in RTO or PTO mode but there is no data to // send. + // When TLP fires, either new data or tail loss probe should be sent. + // There is corner case where TLP fires after RTO because packets get + // acked. Two packets are marked RTO_RETRANSMITTED, but the first packet + // is retransmitted as two packets because of packet number length + // increases (please see QuicConnectionTest.RtoPacketAsTwo). + QUIC_BUG_IF(retransmission_mode == QuicSentPacketManager::TLP_MODE && + stats_.rto_count == 0); DCHECK_LT(0u, sent_packet_manager_.pending_timer_transmission_count()); visitor_->SendPing(); }
diff --git a/quic/core/quic_connection_test.cc b/quic/core/quic_connection_test.cc index 9f8127d..ec09686 100644 --- a/quic/core/quic_connection_test.cc +++ b/quic/core/quic_connection_test.cc
@@ -1283,8 +1283,10 @@ StreamSendingState state, QuicPacketNumber* last_packet) { QuicByteCount packet_size; + // Save the last packet's size. EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)) - .WillOnce(SaveArg<3>(&packet_size)); + .Times(AnyNumber()) + .WillRepeatedly(SaveArg<3>(&packet_size)); connection_.SendStreamDataWithString(id, data, offset, state); if (last_packet != nullptr) { *last_packet = creator_->packet_number(); @@ -9156,6 +9158,52 @@ connection_close_frames[0].transport_close_frame_type); } +// Regression test for b/137401387 and b/138962304. +TEST_P(QuicConnectionTest, RtoPacketAsTwo) { + if (!QuicConnectionPeer::GetSentPacketManager(&connection_) + ->fix_rto_retransmission() || + connection_.PtoEnabled()) { + return; + } + connection_.SetMaxTailLossProbes(1); + connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE); + std::string stream_data(3000, 's'); + // Send packets 1 - 66 and exhaust cwnd. + for (size_t i = 0; i < 22; ++i) { + // 3 packets for each stream, the first 2 are guaranteed to be full packets. + SendStreamDataToPeer(i + 2, stream_data, 0, FIN, nullptr); + } + CongestionBlockWrites(); + + // Fires TLP. Please note, this tail loss probe has 1 byte less stream data + // compared to packet 1 because packet number length increases. + EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, QuicPacketNumber(67), _, _)); + connection_.GetRetransmissionAlarm()->Fire(); + // Fires RTO. Please note, although packets 2 and 3 *should* be RTOed, but + // packet 2 gets RTOed to two packets because packet number length increases. + EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, QuicPacketNumber(68), _, _)); + EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, QuicPacketNumber(69), _, _)); + connection_.GetRetransmissionAlarm()->Fire(); + + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); + // Resets all streams except 2 and ack packets 1 and 2. Now, packet 3 is the + // only one containing retransmittable frames. + for (size_t i = 1; i < 22; ++i) { + notifier_.OnStreamReset(i + 2, QUIC_STREAM_CANCELLED); + } + EXPECT_CALL(*send_algorithm_, OnCongestionEvent(_, _, _, _, _)); + QuicAckFrame frame = + InitAckFrame({{QuicPacketNumber(1), QuicPacketNumber(3)}}); + ProcessAckPacket(1, &frame); + CongestionUnblockWrites(); + + // Fires TLP, verify a PING gets sent because packet 3 is marked + // RTO_RETRANSMITTED. + EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, QuicPacketNumber(70), _, _)); + EXPECT_CALL(visitor_, SendPing()).WillOnce(Invoke([this]() { SendPing(); })); + connection_.GetRetransmissionAlarm()->Fire(); +} + } // namespace } // namespace test } // namespace quic
diff --git a/quic/test_tools/simple_session_notifier.cc b/quic/test_tools/simple_session_notifier.cc index 16c441c..72a23d7 100644 --- a/quic/test_tools/simple_session_notifier.cc +++ b/quic/test_tools/simple_session_notifier.cc
@@ -175,6 +175,14 @@ } } +void SimpleSessionNotifier::OnStreamReset(QuicStreamId id, + QuicRstStreamErrorCode error) { + if (error != QUIC_STREAM_NO_ERROR) { + // Delete stream to avoid retransmissions. + stream_map_.erase(id); + } +} + bool SimpleSessionNotifier::WillingToWrite() const { QUIC_DVLOG(1) << "has_buffered_control_frames: " << HasBufferedControlFrames() << " as_lost_control_frames: " << !lost_control_frames_.empty()
diff --git a/quic/test_tools/simple_session_notifier.h b/quic/test_tools/simple_session_notifier.h index 7424053..b3d5d72 100644 --- a/quic/test_tools/simple_session_notifier.h +++ b/quic/test_tools/simple_session_notifier.h
@@ -45,6 +45,9 @@ // Called when connection_ becomes writable. void OnCanWrite(); + // Called to reset stream. + void OnStreamReset(QuicStreamId id, QuicRstStreamErrorCode error); + // Returns true if there are 1) unsent control frames and stream data, or 2) // lost control frames and stream data. bool WillingToWrite() const;