Add QuicPingManager to manage the PING alarm. Protected by FLAGS_quic_reloadable_flag_quic_use_ping_manager. PiperOrigin-RevId: 442893828
diff --git a/quiche/quic/core/quic_connection.cc b/quiche/quic/core/quic_connection.cc index 44a7830..82b07cc 100644 --- a/quiche/quic/core/quic_connection.cc +++ b/quiche/quic/core/quic_connection.cc
@@ -128,6 +128,7 @@ void OnAlarm() override { QUICHE_DCHECK(connection_->connected()); + QUICHE_DCHECK(!GetQuicReloadableFlag(quic_use_ping_manager)); connection_->OnPingTimeout(); } }; @@ -327,7 +328,8 @@ alarm_factory_, &context_), path_validator_(alarm_factory_, &arena_, this, random_generator_, &context_), - most_recent_frame_type_(NUM_FRAME_TYPES) { + most_recent_frame_type_(NUM_FRAME_TYPES), + ping_manager_(perspective, this, &arena_, alarm_factory_, &context_) { QUICHE_DCHECK(perspective_ == Perspective::IS_CLIENT || default_path_.self_address.IsInitialized()); @@ -1350,7 +1352,11 @@ MaybeUpdateAckTimeout(); visitor_->OnStreamFrame(frame); stats_.stream_bytes_received += frame.data_length; - consecutive_retransmittable_on_wire_ping_count_ = 0; + if (use_ping_manager_) { + ping_manager_.reset_consecutive_retransmittable_on_wire_count(); + } else { + consecutive_retransmittable_on_wire_ping_count_ = 0; + } return connected_; } @@ -4053,6 +4059,7 @@ } void QuicConnection::OnPingTimeout() { + QUICHE_DCHECK(!use_ping_manager_); if (retransmission_alarm_->IsSet() || !visitor_->ShouldKeepConnectionAlive()) { return; @@ -4621,7 +4628,11 @@ QUIC_DVLOG(1) << "Cancelling all QuicConnection alarms."; ack_alarm_->PermanentCancel(); - ping_alarm_->PermanentCancel(); + if (use_ping_manager_) { + ping_manager_.Stop(); + } else { + ping_alarm_->PermanentCancel(); + } retransmission_alarm_->PermanentCancel(); send_alarm_->PermanentCancel(); mtu_discovery_alarm_->PermanentCancel(); @@ -4665,6 +4676,13 @@ if (!connected_) { return; } + if (use_ping_manager_) { + QUIC_RELOADABLE_FLAG_COUNT(quic_use_ping_manager); + ping_manager_.SetAlarm(clock_->ApproximateNow(), + visitor_->ShouldKeepConnectionAlive(), + sent_packet_manager_.HasInFlightPackets()); + return; + } if (perspective_ == Perspective::IS_SERVER && initial_retransmittable_on_wire_timeout_.IsInfinite()) { // The PING alarm exists to support two features: @@ -6280,6 +6298,24 @@ idle_timeout_connection_close_behavior_); } +void QuicConnection::OnKeepAliveTimeout() { + QUICHE_DCHECK(use_ping_manager_); + if (retransmission_alarm_->IsSet() || + !visitor_->ShouldKeepConnectionAlive()) { + return; + } + SendPingAtLevel(framer().GetEncryptionLevelToSendApplicationData()); +} + +void QuicConnection::OnRetransmittableOnWireTimeout() { + QUICHE_DCHECK(use_ping_manager_); + if (retransmission_alarm_->IsSet() || + !visitor_->ShouldKeepConnectionAlive()) { + return; + } + SendPingAtLevel(framer().GetEncryptionLevelToSendApplicationData()); +} + void QuicConnection::OnPeerIssuedConnectionIdRetired() { QUICHE_DCHECK(peer_issued_cid_manager_ != nullptr); QuicConnectionId* default_path_cid = @@ -7107,5 +7143,26 @@ return old_send_algorithm; } +void QuicConnection::set_keep_alive_ping_timeout( + QuicTime::Delta keep_alive_ping_timeout) { + if (use_ping_manager_) { + ping_manager_.set_keep_alive_timeout(keep_alive_ping_timeout); + return; + } + QUICHE_DCHECK(!ping_alarm_->IsSet()); + keep_alive_ping_timeout_ = keep_alive_ping_timeout; +} + +void QuicConnection::set_initial_retransmittable_on_wire_timeout( + QuicTime::Delta retransmittable_on_wire_timeout) { + if (use_ping_manager_) { + ping_manager_.set_initial_retransmittable_on_wire_timeout( + retransmittable_on_wire_timeout); + return; + } + QUICHE_DCHECK(!ping_alarm_->IsSet()); + initial_retransmittable_on_wire_timeout_ = retransmittable_on_wire_timeout; +} + #undef ENDPOINT // undef for jumbo builds } // namespace quic
diff --git a/quiche/quic/core/quic_connection.h b/quiche/quic/core/quic_connection.h index 6aca00b..9c93dd4 100644 --- a/quiche/quic/core/quic_connection.h +++ b/quiche/quic/core/quic_connection.h
@@ -50,6 +50,7 @@ #include "quiche/quic/core/quic_packet_writer.h" #include "quiche/quic/core/quic_packets.h" #include "quiche/quic/core/quic_path_validator.h" +#include "quiche/quic/core/quic_ping_manager.h" #include "quiche/quic/core/quic_sent_packet_manager.h" #include "quiche/quic/core/quic_time.h" #include "quiche/quic/core/quic_types.h" @@ -458,7 +459,8 @@ public QuicNetworkBlackholeDetector::Delegate, public QuicIdleNetworkDetector::Delegate, public QuicPathValidator::SendDelegate, - public QuicConnectionIdManagerVisitorInterface { + public QuicConnectionIdManagerVisitorInterface, + public QuicPingManager::Delegate { public: // Constructs a new QuicConnection for |connection_id| and // |initial_peer_address| using |writer| to write packets. |owns_writer| @@ -710,6 +712,10 @@ void OnHandshakeTimeout() override; void OnIdleNetworkDetected() override; + // QuicPingManager::Delegate + void OnKeepAliveTimeout() override; + void OnRetransmittableOnWireTimeout() override; + // QuicConnectionIdManagerVisitorInterface void OnPeerIssuedConnectionIdRetired() override; bool SendNewConnectionId(const QuicNewConnectionIdFrame& frame) override; @@ -741,17 +747,11 @@ } // Used in Chromium, but not internally. // Must only be called before ping_alarm_ is set. - void set_keep_alive_ping_timeout(QuicTime::Delta keep_alive_ping_timeout) { - QUICHE_DCHECK(!ping_alarm_->IsSet()); - keep_alive_ping_timeout_ = keep_alive_ping_timeout; - } + void set_keep_alive_ping_timeout(QuicTime::Delta keep_alive_ping_timeout); // Sets an initial timeout for the ping alarm when there is no retransmittable // data in flight, allowing for a more aggressive ping alarm in that case. void set_initial_retransmittable_on_wire_timeout( - QuicTime::Delta retransmittable_on_wire_timeout) { - QUICHE_DCHECK(!ping_alarm_->IsSet()); - initial_retransmittable_on_wire_timeout_ = retransmittable_on_wire_timeout; - } + QuicTime::Delta retransmittable_on_wire_timeout); // Used in Chromium, but not internally. void set_creator_debug_delegate(QuicPacketCreator::DebugDelegate* visitor) { packet_creator_.set_debug_delegate(visitor); @@ -1976,6 +1976,8 @@ // SendAlarm. bool defer_send_in_response_to_packets_; + // TODO(fayang): remove PING related fields below when deprecating + // quic_use_ping_manager. // The timeout for keep-alive PING. QuicTime::Delta keep_alive_ping_timeout_; @@ -1999,6 +2001,7 @@ // An alarm that is scheduled when the SentPacketManager requires a delay // before sending packets and fires when the packet may be sent. QuicArenaScopedPtr<QuicAlarm> send_alarm_; + // TODO(fayang): remove ping_alarm_ when deprecating quic_use_ping_manager. // An alarm that fires when a ping should be sent. QuicArenaScopedPtr<QuicAlarm> ping_alarm_; // An alarm that fires when an MTU probe should be sent. @@ -2247,6 +2250,10 @@ // If true, send connection close packet on INVALID_VERSION. bool send_connection_close_for_invalid_version_ = false; + const bool use_ping_manager_ = GetQuicReloadableFlag(quic_use_ping_manager); + + QuicPingManager ping_manager_; + // TODO(b/205023946) Debug-only fields, to be deprecated after the bug is // fixed. absl::optional<QuicWallTime> quic_bug_10511_43_timestamp_;
diff --git a/quiche/quic/core/quic_flags_list.h b/quiche/quic/core/quic_flags_list.h index 085f9d3..bfc2462 100644 --- a/quiche/quic/core/quic_flags_list.h +++ b/quiche/quic/core/quic_flags_list.h
@@ -93,6 +93,8 @@ QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_update_ack_timeout_on_receipt_time, true) // If true, use BBRv2 as the default congestion controller. Takes precedence over --quic_default_to_bbr. QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_default_to_bbr_v2, false) +// If true, use PING manager to manage the PING alarm. +QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_use_ping_manager, true) // If true, use new connection ID in connection migration. QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_connection_migration_use_new_cid_v2, true) // If true, uses conservative cwnd gain and pacing gain when cwnd gets bootstrapped.
diff --git a/quiche/quic/core/quic_one_block_arena.h b/quiche/quic/core/quic_one_block_arena.h index 4bebebe..05d335b 100644 --- a/quiche/quic/core/quic_one_block_arena.h +++ b/quiche/quic/core/quic_one_block_arena.h
@@ -69,7 +69,7 @@ // QuicConnections currently use around 1KB of polymorphic types which would // ordinarily be on the heap. Instead, store them inline in an arena. -using QuicConnectionArena = QuicOneBlockArena<1152>; +using QuicConnectionArena = QuicOneBlockArena<1248>; } // namespace quic
diff --git a/quiche/quic/core/quic_ping_manager.cc b/quiche/quic/core/quic_ping_manager.cc new file mode 100644 index 0000000..7ce158d --- /dev/null +++ b/quiche/quic/core/quic_ping_manager.cc
@@ -0,0 +1,158 @@ +// Copyright (c) 2022 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/quic_ping_manager.h" + +namespace quic { + +namespace { + +class AlarmDelegate : public QuicAlarm::DelegateWithContext { + public: + explicit AlarmDelegate(QuicPingManager* manager, + QuicConnectionContext* context) + : QuicAlarm::DelegateWithContext(context), manager_(manager) {} + AlarmDelegate(const AlarmDelegate&) = delete; + AlarmDelegate& operator=(const AlarmDelegate&) = delete; + + void OnAlarm() override { manager_->OnAlarm(); } + + private: + QuicPingManager* manager_; +}; + +} // namespace + +QuicPingManager::QuicPingManager(Perspective perspective, Delegate* delegate, + QuicConnectionArena* arena, + QuicAlarmFactory* alarm_factory, + QuicConnectionContext* context) + : perspective_(perspective), + delegate_(delegate), + alarm_(alarm_factory->CreateAlarm( + arena->New<AlarmDelegate>(this, context), arena)) {} + +void QuicPingManager::SetAlarm(QuicTime now, bool should_keep_alive, + bool has_in_flight_packets) { + UpdateDeadlines(now, should_keep_alive, has_in_flight_packets); + const QuicTime earliest_deadline = GetEarliestDeadline(); + if (!earliest_deadline.IsInitialized()) { + alarm_->Cancel(); + return; + } + if (earliest_deadline == keep_alive_deadline_) { + // Use 1s granularity for keep-alive time. + alarm_->Update(earliest_deadline, QuicTime::Delta::FromSeconds(1)); + return; + } + alarm_->Update(earliest_deadline, kAlarmGranularity); + if (GetQuicFlag( + FLAGS_quic_max_aggressive_retransmittable_on_wire_ping_count) != 0) { + ++consecutive_retransmittable_on_wire_count_; + } + ++retransmittable_on_wire_count_; +} + +void QuicPingManager::OnAlarm() { + const QuicTime earliest_deadline = GetEarliestDeadline(); + if (!earliest_deadline.IsInitialized()) { + QUIC_BUG(quic_ping_manager_alarm_fires_unexpectedly) + << "QuicPingManager alarm fires unexpectedly."; + return; + } + // Please note, alarm does not get re-armed here, and we are relying on caller + // to SetAlarm later. + if (earliest_deadline == retransmittable_on_wire_deadline_) { + retransmittable_on_wire_deadline_ = QuicTime::Zero(); + delegate_->OnRetransmittableOnWireTimeout(); + return; + } + if (earliest_deadline == keep_alive_deadline_) { + keep_alive_deadline_ = QuicTime::Zero(); + delegate_->OnKeepAliveTimeout(); + } +} + +void QuicPingManager::Stop() { + alarm_->PermanentCancel(); + retransmittable_on_wire_deadline_ = QuicTime::Zero(); + keep_alive_deadline_ = QuicTime::Zero(); +} + +void QuicPingManager::UpdateDeadlines(QuicTime now, bool should_keep_alive, + bool has_in_flight_packets) { + // Reset keep-alive deadline given it will be set later (with left edge + // |now|). + keep_alive_deadline_ = QuicTime::Zero(); + if (perspective_ == Perspective::IS_SERVER && + initial_retransmittable_on_wire_timeout_.IsInfinite()) { + // The PING alarm exists to support two features: + // 1) clients send PINGs every 15s to prevent NAT timeouts, + // 2) both clients and servers can send retransmittable on the wire PINGs + // (ROWP) while ShouldKeepConnectionAlive is true and there is no packets in + // flight. + QUICHE_DCHECK(!retransmittable_on_wire_deadline_.IsInitialized()); + return; + } + if (!should_keep_alive) { + // Don't send a ping unless the application (ie: HTTP/3) says to, usually + // because it is expecting a response from the peer. + retransmittable_on_wire_deadline_ = QuicTime::Zero(); + return; + } + if (perspective_ == Perspective::IS_CLIENT) { + // Clients send 15s PINGs to avoid NATs from timing out. + keep_alive_deadline_ = now + keep_alive_timeout_; + } + if (initial_retransmittable_on_wire_timeout_.IsInfinite() || + has_in_flight_packets || + retransmittable_on_wire_count_ > + GetQuicFlag(FLAGS_quic_max_retransmittable_on_wire_ping_count)) { + // No need to set retransmittable-on-wire timeout. + retransmittable_on_wire_deadline_ = QuicTime::Zero(); + return; + } + + QUICHE_DCHECK_LT(initial_retransmittable_on_wire_timeout_, + keep_alive_timeout_); + QuicTime::Delta retransmittable_on_wire_timeout = + initial_retransmittable_on_wire_timeout_; + const int max_aggressive_retransmittable_on_wire_count = + GetQuicFlag(FLAGS_quic_max_aggressive_retransmittable_on_wire_ping_count); + QUICHE_DCHECK_LE(0, max_aggressive_retransmittable_on_wire_count); + if (consecutive_retransmittable_on_wire_count_ > + max_aggressive_retransmittable_on_wire_count) { + // Exponentially back off the timeout if the number of consecutive + // retransmittable on wire pings has exceeds the allowance. + int shift = consecutive_retransmittable_on_wire_count_ - + max_aggressive_retransmittable_on_wire_count; + retransmittable_on_wire_timeout = + initial_retransmittable_on_wire_timeout_ * (1 << shift); + } + if (retransmittable_on_wire_deadline_.IsInitialized() && + retransmittable_on_wire_deadline_ < + now + retransmittable_on_wire_timeout) { + // Alarm is set to an earlier time. Do not postpone it. + QUIC_BUG_IF(quic_retransmittable_on_wire_deadline_is_in_past, + retransmittable_on_wire_deadline_ < now) + << "QUIC retransmittable PING deadline is in past"; + return; + } + retransmittable_on_wire_deadline_ = now + retransmittable_on_wire_timeout; +} + +QuicTime QuicPingManager::GetEarliestDeadline() const { + QuicTime earliest_deadline = QuicTime::Zero(); + for (QuicTime t : {retransmittable_on_wire_deadline_, keep_alive_deadline_}) { + if (!t.IsInitialized()) { + continue; + } + if (!earliest_deadline.IsInitialized() || t < earliest_deadline) { + earliest_deadline = t; + } + } + return earliest_deadline; +} + +} // namespace quic
diff --git a/quiche/quic/core/quic_ping_manager.h b/quiche/quic/core/quic_ping_manager.h new file mode 100644 index 0000000..d88dac2 --- /dev/null +++ b/quiche/quic/core/quic_ping_manager.h
@@ -0,0 +1,108 @@ +// Copyright (c) 2022 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_QUIC_CORE_QUIC_PING_MANAGER_H_ +#define QUICHE_QUIC_CORE_QUIC_PING_MANAGER_H_ + +#include "quiche/quic/core/quic_alarm.h" +#include "quiche/quic/core/quic_alarm_factory.h" +#include "quiche/quic/core/quic_constants.h" +#include "quiche/quic/core/quic_one_block_arena.h" +#include "quiche/quic/core/quic_time.h" +#include "quiche/quic/platform/api/quic_export.h" + +namespace quic { + +namespace test { +class QuicConnectionPeer; +class QuicPingManagerPeer; +} // namespace test + +// QuicPingManager manages an alarm that has two modes: +// 1) keep-alive. When alarm fires, send packet to extend idle timeout to keep +// connection alive. +// 2) retransmittable-on-wire. When alarm fires, send packets to detect path +// degrading (used in IP/port migrations). +class QUIC_EXPORT_PRIVATE QuicPingManager { + public: + // Interface that get notified when |alarm_| fires. + class QUIC_EXPORT_PRIVATE Delegate { + public: + virtual ~Delegate() {} + + // Called when alarm fires in keep-alive mode. + virtual void OnKeepAliveTimeout() = 0; + // Called when alarm fires in retransmittable-on-wire mode. + virtual void OnRetransmittableOnWireTimeout() = 0; + }; + + QuicPingManager(Perspective perspective, Delegate* delegate, + QuicConnectionArena* arena, QuicAlarmFactory* alarm_factory, + QuicConnectionContext* context); + + // Called to set |alarm_|. + void SetAlarm(QuicTime now, bool should_keep_alive, + bool has_in_flight_packets); + + // Called when |alarm_| fires. + void OnAlarm(); + + // Called to stop |alarm_| permanently. + void Stop(); + + void set_keep_alive_timeout(QuicTime::Delta keep_alive_timeout) { + QUICHE_DCHECK(!alarm_->IsSet()); + keep_alive_timeout_ = keep_alive_timeout; + } + + void set_initial_retransmittable_on_wire_timeout( + QuicTime::Delta retransmittable_on_wire_timeout) { + QUICHE_DCHECK(!alarm_->IsSet()); + initial_retransmittable_on_wire_timeout_ = retransmittable_on_wire_timeout; + } + + void reset_consecutive_retransmittable_on_wire_count() { + consecutive_retransmittable_on_wire_count_ = 0; + } + + private: + friend class test::QuicConnectionPeer; + friend class test::QuicPingManagerPeer; + + // Update |retransmittable_on_wire_deadline_| and |keep_alive_deadline_|. + void UpdateDeadlines(QuicTime now, bool should_keep_alive, + bool has_in_flight_packets); + + // Get earliest deadline of |retransmittable_on_wire_deadline_| and + // |keep_alive_deadline_|. Returns 0 if both deadlines are not initialized. + QuicTime GetEarliestDeadline() const; + + Perspective perspective_; + + Delegate* delegate_; // Not owned. + + // Initial timeout for how long the wire can have no retransmittable packets. + QuicTime::Delta initial_retransmittable_on_wire_timeout_ = + QuicTime::Delta::Infinite(); + + // Indicates how many consecutive retransmittable-on-wire has been armed + // (since last reset). + int consecutive_retransmittable_on_wire_count_ = 0; + + // Indicates how many retransmittable-on-wire has been armed in total. + int retransmittable_on_wire_count_ = 0; + + QuicTime::Delta keep_alive_timeout_ = + QuicTime::Delta::FromSeconds(kPingTimeoutSecs); + + QuicTime retransmittable_on_wire_deadline_ = QuicTime::Zero(); + + QuicTime keep_alive_deadline_ = QuicTime::Zero(); + + QuicArenaScopedPtr<QuicAlarm> alarm_; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_QUIC_PING_MANAGER_H_
diff --git a/quiche/quic/core/quic_ping_manager_test.cc b/quiche/quic/core/quic_ping_manager_test.cc new file mode 100644 index 0000000..e7b4b78 --- /dev/null +++ b/quiche/quic/core/quic_ping_manager_test.cc
@@ -0,0 +1,381 @@ +// Copyright (c) 2022 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "quiche/quic/core/quic_ping_manager.h" + +#include "quiche/quic/core/quic_one_block_arena.h" +#include "quiche/quic/platform/api/quic_test.h" +#include "quiche/quic/test_tools/quic_test_utils.h" + +namespace quic { +namespace test { + +class QuicPingManagerPeer { + public: + static QuicAlarm* GetAlarm(QuicPingManager* manager) { + return manager->alarm_.get(); + } +}; + +namespace { + +const bool kShouldKeepAlive = true; +const bool kHasInflightPackets = true; + +class MockDelegate : public QuicPingManager::Delegate { + public: + MOCK_METHOD(void, OnKeepAliveTimeout, (), (override)); + MOCK_METHOD(void, OnRetransmittableOnWireTimeout, (), (override)); +}; + +class QuicPingManagerTest : public QuicTest { + public: + QuicPingManagerTest() + : manager_(Perspective::IS_CLIENT, &delegate_, &arena_, &alarm_factory_, + /*context=*/nullptr), + alarm_(static_cast<MockAlarmFactory::TestAlarm*>( + QuicPingManagerPeer::GetAlarm(&manager_))) { + clock_.AdvanceTime(QuicTime::Delta::FromSeconds(1)); + } + + protected: + testing::StrictMock<MockDelegate> delegate_; + MockClock clock_; + QuicConnectionArena arena_; + MockAlarmFactory alarm_factory_; + QuicPingManager manager_; + MockAlarmFactory::TestAlarm* alarm_; +}; + +TEST_F(QuicPingManagerTest, KeepAliveTimeout) { + EXPECT_FALSE(alarm_->IsSet()); + + clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); + // Set alarm with in flight packets. + manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, + kHasInflightPackets); + EXPECT_TRUE(alarm_->IsSet()); + EXPECT_EQ(QuicTime::Delta::FromSeconds(kPingTimeoutSecs), + alarm_->deadline() - clock_.ApproximateNow()); + + clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); + // Reset alarm with no in flight packets. + manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, + !kHasInflightPackets); + EXPECT_TRUE(alarm_->IsSet()); + // Verify the deadline is set slightly less than 15 seconds in the future, + // because of the 1s alarm granularity. + EXPECT_EQ(QuicTime::Delta::FromSeconds(kPingTimeoutSecs) - + QuicTime::Delta::FromMilliseconds(5), + alarm_->deadline() - clock_.ApproximateNow()); + + clock_.AdvanceTime(QuicTime::Delta::FromSeconds(kPingTimeoutSecs)); + EXPECT_CALL(delegate_, OnKeepAliveTimeout()); + alarm_->Fire(); + EXPECT_FALSE(alarm_->IsSet()); + // Reset alarm with in flight packets. + manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, + kHasInflightPackets); + EXPECT_TRUE(alarm_->IsSet()); + + // Verify alarm is not armed if !kShouldKeepAlive. + clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); + manager_.SetAlarm(clock_.ApproximateNow(), !kShouldKeepAlive, + kHasInflightPackets); + EXPECT_FALSE(alarm_->IsSet()); +} + +TEST_F(QuicPingManagerTest, CustomizedKeepAliveTimeout) { + EXPECT_FALSE(alarm_->IsSet()); + + // Set customized keep-alive timeout. + manager_.set_keep_alive_timeout(QuicTime::Delta::FromSeconds(10)); + + clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); + // Set alarm with in flight packets. + manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, + kHasInflightPackets); + EXPECT_TRUE(alarm_->IsSet()); + EXPECT_EQ(QuicTime::Delta::FromSeconds(10), + alarm_->deadline() - clock_.ApproximateNow()); + + clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); + // Set alarm with no in flight packets. + manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, + !kHasInflightPackets); + EXPECT_TRUE(alarm_->IsSet()); + // The deadline is set slightly less than 10 seconds in the future, because + // of the 1s alarm granularity. + EXPECT_EQ( + QuicTime::Delta::FromSeconds(10) - QuicTime::Delta::FromMilliseconds(5), + alarm_->deadline() - clock_.ApproximateNow()); + + clock_.AdvanceTime(QuicTime::Delta::FromSeconds(10)); + EXPECT_CALL(delegate_, OnKeepAliveTimeout()); + alarm_->Fire(); + EXPECT_FALSE(alarm_->IsSet()); + // Reset alarm with in flight packets. + manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, + kHasInflightPackets); + EXPECT_TRUE(alarm_->IsSet()); + + // Verify alarm is not armed if !kShouldKeepAlive. + clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); + manager_.SetAlarm(clock_.ApproximateNow(), !kShouldKeepAlive, + kHasInflightPackets); + EXPECT_FALSE(alarm_->IsSet()); +} + +TEST_F(QuicPingManagerTest, RetransmittableOnWireTimeout) { + const QuicTime::Delta kRtransmittableOnWireTimeout = + QuicTime::Delta::FromMilliseconds(50); + manager_.set_initial_retransmittable_on_wire_timeout( + kRtransmittableOnWireTimeout); + + EXPECT_FALSE(alarm_->IsSet()); + + // Set alarm with in flight packets. + manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, + kHasInflightPackets); + // Verify alarm is in keep-alive mode. + EXPECT_TRUE(alarm_->IsSet()); + EXPECT_EQ(QuicTime::Delta::FromSeconds(kPingTimeoutSecs), + alarm_->deadline() - clock_.ApproximateNow()); + + clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); + // Set alarm with no in flight packets. + manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, + !kHasInflightPackets); + EXPECT_TRUE(alarm_->IsSet()); + // Verify alarm is in retransmittable-on-wire mode. + EXPECT_EQ(kRtransmittableOnWireTimeout, + alarm_->deadline() - clock_.ApproximateNow()); + + clock_.AdvanceTime(kRtransmittableOnWireTimeout); + EXPECT_CALL(delegate_, OnRetransmittableOnWireTimeout()); + alarm_->Fire(); + EXPECT_FALSE(alarm_->IsSet()); + // Reset alarm with in flight packets. + manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, + kHasInflightPackets); + // Verify the alarm is in keep-alive mode. + ASSERT_TRUE(alarm_->IsSet()); + EXPECT_EQ(QuicTime::Delta::FromSeconds(kPingTimeoutSecs), + alarm_->deadline() - clock_.ApproximateNow()); +} + +TEST_F(QuicPingManagerTest, RetransmittableOnWireTimeoutExponentiallyBackOff) { + const int kMaxAggressiveRetransmittableOnWireCount = 5; + SetQuicFlag(FLAGS_quic_max_aggressive_retransmittable_on_wire_ping_count, + kMaxAggressiveRetransmittableOnWireCount); + const QuicTime::Delta initial_retransmittable_on_wire_timeout = + QuicTime::Delta::FromMilliseconds(200); + manager_.set_initial_retransmittable_on_wire_timeout( + initial_retransmittable_on_wire_timeout); + + clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); + EXPECT_FALSE(alarm_->IsSet()); + manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, + kHasInflightPackets); + // Verify alarm is in keep-alive mode. + EXPECT_TRUE(alarm_->IsSet()); + EXPECT_EQ(QuicTime::Delta::FromSeconds(kPingTimeoutSecs), + alarm_->deadline() - clock_.ApproximateNow()); + + // Verify no exponential backoff on the first few retransmittable on wire + // timeouts. + for (int i = 0; i <= kMaxAggressiveRetransmittableOnWireCount; ++i) { + clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); + // Reset alarm with no in flight packets. + manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, + !kHasInflightPackets); + EXPECT_TRUE(alarm_->IsSet()); + // Verify alarm is in retransmittable-on-wire mode. + EXPECT_EQ(initial_retransmittable_on_wire_timeout, + alarm_->deadline() - clock_.ApproximateNow()); + clock_.AdvanceTime(initial_retransmittable_on_wire_timeout); + EXPECT_CALL(delegate_, OnRetransmittableOnWireTimeout()); + alarm_->Fire(); + EXPECT_FALSE(alarm_->IsSet()); + // Reset alarm with in flight packets. + manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, + kHasInflightPackets); + } + + QuicTime::Delta retransmittable_on_wire_timeout = + initial_retransmittable_on_wire_timeout; + + // Verify subsequent retransmittable-on-wire timeout is exponentially backed + // off. + while (retransmittable_on_wire_timeout * 2 < + QuicTime::Delta::FromSeconds(kPingTimeoutSecs)) { + retransmittable_on_wire_timeout = retransmittable_on_wire_timeout * 2; + clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); + manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, + !kHasInflightPackets); + EXPECT_TRUE(alarm_->IsSet()); + EXPECT_EQ(retransmittable_on_wire_timeout, + alarm_->deadline() - clock_.ApproximateNow()); + + clock_.AdvanceTime(retransmittable_on_wire_timeout); + EXPECT_CALL(delegate_, OnRetransmittableOnWireTimeout()); + alarm_->Fire(); + EXPECT_FALSE(alarm_->IsSet()); + // Reset alarm with in flight packets. + manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, + kHasInflightPackets); + } + + // Verify alarm is in keep-alive mode. + EXPECT_TRUE(alarm_->IsSet()); + EXPECT_EQ(QuicTime::Delta::FromSeconds(kPingTimeoutSecs), + alarm_->deadline() - clock_.ApproximateNow()); + + clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); + // Reset alarm with no in flight packets + manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, + !kHasInflightPackets); + EXPECT_TRUE(alarm_->IsSet()); + // Verify alarm is in keep-alive mode because retransmittable-on-wire deadline + // is later. + EXPECT_EQ(QuicTime::Delta::FromSeconds(kPingTimeoutSecs) - + QuicTime::Delta::FromMilliseconds(5), + alarm_->deadline() - clock_.ApproximateNow()); + clock_.AdvanceTime(QuicTime::Delta::FromSeconds(kPingTimeoutSecs) - + QuicTime::Delta::FromMilliseconds(5)); + EXPECT_CALL(delegate_, OnKeepAliveTimeout()); + alarm_->Fire(); + EXPECT_FALSE(alarm_->IsSet()); +} + +TEST_F(QuicPingManagerTest, + ResetRetransmitableOnWireTimeoutExponentiallyBackOff) { + const int kMaxAggressiveRetransmittableOnWireCount = 3; + SetQuicFlag(FLAGS_quic_max_aggressive_retransmittable_on_wire_ping_count, + kMaxAggressiveRetransmittableOnWireCount); + const QuicTime::Delta initial_retransmittable_on_wire_timeout = + QuicTime::Delta::FromMilliseconds(200); + manager_.set_initial_retransmittable_on_wire_timeout( + initial_retransmittable_on_wire_timeout); + + clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); + EXPECT_FALSE(alarm_->IsSet()); + manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, + kHasInflightPackets); + // Verify alarm is in keep-alive mode. + EXPECT_TRUE(alarm_->IsSet()); + EXPECT_EQ(QuicTime::Delta::FromSeconds(kPingTimeoutSecs), + alarm_->deadline() - clock_.ApproximateNow()); + + clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); + manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, + !kHasInflightPackets); + EXPECT_TRUE(alarm_->IsSet()); + // Verify alarm is in retransmittable-on-wire mode. + EXPECT_EQ(initial_retransmittable_on_wire_timeout, + alarm_->deadline() - clock_.ApproximateNow()); + + EXPECT_CALL(delegate_, OnRetransmittableOnWireTimeout()); + clock_.AdvanceTime(initial_retransmittable_on_wire_timeout); + alarm_->Fire(); + + clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); + manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, + !kHasInflightPackets); + EXPECT_TRUE(alarm_->IsSet()); + EXPECT_EQ(initial_retransmittable_on_wire_timeout, + alarm_->deadline() - clock_.ApproximateNow()); + + manager_.reset_consecutive_retransmittable_on_wire_count(); + manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, + !kHasInflightPackets); + EXPECT_EQ(initial_retransmittable_on_wire_timeout, + alarm_->deadline() - clock_.ApproximateNow()); + + for (int i = 0; i < kMaxAggressiveRetransmittableOnWireCount; i++) { + manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, + !kHasInflightPackets); + EXPECT_TRUE(alarm_->IsSet()); + EXPECT_EQ(initial_retransmittable_on_wire_timeout, + alarm_->deadline() - clock_.ApproximateNow()); + clock_.AdvanceTime(initial_retransmittable_on_wire_timeout); + EXPECT_CALL(delegate_, OnRetransmittableOnWireTimeout()); + alarm_->Fire(); + // Reset alarm with in flight packets. + manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, + kHasInflightPackets); + // Advance 5ms to receive next packet. + clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); + } + + manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, + !kHasInflightPackets); + EXPECT_TRUE(alarm_->IsSet()); + EXPECT_EQ(initial_retransmittable_on_wire_timeout * 2, + alarm_->deadline() - clock_.ApproximateNow()); + + clock_.AdvanceTime(2 * initial_retransmittable_on_wire_timeout); + EXPECT_CALL(delegate_, OnRetransmittableOnWireTimeout()); + alarm_->Fire(); + + clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); + manager_.reset_consecutive_retransmittable_on_wire_count(); + manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, + !kHasInflightPackets); + EXPECT_TRUE(alarm_->IsSet()); + EXPECT_EQ(initial_retransmittable_on_wire_timeout, + alarm_->deadline() - clock_.ApproximateNow()); +} + +TEST_F(QuicPingManagerTest, RetransmittableOnWireLimit) { + static constexpr int kMaxRetransmittableOnWirePingCount = 3; + SetQuicFlag(FLAGS_quic_max_retransmittable_on_wire_ping_count, + kMaxRetransmittableOnWirePingCount); + static constexpr QuicTime::Delta initial_retransmittable_on_wire_timeout = + QuicTime::Delta::FromMilliseconds(200); + static constexpr QuicTime::Delta kShortDelay = + QuicTime::Delta::FromMilliseconds(5); + ASSERT_LT(kShortDelay * 10, initial_retransmittable_on_wire_timeout); + manager_.set_initial_retransmittable_on_wire_timeout( + initial_retransmittable_on_wire_timeout); + + clock_.AdvanceTime(kShortDelay); + EXPECT_FALSE(alarm_->IsSet()); + manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, + kHasInflightPackets); + + EXPECT_TRUE(alarm_->IsSet()); + EXPECT_EQ(QuicTime::Delta::FromSeconds(kPingTimeoutSecs), + alarm_->deadline() - clock_.ApproximateNow()); + + for (int i = 0; i <= kMaxRetransmittableOnWirePingCount; i++) { + clock_.AdvanceTime(kShortDelay); + manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, + !kHasInflightPackets); + EXPECT_TRUE(alarm_->IsSet()); + EXPECT_EQ(initial_retransmittable_on_wire_timeout, + alarm_->deadline() - clock_.ApproximateNow()); + clock_.AdvanceTime(initial_retransmittable_on_wire_timeout); + EXPECT_CALL(delegate_, OnRetransmittableOnWireTimeout()); + alarm_->Fire(); + manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, + kHasInflightPackets); + } + + manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, + !kHasInflightPackets); + EXPECT_TRUE(alarm_->IsSet()); + // Verify alarm is in keep-alive mode. + EXPECT_EQ(QuicTime::Delta::FromSeconds(kPingTimeoutSecs), + alarm_->deadline() - clock_.ApproximateNow()); + clock_.AdvanceTime(QuicTime::Delta::FromSeconds(kPingTimeoutSecs)); + EXPECT_CALL(delegate_, OnKeepAliveTimeout()); + alarm_->Fire(); + EXPECT_FALSE(alarm_->IsSet()); +} + +} // namespace + +} // namespace test +} // namespace quic
diff --git a/quiche/quic/test_tools/quic_connection_peer.cc b/quiche/quic/test_tools/quic_connection_peer.cc index e124580..9eaa6fb 100644 --- a/quiche/quic/test_tools/quic_connection_peer.cc +++ b/quiche/quic/test_tools/quic_connection_peer.cc
@@ -64,6 +64,7 @@ Perspective perspective) { connection->perspective_ = perspective; QuicFramerPeer::SetPerspective(&connection->framer_, perspective); + connection->ping_manager_.perspective_ = perspective; } // static @@ -128,6 +129,9 @@ // static QuicAlarm* QuicConnectionPeer::GetPingAlarm(QuicConnection* connection) { + if (GetQuicReloadableFlag(quic_use_ping_manager)) { + return connection->ping_manager_.alarm_.get(); + } return connection->ping_alarm_.get(); }