QUIC client do not send new request if now is close to idle timeout. Only affecting client, not protected.

PiperOrigin-RevId: 323845829
Change-Id: Iabaddc08fb901d28e228cd89d96add2ef4e88e85
diff --git a/quic/core/quic_connection.cc b/quic/core/quic_connection.cc
index 2fc3098..ff909e9 100644
--- a/quic/core/quic_connection.cc
+++ b/quic/core/quic_connection.cc
@@ -639,6 +639,28 @@
   legacy_version_encapsulation_sni_ = server_name;
 }
 
+bool QuicConnection::MaybeTestLiveness() {
+  if (encryption_level_ != ENCRYPTION_FORWARD_SECURE) {
+    return false;
+  }
+  const QuicTime idle_network_deadline =
+      idle_network_detector_.GetIdleNetworkDeadline();
+  if (!idle_network_deadline.IsInitialized()) {
+    return false;
+  }
+  const QuicTime now = clock_->ApproximateNow();
+  if (now > idle_network_deadline) {
+    QUIC_BUG << "Idle network deadline has passed";
+    return false;
+  }
+  const QuicTime::Delta timeout = idle_network_deadline - now;
+  if (!sent_packet_manager_.IsLessThanThreePTOs(timeout)) {
+    return false;
+  }
+  SendConnectivityProbingPacket(writer_, peer_address_);
+  return true;
+}
+
 void QuicConnection::ApplyConnectionOptions(
     const QuicTagVector& connection_options) {
   sent_packet_manager_.ApplyConnectionOptions(connection_options);
diff --git a/quic/core/quic_connection.h b/quic/core/quic_connection.h
index 32eabef..ff7ecb4 100644
--- a/quic/core/quic_connection.h
+++ b/quic/core/quic_connection.h
@@ -990,6 +990,11 @@
   // Can only be set if this is a client connection.
   void EnableLegacyVersionEncapsulation(const std::string& server_name);
 
+  // If now is close to idle timeout, returns true and sends a connectivity
+  // probing packet to test the connection for liveness. Otherwise, returns
+  // false.
+  bool MaybeTestLiveness();
+
  protected:
   // Calls cancel() on all the alarms owned by this connection.
   void CancelAllAlarms();
diff --git a/quic/core/quic_connection_test.cc b/quic/core/quic_connection_test.cc
index 5f1ff0c..f345c36 100644
--- a/quic/core/quic_connection_test.cc
+++ b/quic/core/quic_connection_test.cc
@@ -11547,6 +11547,50 @@
   connection_.GetRetransmissionAlarm()->Fire();
 }
 
+TEST_P(QuicConnectionTest, TestingLiveness) {
+  const size_t kMinRttMs = 40;
+  RttStats* rtt_stats = const_cast<RttStats*>(manager_->GetRttStats());
+  rtt_stats->UpdateRtt(QuicTime::Delta::FromMilliseconds(kMinRttMs),
+                       QuicTime::Delta::Zero(), QuicTime::Zero());
+  EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _));
+  QuicConfig config;
+
+  CryptoHandshakeMessage msg;
+  std::string error_details;
+  QuicConfig client_config;
+  client_config.SetInitialStreamFlowControlWindowToSend(
+      kInitialStreamFlowControlWindowForTest);
+  client_config.SetInitialSessionFlowControlWindowToSend(
+      kInitialSessionFlowControlWindowForTest);
+  client_config.SetIdleNetworkTimeout(QuicTime::Delta::FromSeconds(30));
+  client_config.ToHandshakeMessage(&msg, connection_.transport_version());
+  const QuicErrorCode error =
+      config.ProcessPeerHello(msg, CLIENT, &error_details);
+  EXPECT_THAT(error, IsQuicNoError());
+
+  if (connection_.version().AuthenticatesHandshakeConnectionIds()) {
+    QuicConfigPeer::SetReceivedOriginalConnectionId(
+        &config, connection_.connection_id());
+    QuicConfigPeer::SetReceivedInitialSourceConnectionId(
+        &config, connection_.connection_id());
+  }
+
+  connection_.SetFromConfig(config);
+  connection_.OnHandshakeComplete();
+  connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
+  ASSERT_TRUE(connection_.GetTimeoutAlarm()->IsSet());
+  EXPECT_FALSE(connection_.MaybeTestLiveness());
+
+  QuicTime deadline = connection_.GetTimeoutAlarm()->deadline();
+  QuicTime::Delta timeout = deadline - clock_.ApproximateNow();
+  // Advance time to near the idle timeout.
+  clock_.AdvanceTime(timeout - QuicTime::Delta::FromMilliseconds(1));
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(1);
+  EXPECT_TRUE(connection_.MaybeTestLiveness());
+  // Verify idle deadline does not change.
+  EXPECT_EQ(deadline, connection_.GetTimeoutAlarm()->deadline());
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic
diff --git a/quic/core/quic_idle_network_detector.cc b/quic/core/quic_idle_network_detector.cc
index 8d76fd2..a033f7a 100644
--- a/quic/core/quic_idle_network_detector.cc
+++ b/quic/core/quic_idle_network_detector.cc
@@ -95,8 +95,7 @@
     new_deadline = start_time_ + handshake_timeout_;
   }
   if (!idle_network_timeout_.IsInfinite()) {
-    const QuicTime idle_network_deadline =
-        last_network_activity_time() + idle_network_timeout_;
+    const QuicTime idle_network_deadline = GetIdleNetworkDeadline();
     if (new_deadline.IsInitialized()) {
       new_deadline = std::min(new_deadline, idle_network_deadline);
     } else {
@@ -106,4 +105,11 @@
   alarm_->Update(new_deadline, kAlarmGranularity);
 }
 
+QuicTime QuicIdleNetworkDetector::GetIdleNetworkDeadline() const {
+  if (idle_network_timeout_.IsInfinite()) {
+    return QuicTime::Zero();
+  }
+  return last_network_activity_time() + idle_network_timeout_;
+}
+
 }  // namespace quic
diff --git a/quic/core/quic_idle_network_detector.h b/quic/core/quic_idle_network_detector.h
index 83beb29..1537222 100644
--- a/quic/core/quic_idle_network_detector.h
+++ b/quic/core/quic_idle_network_detector.h
@@ -67,6 +67,8 @@
 
   QuicTime::Delta idle_network_timeout() const { return idle_network_timeout_; }
 
+  QuicTime GetIdleNetworkDeadline() const;
+
  private:
   friend class test::QuicConnectionPeer;
   friend class test::QuicIdleNetworkDetectorTestPeer;
diff --git a/quic/core/quic_sent_packet_manager.cc b/quic/core/quic_sent_packet_manager.cc
index ae536a2..1a97417 100644
--- a/quic/core/quic_sent_packet_manager.cc
+++ b/quic/core/quic_sent_packet_manager.cc
@@ -1576,5 +1576,12 @@
   return handshake_finished_ || handshake_packet_acked_;
 }
 
+bool QuicSentPacketManager::IsLessThanThreePTOs(QuicTime::Delta timeout) const {
+  const QuicTime::Delta retransmission_delay =
+      pto_enabled_ ? GetProbeTimeoutDelay(APPLICATION_DATA)
+                   : GetRetransmissionDelay();
+  return timeout < 3 * retransmission_delay;
+}
+
 #undef ENDPOINT  // undef for jumbo builds
 }  // namespace quic
diff --git a/quic/core/quic_sent_packet_manager.h b/quic/core/quic_sent_packet_manager.h
index 4e6cab1..e2af3e2 100644
--- a/quic/core/quic_sent_packet_manager.h
+++ b/quic/core/quic_sent_packet_manager.h
@@ -408,6 +408,9 @@
   // Called to retransmit in flight packet of |space| if any.
   void RetransmitDataOfSpaceIfAny(PacketNumberSpace space);
 
+  // Returns true if |timeout| is less than 3 * RTO/PTO delay.
+  bool IsLessThanThreePTOs(QuicTime::Delta timeout) const;
+
   bool supports_multiple_packet_number_spaces() const {
     return unacked_packets_.supports_multiple_packet_number_spaces();
   }
diff --git a/quic/core/quic_session.cc b/quic/core/quic_session.cc
index 99bbb0e..694d76e 100644
--- a/quic/core/quic_session.cc
+++ b/quic/core/quic_session.cc
@@ -99,6 +99,7 @@
       is_configured_(false),
       enable_round_robin_scheduling_(false),
       was_zero_rtt_rejected_(false),
+      liveness_testing_in_progress_(false),
       fix_gquic_stream_type_(GetQuicReloadableFlag(quic_fix_gquic_stream_type)),
       remove_streams_waiting_for_acks_(
           GetQuicReloadableFlag(quic_remove_streams_waiting_for_acks)),
@@ -264,6 +265,10 @@
 
 void QuicSession::OnPacketDecrypted(EncryptionLevel level) {
   GetMutableCryptoStream()->OnPacketDecrypted(level);
+  if (liveness_testing_in_progress_) {
+    liveness_testing_in_progress_ = false;
+    OnCanCreateNewOutgoingStream(/*unidirectional=*/false);
+  }
 }
 
 void QuicSession::OnOneRttPacketAcknowledged() {
@@ -1746,19 +1751,33 @@
 }
 
 bool QuicSession::CanOpenNextOutgoingBidirectionalStream() {
+  if (liveness_testing_in_progress_) {
+    DCHECK_EQ(Perspective::IS_CLIENT, perspective());
+    return false;
+  }
   if (!VersionHasIetfQuicFrames(transport_version())) {
-    return stream_id_manager_.CanOpenNextOutgoingStream();
+    if (!stream_id_manager_.CanOpenNextOutgoingStream()) {
+      return false;
+    }
+  } else {
+    if (!v99_streamid_manager_.CanOpenNextOutgoingBidirectionalStream()) {
+      if (is_configured_) {
+        // Send STREAM_BLOCKED after config negotiated.
+        control_frame_manager_.WriteOrBufferStreamsBlocked(
+            v99_streamid_manager_.max_outgoing_bidirectional_streams(),
+            /*unidirectional=*/false);
+      }
+      return false;
+    }
   }
-  if (v99_streamid_manager_.CanOpenNextOutgoingBidirectionalStream()) {
-    return true;
+  if (perspective() == Perspective::IS_CLIENT &&
+      connection_->MaybeTestLiveness()) {
+    // Now is relatively close to the idle timeout having the risk that requests
+    // could be discarded at the server.
+    liveness_testing_in_progress_ = true;
+    return false;
   }
-  if (is_configured_) {
-    // Send STREAM_BLOCKED after config negotiated.
-    control_frame_manager_.WriteOrBufferStreamsBlocked(
-        v99_streamid_manager_.max_outgoing_bidirectional_streams(),
-        /*unidirectional=*/false);
-  }
-  return false;
+  return true;
 }
 
 bool QuicSession::CanOpenNextOutgoingUnidirectionalStream() {
diff --git a/quic/core/quic_session.h b/quic/core/quic_session.h
index 09ce2fd..b15bb3a 100644
--- a/quic/core/quic_session.h
+++ b/quic/core/quic_session.h
@@ -845,6 +845,10 @@
   // Whether the session has received a 0-RTT rejection (QUIC+TLS only).
   bool was_zero_rtt_rejected_;
 
+  // This indicates a liveness testing is in progress, and push back the
+  // creation of new outgoing bidirectional streams.
+  bool liveness_testing_in_progress_;
+
   // Latched value of flag quic_fix_gquic_stream_type.
   const bool fix_gquic_stream_type_;
 
diff --git a/quic/core/quic_session_test.cc b/quic/core/quic_session_test.cc
index 2d8cb6e..a033573 100644
--- a/quic/core/quic_session_test.cc
+++ b/quic/core/quic_session_test.cc
@@ -2215,6 +2215,28 @@
             kDefaultMinAckDelayTimeMs);
 }
 
+TEST_P(QuicSessionTestClient, FailedToCreateStreamIfTooCloseToIdleTimeout) {
+  connection_->SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
+  EXPECT_TRUE(session_.CanOpenNextOutgoingBidirectionalStream());
+  QuicTime deadline = QuicConnectionPeer::GetIdleNetworkDeadline(connection_);
+  ASSERT_TRUE(deadline.IsInitialized());
+  QuicTime::Delta timeout = deadline - helper_.GetClock()->ApproximateNow();
+  // Advance time to very close idle timeout.
+  connection_->AdvanceTime(timeout - QuicTime::Delta::FromMilliseconds(1));
+  // Verify creation of new stream gets pushed back and connectivity probing
+  // packet gets sent.
+  EXPECT_CALL(*connection_, SendConnectivityProbingPacket(_, _)).Times(1);
+  EXPECT_FALSE(session_.CanOpenNextOutgoingBidirectionalStream());
+
+  // New packet gets received, idle deadline gets extended.
+  EXPECT_CALL(session_, OnCanCreateNewOutgoingStream(false));
+  QuicConnectionPeer::GetIdleNetworkDetector(connection_)
+      .OnPacketReceived(helper_.GetClock()->ApproximateNow());
+  session_.OnPacketDecrypted(ENCRYPTION_FORWARD_SECURE);
+
+  EXPECT_TRUE(session_.CanOpenNextOutgoingBidirectionalStream());
+}
+
 TEST_P(QuicSessionTestServer, ZombieStreams) {
   TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
   QuicStreamPeer::SetStreamBytesWritten(3, stream2);
diff --git a/quic/test_tools/quic_connection_peer.cc b/quic/test_tools/quic_connection_peer.cc
index 5fb31a5..6f9c8c6 100644
--- a/quic/test_tools/quic_connection_peer.cc
+++ b/quic/test_tools/quic_connection_peer.cc
@@ -362,12 +362,24 @@
 }
 
 // static
+QuicTime QuicConnectionPeer::GetIdleNetworkDeadline(
+    QuicConnection* connection) {
+  return connection->idle_network_detector_.GetIdleNetworkDeadline();
+}
+
+// static
 QuicAlarm* QuicConnectionPeer::GetIdleNetworkDetectorAlarm(
     QuicConnection* connection) {
   return connection->idle_network_detector_.alarm_.get();
 }
 
 // static
+QuicIdleNetworkDetector& QuicConnectionPeer::GetIdleNetworkDetector(
+    QuicConnection* connection) {
+  return connection->idle_network_detector_;
+}
+
+// static
 void QuicConnectionPeer::SetServerConnectionId(
     QuicConnection* connection,
     const QuicConnectionId& server_connection_id) {
diff --git a/quic/test_tools/quic_connection_peer.h b/quic/test_tools/quic_connection_peer.h
index 8f3a78c..01d16f2 100644
--- a/quic/test_tools/quic_connection_peer.h
+++ b/quic/test_tools/quic_connection_peer.h
@@ -146,6 +146,11 @@
 
   static QuicAlarm* GetIdleNetworkDetectorAlarm(QuicConnection* connection);
 
+  static QuicTime GetIdleNetworkDeadline(QuicConnection* connection);
+
+  static QuicIdleNetworkDetector& GetIdleNetworkDetector(
+      QuicConnection* connection);
+
   static void SetServerConnectionId(
       QuicConnection* connection,
       const QuicConnectionId& server_connection_id);