gfe-relnote: ignore TLPR for retransmission delay in TLP mode when sending pings from the ping alarm. Flag protected by quic_ignore_tlpr_if_stream_not_waiting_ack.

This change will help mitigate the issue that PING packets sent by retransmittable-on-wire are retransmitted too aggressively in TLPR.

PiperOrigin-RevId: 242968845
Change-Id: I7e346bc309cbb52411fa3a1f0ed11615f16a61bf
diff --git a/quic/core/quic_connection.cc b/quic/core/quic_connection.cc
index 311651c..8546076 100644
--- a/quic/core/quic_connection.cc
+++ b/quic/core/quic_connection.cc
@@ -2745,7 +2745,17 @@
 
 void QuicConnection::OnPingTimeout() {
   if (!retransmission_alarm_->IsSet()) {
+    bool enable_half_rtt_tail_loss_probe =
+        sent_packet_manager_.enable_half_rtt_tail_loss_probe();
+    if (enable_half_rtt_tail_loss_probe &&
+        GetQuicReloadableFlag(quic_ignore_tlpr_if_sending_ping)) {
+      sent_packet_manager_.set_enable_half_rtt_tail_loss_probe(false);
+    }
     visitor_->SendPing();
+    if (enable_half_rtt_tail_loss_probe &&
+        GetQuicReloadableFlag(quic_ignore_tlpr_if_sending_ping)) {
+      sent_packet_manager_.set_enable_half_rtt_tail_loss_probe(true);
+    }
   }
 }
 
diff --git a/quic/core/quic_connection_test.cc b/quic/core/quic_connection_test.cc
index 7345246..cd53edf 100644
--- a/quic/core/quic_connection_test.cc
+++ b/quic/core/quic_connection_test.cc
@@ -1201,6 +1201,14 @@
     connection_.OnStreamReset(id, error);
   }
 
+  void SendPing() {
+    if (connection_.session_decides_what_to_write()) {
+      notifier_.WriteOrBufferPing();
+    } else {
+      connection_.SendControlFrame(QuicFrame(QuicPingFrame(1)));
+    }
+  }
+
   void ProcessAckPacket(uint64_t packet_number, QuicAckFrame* frame) {
     if (packet_number > 1) {
       QuicPacketCreatorPeer::SetPacketNumber(&peer_creator_, packet_number - 1);
@@ -3864,6 +3872,164 @@
   EXPECT_EQ(QuicPacketNumber(1u), stop_waiting()->least_unacked);
 }
 
+TEST_P(QuicConnectionTest, TailLossProbeDelayForStreamDataInTLPR) {
+  if (connection_.SupportsMultiplePacketNumberSpaces()) {
+    return;
+  }
+  // Set TLPR from QuicConfig.
+  EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _));
+  QuicConfig config;
+  QuicTagVector options;
+  options.push_back(kTLPR);
+  config.SetConnectionOptionsToSend(options);
+  connection_.SetFromConfig(config);
+  connection_.SetMaxTailLossProbes(1);
+
+  SendStreamDataToPeer(3, "foo", 0, NO_FIN, nullptr);
+  EXPECT_EQ(QuicPacketNumber(1u), stop_waiting()->least_unacked);
+
+  QuicTime retransmission_time =
+      connection_.GetRetransmissionAlarm()->deadline();
+  EXPECT_NE(QuicTime::Zero(), retransmission_time);
+  QuicTime::Delta expected_tlp_delay =
+      0.5 * manager_->GetRttStats()->SmoothedOrInitialRtt();
+  EXPECT_EQ(expected_tlp_delay, retransmission_time - clock_.Now());
+
+  EXPECT_EQ(QuicPacketNumber(1u), writer_->header().packet_number);
+  // Simulate firing of the retransmission alarm and retransmit the packet.
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, QuicPacketNumber(2), _, _));
+  clock_.AdvanceTime(retransmission_time - clock_.Now());
+  connection_.GetRetransmissionAlarm()->Fire();
+  EXPECT_EQ(QuicPacketNumber(2u), writer_->header().packet_number);
+
+  // We do not raise the high water mark yet.
+  EXPECT_EQ(QuicPacketNumber(1u), stop_waiting()->least_unacked);
+}
+
+TEST_P(QuicConnectionTest, TailLossProbeDelayForNonStreamDataInTLPR) {
+  if (connection_.SupportsMultiplePacketNumberSpaces()) {
+    return;
+  }
+  // Set TLPR from QuicConfig.
+  EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _));
+  QuicConfig config;
+  QuicTagVector options;
+  options.push_back(kTLPR);
+  config.SetConnectionOptionsToSend(options);
+  connection_.SetFromConfig(config);
+  connection_.SetMaxTailLossProbes(1);
+
+  // Sets retransmittable on wire.
+  const QuicTime::Delta retransmittable_on_wire_timeout =
+      QuicTime::Delta::FromMilliseconds(50);
+  connection_.set_retransmittable_on_wire_timeout(
+      retransmittable_on_wire_timeout);
+
+  EXPECT_TRUE(connection_.connected());
+  EXPECT_CALL(visitor_, ShouldKeepConnectionAlive())
+      .WillRepeatedly(Return(true));
+  EXPECT_FALSE(connection_.GetPathDegradingAlarm()->IsSet());
+  EXPECT_FALSE(connection_.IsPathDegrading());
+  EXPECT_FALSE(connection_.GetPingAlarm()->IsSet());
+
+  const char data[] = "data";
+  size_t data_size = strlen(data);
+  QuicStreamOffset offset = 0;
+
+  // Send a data packet.
+  connection_.SendStreamDataWithString(1, data, offset, NO_FIN);
+  offset += data_size;
+
+  // Path degrading alarm should be set when there is a retransmittable packet
+  // on the wire.
+  EXPECT_TRUE(connection_.GetPathDegradingAlarm()->IsSet());
+
+  // Verify the path degrading delay.
+  // First TLP with stream data.
+  QuicTime::Delta srtt = manager_->GetRttStats()->SmoothedOrInitialRtt();
+  QuicTime::Delta expected_delay = 0.5 * srtt;
+  // Add 1st RTO.
+  QuicTime::Delta retransmission_delay =
+      QuicTime::Delta::FromMilliseconds(kDefaultRetransmissionTimeMs);
+  expected_delay = expected_delay + retransmission_delay;
+  // Add 2nd RTO.
+  expected_delay = expected_delay + retransmission_delay * 2;
+  EXPECT_EQ(expected_delay,
+            QuicConnectionPeer::GetSentPacketManager(&connection_)
+                ->GetPathDegradingDelay());
+  ASSERT_TRUE(connection_.sent_packet_manager().HasInFlightPackets());
+
+  // The ping alarm is set for the ping timeout, not the shorter
+  // retransmittable_on_wire_timeout.
+  EXPECT_TRUE(connection_.GetPingAlarm()->IsSet());
+  EXPECT_EQ(QuicTime::Delta::FromSeconds(kPingTimeoutSecs),
+            connection_.GetPingAlarm()->deadline() - clock_.ApproximateNow());
+
+  // Receive an ACK for the data packet.
+  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _, _));
+  QuicAckFrame frame =
+      InitAckFrame({{QuicPacketNumber(1), QuicPacketNumber(2)}});
+  ProcessAckPacket(&frame);
+
+  // Path degrading alarm should be cancelled as there is no more
+  // reretransmittable packets on the wire.
+  EXPECT_FALSE(connection_.GetPathDegradingAlarm()->IsSet());
+  // The ping alarm should be set to the retransmittable_on_wire_timeout.
+  EXPECT_TRUE(connection_.GetPingAlarm()->IsSet());
+  EXPECT_EQ(retransmittable_on_wire_timeout,
+            connection_.GetPingAlarm()->deadline() - clock_.ApproximateNow());
+
+  // Simulate firing of the retransmittable on wire and send a PING.
+  EXPECT_CALL(visitor_, SendPing()).WillOnce(Invoke([this]() { SendPing(); }));
+  clock_.AdvanceTime(retransmittable_on_wire_timeout);
+  connection_.GetPingAlarm()->Fire();
+
+  // The retransmission alarm and the path degrading alarm should be set as
+  // there is a retransmittable packet (PING) on the wire,
+  EXPECT_TRUE(connection_.GetRetransmissionAlarm()->IsSet());
+  EXPECT_TRUE(connection_.GetPathDegradingAlarm()->IsSet());
+
+  // Verify the retransmission delay.
+  QuicTime::Delta min_rto_timeout =
+      QuicTime::Delta::FromMilliseconds(kMinRetransmissionTimeMs);
+  srtt = manager_->GetRttStats()->SmoothedOrInitialRtt();
+  if (GetQuicReloadableFlag(quic_ignore_tlpr_if_sending_ping)) {
+    // First TLP without unacked stream data will no longer use TLPR.
+    expected_delay = std::max(2 * srtt, 1.5 * srtt + 0.5 * min_rto_timeout);
+  } else {
+    expected_delay =
+        std::max(QuicTime::Delta::FromMilliseconds(kMinTailLossProbeTimeoutMs),
+                 srtt * 0.5);
+  }
+  EXPECT_EQ(expected_delay,
+            connection_.GetRetransmissionAlarm()->deadline() - clock_.Now());
+
+  // Verify the path degrading delay.
+  // Path degrading delay will count TLPR for the tail loss probe delay.
+  expected_delay =
+      std::max(QuicTime::Delta::FromMilliseconds(kMinTailLossProbeTimeoutMs),
+               srtt * 0.5);
+  // Add 1st RTO.
+  retransmission_delay =
+      std::max(manager_->GetRttStats()->smoothed_rtt() +
+                   4 * manager_->GetRttStats()->mean_deviation(),
+               min_rto_timeout);
+  expected_delay = expected_delay + retransmission_delay;
+  // Add 2nd RTO.
+  expected_delay = expected_delay + retransmission_delay * 2;
+  EXPECT_EQ(expected_delay,
+            QuicConnectionPeer::GetSentPacketManager(&connection_)
+                ->GetPathDegradingDelay());
+
+  // The ping alarm is set for the ping timeout, not the shorter
+  // retransmittable_on_wire_timeout.
+  EXPECT_TRUE(connection_.GetPingAlarm()->IsSet());
+  EXPECT_EQ(QuicTime::Delta::FromSeconds(kPingTimeoutSecs),
+            connection_.GetPingAlarm()->deadline() - clock_.ApproximateNow());
+}
+
 TEST_P(QuicConnectionTest, RTO) {
   if (connection_.SupportsMultiplePacketNumberSpaces()) {
     return;
@@ -4435,9 +4601,7 @@
 
   writer_->Reset();
   clock_.AdvanceTime(QuicTime::Delta::FromSeconds(15));
-  EXPECT_CALL(visitor_, SendPing()).WillOnce(Invoke([this]() {
-    connection_.SendControlFrame(QuicFrame(QuicPingFrame(1)));
-  }));
+  EXPECT_CALL(visitor_, SendPing()).WillOnce(Invoke([this]() { SendPing(); }));
   connection_.GetPingAlarm()->Fire();
   EXPECT_EQ(1u, writer_->frame_count());
   ASSERT_EQ(1u, writer_->ping_frames().size());
diff --git a/quic/core/quic_sent_packet_manager.h b/quic/core/quic_sent_packet_manager.h
index c892126..039ee59 100644
--- a/quic/core/quic_sent_packet_manager.h
+++ b/quic/core/quic_sent_packet_manager.h
@@ -355,6 +355,15 @@
     delayed_ack_time_ = delayed_ack_time;
   }
 
+  bool enable_half_rtt_tail_loss_probe() const {
+    return enable_half_rtt_tail_loss_probe_;
+  }
+
+  void set_enable_half_rtt_tail_loss_probe(
+      bool enable_half_rtt_tail_loss_probe) {
+    enable_half_rtt_tail_loss_probe_ = enable_half_rtt_tail_loss_probe;
+  }
+
   const QuicUnackedPacketMap& unacked_packets() const {
     return unacked_packets_;
   }
diff --git a/quic/test_tools/simple_session_notifier.cc b/quic/test_tools/simple_session_notifier.cc
index 7a76194..643821a 100644
--- a/quic/test_tools/simple_session_notifier.cc
+++ b/quic/test_tools/simple_session_notifier.cc
@@ -112,6 +112,19 @@
   WriteBufferedControlFrames();
 }
 
+void SimpleSessionNotifier::WriteOrBufferPing() {
+  QUIC_DVLOG(1) << "Writing PING_FRAME";
+  const bool had_buffered_data =
+      HasBufferedStreamData() || HasBufferedControlFrames();
+  control_frames_.emplace_back(
+      (QuicFrame(QuicPingFrame(++last_control_frame_id_))));
+  if (had_buffered_data) {
+    QUIC_DLOG(WARNING) << "Connection is write blocked";
+    return;
+  }
+  WriteBufferedControlFrames();
+}
+
 void SimpleSessionNotifier::NeuterUnencryptedData() {
   for (const auto& interval : crypto_bytes_transferred_[ENCRYPTION_INITIAL]) {
     // TODO(nharper): Handle CRYPTO frame case.
diff --git a/quic/test_tools/simple_session_notifier.h b/quic/test_tools/simple_session_notifier.h
index f7982d2..17616b7 100644
--- a/quic/test_tools/simple_session_notifier.h
+++ b/quic/test_tools/simple_session_notifier.h
@@ -31,6 +31,8 @@
   void WriteOrBufferRstStream(QuicStreamId id,
                               QuicRstStreamErrorCode error,
                               QuicStreamOffset bytes_written);
+  // Tries to write PING.
+  void WriteOrBufferPing();
 
   // Tries to write CRYPTO data and returns the number of bytes written.
   size_t WriteCryptoData(EncryptionLevel level,
diff --git a/quic/test_tools/simple_session_notifier_test.cc b/quic/test_tools/simple_session_notifier_test.cc
index 136544b..53712fd 100644
--- a/quic/test_tools/simple_session_notifier_test.cc
+++ b/quic/test_tools/simple_session_notifier_test.cc
@@ -100,6 +100,31 @@
   EXPECT_FALSE(notifier_.StreamIsWaitingForAcks(5));
 }
 
+TEST_F(SimpleSessionNotifierTest, WriteOrBufferPing) {
+  InSequence s;
+  // Write ping when connection is not write blocked.
+  EXPECT_CALL(connection_, SendControlFrame(_))
+      .WillRepeatedly(
+          Invoke(this, &SimpleSessionNotifierTest::ControlFrameConsumed));
+  notifier_.WriteOrBufferPing();
+  EXPECT_EQ(0u, notifier_.StreamBytesToSend());
+  EXPECT_FALSE(notifier_.WillingToWrite());
+
+  // Write stream data and cause the connection to be write blocked.
+  EXPECT_CALL(connection_, SendStreamData(3, 1024, 0, NO_FIN))
+      .WillOnce(Return(QuicConsumedData(1024, false)));
+  notifier_.WriteOrBufferData(3, 1024, NO_FIN);
+  EXPECT_EQ(0u, notifier_.StreamBytesToSend());
+  EXPECT_CALL(connection_, SendStreamData(5, 512, 0, NO_FIN))
+      .WillOnce(Return(QuicConsumedData(256, false)));
+  notifier_.WriteOrBufferData(5, 512, NO_FIN);
+  EXPECT_TRUE(notifier_.WillingToWrite());
+
+  // Connection is blocked.
+  EXPECT_CALL(connection_, SendControlFrame(_)).Times(0);
+  notifier_.WriteOrBufferPing();
+}
+
 TEST_F(SimpleSessionNotifierTest, NeuterUnencryptedData) {
   InSequence s;
   // Send crypto data [0, 1024) in ENCRYPTION_INITIAL.