In QUIC, do not send PING when PING alarm fires but ShouldKeepConnectionAlive returns false. Client side only, protected by ENABLED gfe2_reloadable_flag_quic_fix_on_ping_timeout.

This is used to fix a bug where PING alarm is not cancelled when application finish reading data (draining streams get closed).

PiperOrigin-RevId: 326053367
Change-Id: I06aea85dad633c40e1d8486f0cb790c644b0e0e9
diff --git a/quic/core/quic_connection.cc b/quic/core/quic_connection.cc
index f442b1b..4bd2195 100644
--- a/quic/core/quic_connection.cc
+++ b/quic/core/quic_connection.cc
@@ -3091,9 +3091,14 @@
 }
 
 void QuicConnection::OnPingTimeout() {
-  if (!retransmission_alarm_->IsSet()) {
-    visitor_->SendPing();
+  if (retransmission_alarm_->IsSet()) {
+    return;
   }
+  if (GetQuicReloadableFlag(quic_fix_on_ping_timeout) &&
+      !visitor_->ShouldKeepConnectionAlive()) {
+    return;
+  }
+  visitor_->SendPing();
 }
 
 void QuicConnection::SendAck() {
diff --git a/quic/core/quic_connection_test.cc b/quic/core/quic_connection_test.cc
index 49c5c4d..5340233 100644
--- a/quic/core/quic_connection_test.cc
+++ b/quic/core/quic_connection_test.cc
@@ -11646,6 +11646,58 @@
   }
 }
 
+TEST_P(QuicConnectionTest, DonotSendPing) {
+  connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
+  connection_.OnHandshakeComplete();
+  EXPECT_TRUE(connection_.connected());
+  EXPECT_CALL(visitor_, ShouldKeepConnectionAlive())
+      .WillRepeatedly(Return(true));
+  EXPECT_FALSE(connection_.GetPingAlarm()->IsSet());
+  EXPECT_FALSE(connection_.GetRetransmissionAlarm()->IsSet());
+
+  SendStreamDataToPeer(
+      GetNthClientInitiatedStreamId(0, connection_.transport_version()),
+      "GET /", 0, FIN, nullptr);
+  EXPECT_TRUE(connection_.GetPingAlarm()->IsSet());
+  EXPECT_TRUE(connection_.GetRetransmissionAlarm()->IsSet());
+  EXPECT_EQ(QuicTime::Delta::FromSeconds(15),
+            connection_.GetPingAlarm()->deadline() - clock_.ApproximateNow());
+
+  // Now recevie an ACK and response of the previous packet, which will move the
+  // ping alarm forward.
+  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5));
+  QuicFrames frames;
+  QuicAckFrame ack_frame = InitAckFrame(1);
+  frames.push_back(QuicFrame(&ack_frame));
+  frames.push_back(QuicFrame(QuicStreamFrame(
+      GetNthClientInitiatedStreamId(0, connection_.transport_version()), true,
+      0u, quiche::QuicheStringPiece())));
+  EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+  EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _, _));
+  EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(1);
+  ProcessFramesPacketAtLevel(1, frames, ENCRYPTION_FORWARD_SECURE);
+  EXPECT_TRUE(connection_.GetPingAlarm()->IsSet());
+  EXPECT_FALSE(connection_.GetRetransmissionAlarm()->IsSet());
+  // The ping timer is set slightly less than 15 seconds in the future, because
+  // of the 1s ping timer alarm granularity.
+  EXPECT_EQ(
+      QuicTime::Delta::FromSeconds(15) - QuicTime::Delta::FromMilliseconds(5),
+      connection_.GetPingAlarm()->deadline() - clock_.ApproximateNow());
+
+  clock_.AdvanceTime(QuicTime::Delta::FromSeconds(15));
+  // Suppose now ShouldKeepConnectionAlive returns false.
+  EXPECT_CALL(visitor_, ShouldKeepConnectionAlive())
+      .WillRepeatedly(Return(false));
+  if (GetQuicReloadableFlag(quic_fix_on_ping_timeout)) {
+    // Verify PING does not get sent.
+    EXPECT_CALL(visitor_, SendPing()).Times(0);
+  } else {
+    // PING get sent even ShouldKeepConnectionAlive returns false.
+    EXPECT_CALL(visitor_, SendPing()).Times(1);
+  }
+  connection_.GetPingAlarm()->Fire();
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic