Add a memory reduction timeout to QuicIdleNetworkDetector.

Note:
This knob is useful for trimming memory usage for long running idle connections.
PiperOrigin-RevId: 877949375
diff --git a/quiche/quic/core/quic_connection.cc b/quiche/quic/core/quic_connection.cc
index 27499a8..23e96cf 100644
--- a/quiche/quic/core/quic_connection.cc
+++ b/quiche/quic/core/quic_connection.cc
@@ -6496,6 +6496,10 @@
                   idle_timeout_connection_close_behavior_);
 }
 
+void QuicConnection::OnMemoryReductionTimeout() {
+  sent_packet_manager_.ReduceMemoryUsage();
+}
+
 void QuicConnection::OnKeepAliveTimeout() {
   if (retransmission_alarm().IsSet() ||
       !visitor_->ShouldKeepConnectionAlive()) {
diff --git a/quiche/quic/core/quic_connection.h b/quiche/quic/core/quic_connection.h
index 3a46762..4decc34 100644
--- a/quiche/quic/core/quic_connection.h
+++ b/quiche/quic/core/quic_connection.h
@@ -836,6 +836,7 @@
   // QuicIdleNetworkDetector::Delegate
   void OnHandshakeTimeout() override;
   void OnIdleNetworkDetected() override;
+  void OnMemoryReductionTimeout() override;
 
   // QuicPingManager::Delegate
   void OnKeepAliveTimeout() override;
@@ -935,6 +936,12 @@
   // Sets the handshake and idle state connection timeouts.
   void SetNetworkTimeouts(QuicTime::Delta handshake_timeout,
                           QuicTime::Delta idle_timeout);
+  // Trim memory usage when network has been idle for
+  // `memory_reduction_timeout`. The timeout only takes effect after handshake
+  // completes.
+  void SetMemoryReductionTimeout(QuicTime::Delta memory_reduction_timeout) {
+    idle_network_detector_.SetMemoryReductionTimeout(memory_reduction_timeout);
+  }
 
   void SetMultiPortProbingInterval(QuicTime::Delta probing_interval) {
     multi_port_probing_interval_ = probing_interval;
diff --git a/quiche/quic/core/quic_idle_network_detector.cc b/quiche/quic/core/quic_idle_network_detector.cc
index f469089..aa58458 100644
--- a/quiche/quic/core/quic_idle_network_detector.cc
+++ b/quiche/quic/core/quic_idle_network_detector.cc
@@ -13,10 +13,6 @@
 
 namespace quic {
 
-namespace {
-
-}  // namespace
-
 QuicIdleNetworkDetector::QuicIdleNetworkDetector(Delegate* delegate,
                                                  QuicTime now,
                                                  QuicAlarmProxy alarm)
@@ -30,7 +26,14 @@
 
 void QuicIdleNetworkDetector::OnAlarm() {
   if (handshake_timeout_.IsInfinite()) {
-    delegate_->OnIdleNetworkDetected();
+    if (last_alarm_type_ != AlarmType::kMemoryReductionTimeout) {
+      delegate_->OnIdleNetworkDetected();
+    } else {
+      QUICHE_DCHECK(last_alarm_type_ == AlarmType::kMemoryReductionTimeout);
+      delegate_->OnMemoryReductionTimeout();
+      // Rearms the alarm to idle network deadline.
+      UpdateAlarm(AlarmType::kIdleNetworkTimeout, GetIdleNetworkDeadline());
+    }
     return;
   }
   if (idle_network_timeout_.IsInfinite()) {
@@ -57,6 +60,7 @@
   alarm_.PermanentCancel();
   handshake_timeout_ = QuicTime::Delta::Infinite();
   idle_network_timeout_ = QuicTime::Delta::Infinite();
+  memory_reduction_timeout_ = QuicTime::Delta::Infinite();
   last_alarm_type_ = AlarmType::kUnknown;
   stopped_ = true;
 }
@@ -83,6 +87,26 @@
   SetAlarm();
 }
 
+bool QuicIdleNetworkDetector::ShouldMemoryReductionTimeoutBeUsed() const {
+  if (shorter_idle_timeout_on_sent_packet_) {
+    // No benefit to consider memory reduction if shorter idle timeout on sent
+    // packet is enabled.
+    return false;
+  }
+  if (!handshake_timeout_.IsInfinite()) {
+    // No benefit to consider memory reduction if handshake has not completed.
+    return false;
+  }
+  if (memory_reduction_timeout_ >= idle_network_timeout_ ||
+      (idle_network_timeout_ - memory_reduction_timeout_ <
+       QuicTime::Delta::FromSeconds(60))) {
+    // No benefit to consider memory reduction if memory reduction timeout is
+    // too close to idle network timeout.
+    return false;
+  }
+  return true;
+}
+
 void QuicIdleNetworkDetector::SetAlarm() {
   if (stopped_) {
     // TODO(wub): If this QUIC_BUG fires, it indicates a problem in the
@@ -110,6 +134,11 @@
       alarm_type = AlarmType::kIdleNetworkTimeout;
     }
   }
+  if (!memory_reduction_timeout_.IsInfinite() &&
+      ShouldMemoryReductionTimeoutBeUsed()) {
+    alarm_type = AlarmType::kMemoryReductionTimeout;
+    new_deadline = last_network_activity_time() + memory_reduction_timeout_;
+  }
   UpdateAlarm(alarm_type, new_deadline);
 }
 
diff --git a/quiche/quic/core/quic_idle_network_detector.h b/quiche/quic/core/quic_idle_network_detector.h
index a4d17dc..9f76051 100644
--- a/quiche/quic/core/quic_idle_network_detector.h
+++ b/quiche/quic/core/quic_idle_network_detector.h
@@ -38,6 +38,10 @@
 
     // Called when idle network has been detected.
     virtual void OnIdleNetworkDetected() = 0;
+
+    // Called when network has been idle for a while such that per-connection
+    // memory usage can be reduced.
+    virtual void OnMemoryReductionTimeout() = 0;
   };
 
   QuicIdleNetworkDetector(Delegate* delegate, QuicTime now,
@@ -49,6 +53,12 @@
   void SetTimeouts(QuicTime::Delta handshake_timeout,
                    QuicTime::Delta idle_network_timeout);
 
+  // Called to set memory_reduction_timeout_, which may be used in SetAlarm to
+  // schedule a memory reduction callback.
+  void SetMemoryReductionTimeout(QuicTime::Delta timeout) {
+    memory_reduction_timeout_ = timeout;
+  }
+
   // Stop the detection once and for all.
   void StopDetection();
 
@@ -84,6 +94,7 @@
   enum class AlarmType : uint8_t {
     kHandshakeTimeout,
     kIdleNetworkTimeout,
+    kMemoryReductionTimeout,
     kPtoDelay,
     kUnknown,
   };
@@ -97,7 +108,8 @@
 
   void MaybeSetAlarmOnSentPacket(QuicTime::Delta pto_delay);
 
-  QuicTime GetBandwidthUpdateDeadline() const;
+  // Whether memory_reduction_timeout_ should be used when setting the alarm.
+  bool ShouldMemoryReductionTimeoutBeUsed() const;
 
   Delegate* delegate_;  // Not owned.
 
@@ -121,6 +133,10 @@
   // Idle network timeout. Infinite means no idle network timeout.
   QuicTime::Delta idle_network_timeout_;
 
+  // Timeout for scheulduling a memory reduction callback when network has
+  // been idle for a while. Infinite means no timeout.
+  QuicTime::Delta memory_reduction_timeout_ = QuicTime::Delta::Infinite();
+
   QuicAlarmProxy alarm_;
 
   AlarmType last_alarm_type_ = AlarmType::kUnknown;
diff --git a/quiche/quic/core/quic_idle_network_detector_test.cc b/quiche/quic/core/quic_idle_network_detector_test.cc
index 8896693..063cb77 100644
--- a/quiche/quic/core/quic_idle_network_detector_test.cc
+++ b/quiche/quic/core/quic_idle_network_detector_test.cc
@@ -30,6 +30,7 @@
  public:
   MOCK_METHOD(void, OnHandshakeTimeout, (), (override));
   MOCK_METHOD(void, OnIdleNetworkDetected, (), (override));
+  MOCK_METHOD(void, OnMemoryReductionTimeout, (), (override));
 };
 
 class QuicIdleNetworkDetectorTest : public QuicTest {
@@ -203,6 +204,82 @@
   EXPECT_FALSE(alarm_->IsSet());
 }
 
+TEST_F(QuicIdleNetworkDetectorTest, MemoryReductionTimeout) {
+  detector_.SetMemoryReductionTimeout(QuicTime::Delta::FromSeconds(200));
+  // Memory reduction timeout is only set along with idle network timeout alarm.
+  EXPECT_FALSE(alarm_->IsSet());
+
+  detector_.SetTimeouts(
+      /*handshake_timeout=*/QuicTime::Delta::FromSeconds(30),
+      /*idle_network_timeout=*/QuicTime::Delta::FromSeconds(20));
+  EXPECT_TRUE(alarm_->IsSet());
+  EXPECT_EQ(clock_.Now() + QuicTime::Delta::FromSeconds(20),
+            alarm_->deadline());
+
+  // Handshake completes in 200ms.
+  clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(200));
+  detector_.OnPacketReceived(clock_.Now());
+  detector_.SetTimeouts(
+      /*handshake_timeout=*/QuicTime::Delta::Infinite(),
+      QuicTime::Delta::FromSeconds(600));
+  // Verify memory reduction alarm is set.
+  EXPECT_EQ(clock_.Now() + QuicTime::Delta::FromSeconds(200),
+            alarm_->deadline());
+
+  // Fires the memory reduction alarm.
+  clock_.AdvanceTime(QuicTime::Delta::FromSeconds(200));
+  EXPECT_CALL(delegate_, OnMemoryReductionTimeout());
+  alarm_->Fire();
+  // Verifies that idle timeout alarm is set to tigger in (600s - 200s) = 400s.
+  EXPECT_EQ(clock_.Now() + QuicTime::Delta::FromSeconds(400),
+            alarm_->deadline());
+
+  // Fires the idle network alarm.
+  clock_.AdvanceTime(QuicTime::Delta::FromSeconds(400));
+  EXPECT_CALL(delegate_, OnIdleNetworkDetected());
+  alarm_->Fire();
+}
+
+TEST_F(QuicIdleNetworkDetectorTest,
+       MemoryReductionTimeoutTooCloseToIdleTimeoutIgnored) {
+  detector_.SetMemoryReductionTimeout(QuicTime::Delta::FromSeconds(590));
+  // Handshake completes.
+  detector_.OnPacketReceived(clock_.Now());
+  detector_.SetTimeouts(
+      /*handshake_timeout=*/QuicTime::Delta::Infinite(),
+      QuicTime::Delta::FromSeconds(600));
+  // Verify memory reduction alarm is NOT set.
+  EXPECT_EQ(clock_.Now() + QuicTime::Delta::FromSeconds(600),
+            alarm_->deadline());
+}
+
+TEST_F(QuicIdleNetworkDetectorTest,
+       MemoryReductionTimeoutIgnoredBeforeHandshakeCompletes) {
+  detector_.SetMemoryReductionTimeout(QuicTime::Delta::FromSeconds(5));
+  // Handshake not yet completed.
+  detector_.OnPacketReceived(clock_.Now());
+  detector_.SetTimeouts(QuicTime::Delta::FromSeconds(30),
+                        QuicTime::Delta::FromSeconds(200));
+  // Verify memory reduction alarm is NOT set.
+  EXPECT_EQ(clock_.Now() + QuicTime::Delta::FromSeconds(30),
+            alarm_->deadline());
+}
+
+TEST_F(QuicIdleNetworkDetectorTest,
+       MemoryReductionTimeoutDisabledByShorterIdleTimeoutOnSentPacket) {
+  detector_.SetMemoryReductionTimeout(QuicTime::Delta::FromSeconds(200));
+  detector_.enable_shorter_idle_timeout_on_sent_packet();
+
+  // Handshake completes.
+  detector_.OnPacketReceived(clock_.Now());
+  detector_.SetTimeouts(
+      /*handshake_timeout=*/QuicTime::Delta::Infinite(),
+      QuicTime::Delta::FromSeconds(600));
+  // Verify memory reduction alarm is NOT set.
+  EXPECT_EQ(clock_.Now() + QuicTime::Delta::FromSeconds(600),
+            alarm_->deadline());
+}
+
 }  // namespace
 
 }  // namespace test