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