Add experimental logic for RetransmittableOnWireTimeout This change allows RetransmittableOnWireTimeout be set based on a multiple of PTO. This new functionality is enabled only on client side and behind three tags (ROW1, ROW2, and ROW3), where: - ROW1 will make the timeout value be equal to 1PTO - ROW2 will make the timeout value be equal to 2PTO - ROW3 will make the timeout value be equal to 3PTO. This new logic is not enabled by default. Protected by experimental quic connection options. PiperOrigin-RevId: 775783151
diff --git a/quiche/quic/core/crypto/crypto_protocol.h b/quiche/quic/core/crypto/crypto_protocol.h index 9056c47..9d1dbe2 100644 --- a/quiche/quic/core/crypto/crypto_protocol.h +++ b/quiche/quic/core/crypto/crypto_protocol.h
@@ -445,6 +445,13 @@ // ROWP timeout. DEFINE_STATIC_QUIC_TAG(ROWR); // Send random bytes on ROWP // timeout. + +// Retransmittable on wire timeout experiment. +// TODO: b/427246911 - Remove these tags once the experiment is complete. +DEFINE_STATIC_QUIC_TAG(ROW1); // Set retransmittable on wire timeout to 1*PTO. +DEFINE_STATIC_QUIC_TAG(ROW2); // Set retransmittable on wire timeout to 2*PTO. +DEFINE_STATIC_QUIC_TAG(ROW3); // Set retransmittable on wire timeout to 3*PTO. + // Selective Resumption variants. DEFINE_STATIC_QUIC_TAG(GSR0); DEFINE_STATIC_QUIC_TAG(GSR1);
diff --git a/quiche/quic/core/quic_connection.cc b/quiche/quic/core/quic_connection.cc index 1e6279e..fe95424 100644 --- a/quiche/quic/core/quic_connection.cc +++ b/quiche/quic/core/quic_connection.cc
@@ -549,6 +549,20 @@ retransmittable_on_wire_behavior_ = SEND_RANDOM_BYTES; } } + + // Set retransmittable-on-wire timeout to different PTO based values. + if (perspective_ == Perspective::IS_CLIENT && version().HasIetfQuicFrames()) { + if (config.HasClientRequestedIndependentOption(kROW1, perspective_)) { + ping_manager_.set_num_ptos_for_retransmittable_on_wire_timeout(1); + } + if (config.HasClientRequestedIndependentOption(kROW2, perspective_)) { + ping_manager_.set_num_ptos_for_retransmittable_on_wire_timeout(2); + } + if (config.HasClientRequestedIndependentOption(kROW3, perspective_)) { + ping_manager_.set_num_ptos_for_retransmittable_on_wire_timeout(3); + } + } + if (config.HasClientRequestedIndependentOption(k3AFF, perspective_)) { anti_amplification_factor_ = 3; } @@ -4892,7 +4906,8 @@ } ping_manager_.SetAlarm(clock_->ApproximateNow(), visitor_->ShouldKeepConnectionAlive(), - sent_packet_manager_.HasInFlightPackets()); + sent_packet_manager_.HasInFlightPackets(), + sent_packet_manager_.GetPtoDelay()); } void QuicConnection::SetRetransmissionAlarm() { @@ -6580,9 +6595,9 @@ if (connected_) { // Always reset PING alarm with has_in_flight_packets=true. This is used // to avoid re-arming the alarm in retransmittable-on-wire mode. - ping_manager_.SetAlarm(clock_->ApproximateNow(), - visitor_->ShouldKeepConnectionAlive(), - /*has_in_flight_packets=*/true); + ping_manager_.SetAlarm( + clock_->ApproximateNow(), visitor_->ShouldKeepConnectionAlive(), + /*has_in_flight_packets=*/true, sent_packet_manager_.GetPtoDelay()); } return; }
diff --git a/quiche/quic/core/quic_connection_test.cc b/quiche/quic/core/quic_connection_test.cc index c0251ab..74060c0 100644 --- a/quiche/quic/core/quic_connection_test.cc +++ b/quiche/quic/core/quic_connection_test.cc
@@ -8587,6 +8587,40 @@ connection_.GetPingAlarm()->deadline() - clock_.ApproximateNow()); } +// Make sure when enabled, the retransmittable on wire timeout is based on the +// PTO. +TEST_P(QuicConnectionTest, PtoBasedRetransmittableOnWireTimeout) { + if (!VersionHasIetfQuicFrames(connection_.version().transport_version)) { + return; + } + + EXPECT_CALL(*send_algorithm_, EnableECT1()).WillRepeatedly(Return(false)); + EXPECT_CALL(*send_algorithm_, EnableECT0()).WillRepeatedly(Return(false)); + + // Enable the retransmittable on wire timeout for 3 different PTOs. + struct TestCase { + QuicTag timeout_tag; + uint8_t expected_pto_count; + }; + static constexpr TestCase kTestCases[] = { + {kROW1, 1}, + {kROW2, 2}, + {kROW3, 3}, + }; + + for (const auto& test_case : kTestCases) { + QuicConfig config; + QuicTagVector connection_options; + EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)); + connection_options.push_back(test_case.timeout_tag); + config.SetClientConnectionOptions(connection_options); + connection_.SetFromConfig(config); + EXPECT_EQ(QuicConnectionPeer::GetNumPtosForRetransmittableOnWireTimeout( + &connection_), + test_case.expected_pto_count); + } +} + TEST_P(QuicConnectionTest, ValidStatelessResetToken) { const StatelessResetToken kTestToken{0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1};
diff --git a/quiche/quic/core/quic_ping_manager.cc b/quiche/quic/core/quic_ping_manager.cc index 533dd10..52a5cd4 100644 --- a/quiche/quic/core/quic_ping_manager.cc +++ b/quiche/quic/core/quic_ping_manager.cc
@@ -25,8 +25,9 @@ : perspective_(perspective), delegate_(delegate), alarm_(alarm) {} void QuicPingManager::SetAlarm(QuicTime now, bool should_keep_alive, - bool has_in_flight_packets) { - UpdateDeadlines(now, should_keep_alive, has_in_flight_packets); + bool has_in_flight_packets, + QuicTime::Delta pto_delay) { + UpdateDeadlines(now, should_keep_alive, has_in_flight_packets, pto_delay); const QuicTime earliest_deadline = GetEarliestDeadline(); if (!earliest_deadline.IsInitialized()) { alarm_.Cancel(); @@ -72,7 +73,8 @@ } void QuicPingManager::UpdateDeadlines(QuicTime now, bool should_keep_alive, - bool has_in_flight_packets) { + bool has_in_flight_packets, + QuicTime::Delta pto_delay) { // Reset keep-alive deadline given it will be set later (with left edge // |now|). keep_alive_deadline_ = QuicTime::Zero(); @@ -96,7 +98,8 @@ // Clients send 15s PINGs to avoid NATs from timing out. keep_alive_deadline_ = now + keep_alive_timeout_; } - if (initial_retransmittable_on_wire_timeout_.IsInfinite() || + if ((num_ptos_for_retransmittable_on_wire_timeout_ == 0 && + initial_retransmittable_on_wire_timeout_.IsInfinite()) || has_in_flight_packets || retransmittable_on_wire_count_ > GetQuicFlag(quic_max_retransmittable_on_wire_ping_count)) { @@ -105,10 +108,18 @@ return; } - QUICHE_DCHECK_LT(initial_retransmittable_on_wire_timeout_, - keep_alive_timeout_); - QuicTime::Delta retransmittable_on_wire_timeout = - initial_retransmittable_on_wire_timeout_; + QuicTime::Delta retransmittable_on_wire_timeout = QuicTime::Delta::Zero(); + if (num_ptos_for_retransmittable_on_wire_timeout_ > 0) { + QUICHE_DCHECK_NE(pto_delay, QuicTime::Delta::Zero()); + retransmittable_on_wire_timeout = + static_cast<int>(num_ptos_for_retransmittable_on_wire_timeout_) * + pto_delay; + } else { + QUICHE_DCHECK_LT(initial_retransmittable_on_wire_timeout_, + keep_alive_timeout_); + retransmittable_on_wire_timeout = initial_retransmittable_on_wire_timeout_; + } + const int max_aggressive_retransmittable_on_wire_count = GetQuicFlag(quic_max_aggressive_retransmittable_on_wire_ping_count); QUICHE_DCHECK_LE(0, max_aggressive_retransmittable_on_wire_count); @@ -120,8 +131,9 @@ max_aggressive_retransmittable_on_wire_count, kMaxRetransmittableOnWireDelayShift); retransmittable_on_wire_timeout = - initial_retransmittable_on_wire_timeout_ * (1 << shift); + retransmittable_on_wire_timeout * (1 << shift); } + if (retransmittable_on_wire_deadline_.IsInitialized() && retransmittable_on_wire_deadline_ < now + retransmittable_on_wire_timeout) {
diff --git a/quiche/quic/core/quic_ping_manager.h b/quiche/quic/core/quic_ping_manager.h index 3250416..4cf9810 100644 --- a/quiche/quic/core/quic_ping_manager.h +++ b/quiche/quic/core/quic_ping_manager.h
@@ -5,6 +5,8 @@ #ifndef QUICHE_QUIC_CORE_QUIC_PING_MANAGER_H_ #define QUICHE_QUIC_CORE_QUIC_PING_MANAGER_H_ +#include <cstdint> + #include "quiche/quic/core/quic_alarm.h" #include "quiche/quic/core/quic_alarm_factory.h" #include "quiche/quic/core/quic_connection_alarms.h" @@ -43,7 +45,7 @@ // Called to set |alarm_|. void SetAlarm(QuicTime now, bool should_keep_alive, - bool has_in_flight_packets); + bool has_in_flight_packets, QuicTime::Delta pto_delay); // Called when |alarm_| fires. void OnAlarm(); @@ -66,13 +68,19 @@ consecutive_retransmittable_on_wire_count_ = 0; } + void set_num_ptos_for_retransmittable_on_wire_timeout( + uint8_t num_ptos_for_retransmittable_on_wire_timeout) { + num_ptos_for_retransmittable_on_wire_timeout_ = + num_ptos_for_retransmittable_on_wire_timeout; + } + 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); + bool has_in_flight_packets, QuicTime::Delta pto_delay); // Get earliest deadline of |retransmittable_on_wire_deadline_| and // |keep_alive_deadline_|. Returns 0 if both deadlines are not initialized. @@ -101,6 +109,8 @@ QuicTime keep_alive_deadline_ = QuicTime::Zero(); QuicAlarmProxy alarm_; + + uint8_t num_ptos_for_retransmittable_on_wire_timeout_ = 0; }; } // namespace quic
diff --git a/quiche/quic/core/quic_ping_manager_test.cc b/quiche/quic/core/quic_ping_manager_test.cc index e2a3155..0ab3331 100644 --- a/quiche/quic/core/quic_ping_manager_test.cc +++ b/quiche/quic/core/quic_ping_manager_test.cc
@@ -26,6 +26,7 @@ const bool kShouldKeepAlive = true; const bool kHasInflightPackets = true; +const QuicTime::Delta kPtoDelay = QuicTime::Delta::FromMilliseconds(50); class MockDelegate : public QuicPingManager::Delegate { public: @@ -62,7 +63,7 @@ clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); // Set alarm with in flight packets. manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, - kHasInflightPackets); + kHasInflightPackets, kPtoDelay); EXPECT_TRUE(alarm_->IsSet()); EXPECT_EQ(QuicTime::Delta::FromSeconds(kPingTimeoutSecs), alarm_->deadline() - clock_.ApproximateNow()); @@ -70,7 +71,7 @@ clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); // Reset alarm with no in flight packets. manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, - !kHasInflightPackets); + !kHasInflightPackets, kPtoDelay); EXPECT_TRUE(alarm_->IsSet()); // Verify the deadline is set slightly less than 15 seconds in the future, // because of the 1s alarm granularity. @@ -84,13 +85,13 @@ EXPECT_FALSE(alarm_->IsSet()); // Reset alarm with in flight packets. manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, - kHasInflightPackets); + kHasInflightPackets, kPtoDelay); EXPECT_TRUE(alarm_->IsSet()); // Verify alarm is not armed if !kShouldKeepAlive. clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); manager_.SetAlarm(clock_.ApproximateNow(), !kShouldKeepAlive, - kHasInflightPackets); + kHasInflightPackets, kPtoDelay); EXPECT_FALSE(alarm_->IsSet()); } @@ -103,7 +104,7 @@ clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); // Set alarm with in flight packets. manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, - kHasInflightPackets); + kHasInflightPackets, kPtoDelay); EXPECT_TRUE(alarm_->IsSet()); EXPECT_EQ(QuicTime::Delta::FromSeconds(10), alarm_->deadline() - clock_.ApproximateNow()); @@ -111,7 +112,7 @@ clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); // Set alarm with no in flight packets. manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, - !kHasInflightPackets); + !kHasInflightPackets, kPtoDelay); EXPECT_TRUE(alarm_->IsSet()); // The deadline is set slightly less than 10 seconds in the future, because // of the 1s alarm granularity. @@ -125,13 +126,13 @@ EXPECT_FALSE(alarm_->IsSet()); // Reset alarm with in flight packets. manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, - kHasInflightPackets); + kHasInflightPackets, kPtoDelay); EXPECT_TRUE(alarm_->IsSet()); // Verify alarm is not armed if !kShouldKeepAlive. clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); manager_.SetAlarm(clock_.ApproximateNow(), !kShouldKeepAlive, - kHasInflightPackets); + kHasInflightPackets, kPtoDelay); EXPECT_FALSE(alarm_->IsSet()); } @@ -145,7 +146,7 @@ // Set alarm with in flight packets. manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, - kHasInflightPackets); + kHasInflightPackets, kPtoDelay); // Verify alarm is in keep-alive mode. EXPECT_TRUE(alarm_->IsSet()); EXPECT_EQ(QuicTime::Delta::FromSeconds(kPingTimeoutSecs), @@ -154,7 +155,7 @@ clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); // Set alarm with no in flight packets. manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, - !kHasInflightPackets); + !kHasInflightPackets, kPtoDelay); EXPECT_TRUE(alarm_->IsSet()); // Verify alarm is in retransmittable-on-wire mode. EXPECT_EQ(kRtransmittableOnWireTimeout, @@ -166,7 +167,7 @@ EXPECT_FALSE(alarm_->IsSet()); // Reset alarm with in flight packets. manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, - kHasInflightPackets); + kHasInflightPackets, kPtoDelay); // Verify the alarm is in keep-alive mode. ASSERT_TRUE(alarm_->IsSet()); EXPECT_EQ(QuicTime::Delta::FromSeconds(kPingTimeoutSecs), @@ -185,7 +186,7 @@ clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); EXPECT_FALSE(alarm_->IsSet()); manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, - kHasInflightPackets); + kHasInflightPackets, kPtoDelay); // Verify alarm is in keep-alive mode. EXPECT_TRUE(alarm_->IsSet()); EXPECT_EQ(QuicTime::Delta::FromSeconds(kPingTimeoutSecs), @@ -197,7 +198,7 @@ clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); // Reset alarm with no in flight packets. manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, - !kHasInflightPackets); + !kHasInflightPackets, kPtoDelay); EXPECT_TRUE(alarm_->IsSet()); // Verify alarm is in retransmittable-on-wire mode. EXPECT_EQ(initial_retransmittable_on_wire_timeout, @@ -208,7 +209,7 @@ EXPECT_FALSE(alarm_->IsSet()); // Reset alarm with in flight packets. manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, - kHasInflightPackets); + kHasInflightPackets, kPtoDelay); } QuicTime::Delta retransmittable_on_wire_timeout = @@ -221,7 +222,7 @@ retransmittable_on_wire_timeout = retransmittable_on_wire_timeout * 2; clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, - !kHasInflightPackets); + !kHasInflightPackets, kPtoDelay); EXPECT_TRUE(alarm_->IsSet()); EXPECT_EQ(retransmittable_on_wire_timeout, alarm_->deadline() - clock_.ApproximateNow()); @@ -232,7 +233,7 @@ EXPECT_FALSE(alarm_->IsSet()); // Reset alarm with in flight packets. manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, - kHasInflightPackets); + kHasInflightPackets, kPtoDelay); } // Verify alarm is in keep-alive mode. @@ -243,7 +244,7 @@ clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); // Reset alarm with no in flight packets manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, - !kHasInflightPackets); + !kHasInflightPackets, kPtoDelay); EXPECT_TRUE(alarm_->IsSet()); // Verify alarm is in keep-alive mode because retransmittable-on-wire deadline // is later. @@ -270,7 +271,7 @@ clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); EXPECT_FALSE(alarm_->IsSet()); manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, - kHasInflightPackets); + kHasInflightPackets, kPtoDelay); // Verify alarm is in keep-alive mode. EXPECT_TRUE(alarm_->IsSet()); EXPECT_EQ(QuicTime::Delta::FromSeconds(kPingTimeoutSecs), @@ -278,7 +279,7 @@ clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, - !kHasInflightPackets); + !kHasInflightPackets, kPtoDelay); EXPECT_TRUE(alarm_->IsSet()); // Verify alarm is in retransmittable-on-wire mode. EXPECT_EQ(initial_retransmittable_on_wire_timeout, @@ -290,14 +291,14 @@ clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, - !kHasInflightPackets); + !kHasInflightPackets, kPtoDelay); 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); + !kHasInflightPackets, kPtoDelay); EXPECT_EQ(initial_retransmittable_on_wire_timeout, alarm_->deadline() - clock_.ApproximateNow()); EXPECT_CALL(delegate_, OnRetransmittableOnWireTimeout()); @@ -306,7 +307,7 @@ for (int i = 0; i < kMaxAggressiveRetransmittableOnWireCount; i++) { manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, - !kHasInflightPackets); + !kHasInflightPackets, kPtoDelay); EXPECT_TRUE(alarm_->IsSet()); EXPECT_EQ(initial_retransmittable_on_wire_timeout, alarm_->deadline() - clock_.ApproximateNow()); @@ -315,13 +316,13 @@ alarm_->Fire(); // Reset alarm with in flight packets. manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, - kHasInflightPackets); + kHasInflightPackets, kPtoDelay); // Advance 5ms to receive next packet. clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); } manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, - !kHasInflightPackets); + !kHasInflightPackets, kPtoDelay); EXPECT_TRUE(alarm_->IsSet()); EXPECT_EQ(initial_retransmittable_on_wire_timeout * 2, alarm_->deadline() - clock_.ApproximateNow()); @@ -333,7 +334,7 @@ clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); manager_.reset_consecutive_retransmittable_on_wire_count(); manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, - !kHasInflightPackets); + !kHasInflightPackets, kPtoDelay); EXPECT_TRUE(alarm_->IsSet()); EXPECT_EQ(initial_retransmittable_on_wire_timeout, alarm_->deadline() - clock_.ApproximateNow()); @@ -354,7 +355,7 @@ clock_.AdvanceTime(kShortDelay); EXPECT_FALSE(alarm_->IsSet()); manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, - kHasInflightPackets); + kHasInflightPackets, kPtoDelay); EXPECT_TRUE(alarm_->IsSet()); EXPECT_EQ(QuicTime::Delta::FromSeconds(kPingTimeoutSecs), @@ -363,7 +364,7 @@ for (int i = 0; i <= kMaxRetransmittableOnWirePingCount; i++) { clock_.AdvanceTime(kShortDelay); manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, - !kHasInflightPackets); + !kHasInflightPackets, kPtoDelay); EXPECT_TRUE(alarm_->IsSet()); EXPECT_EQ(initial_retransmittable_on_wire_timeout, alarm_->deadline() - clock_.ApproximateNow()); @@ -371,11 +372,11 @@ EXPECT_CALL(delegate_, OnRetransmittableOnWireTimeout()); alarm_->Fire(); manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, - kHasInflightPackets); + kHasInflightPackets, kPtoDelay); } manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, - !kHasInflightPackets); + !kHasInflightPackets, kPtoDelay); EXPECT_TRUE(alarm_->IsSet()); // Verify alarm is in keep-alive mode. EXPECT_EQ(QuicTime::Delta::FromSeconds(kPingTimeoutSecs), @@ -398,7 +399,7 @@ for (int i = 0; i <= kMaxAggressiveRetransmittableOnWireCount; i++) { manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, - !kHasInflightPackets); + !kHasInflightPackets, kPtoDelay); EXPECT_TRUE(alarm_->IsSet()); EXPECT_EQ(initial_retransmittable_on_wire_timeout, alarm_->deadline() - clock_.ApproximateNow()); @@ -406,11 +407,11 @@ EXPECT_CALL(delegate_, OnRetransmittableOnWireTimeout()); alarm_->Fire(); manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, - kHasInflightPackets); + kHasInflightPackets, kPtoDelay); } for (int i = 1; i <= 20; ++i) { manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, - !kHasInflightPackets); + !kHasInflightPackets, kPtoDelay); EXPECT_TRUE(alarm_->IsSet()); if (i <= 10) { EXPECT_EQ(initial_retransmittable_on_wire_timeout * (1 << i), @@ -426,6 +427,113 @@ } } +TEST_F(QuicPingManagerTest, PtoBasedRetransmittableOnWireTimeout) { + // Set the initial retransmittable on wire timeout. + manager_.set_initial_retransmittable_on_wire_timeout( + QuicTime::Delta::FromMilliseconds(200)); + // Verify the alarm is set based on different value based on PTO delay. + for (int num_times_pto : {1, 2, 3}) { + EXPECT_TRUE(!alarm_->IsSet()); + manager_.set_num_ptos_for_retransmittable_on_wire_timeout(num_times_pto); + manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, + !kHasInflightPackets, kPtoDelay); + EXPECT_EQ(kPtoDelay * num_times_pto, + alarm_->deadline() - clock_.ApproximateNow()); + EXPECT_TRUE(alarm_->IsSet()); + clock_.AdvanceTime(kPtoDelay * num_times_pto); + EXPECT_CALL(delegate_, OnRetransmittableOnWireTimeout()); + alarm_->Fire(); + } +} + +TEST_F(QuicPingManagerTest, + PtoBasedRetransmittableOnWireTimeoutExponentiallyBackOff) { + const int kMaxAggressiveRetransmittableOnWireCount = 5; + SetQuicFlag(quic_max_aggressive_retransmittable_on_wire_ping_count, + kMaxAggressiveRetransmittableOnWireCount); + + for (int num_times_pto : {1, 2, 3}) { + manager_.set_num_ptos_for_retransmittable_on_wire_timeout(num_times_pto); + + QuicTime::Delta pto_delay = static_cast<int>(num_times_pto) * kPtoDelay; + + clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(5)); + EXPECT_FALSE(alarm_->IsSet()); + manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, + kHasInflightPackets, kPtoDelay); + // 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, kPtoDelay); + EXPECT_TRUE(alarm_->IsSet()); + // Verify alarm is in retransmittable-on-wire mode. + EXPECT_EQ(pto_delay, alarm_->deadline() - clock_.ApproximateNow()); + clock_.AdvanceTime(pto_delay); + EXPECT_CALL(delegate_, OnRetransmittableOnWireTimeout()); + alarm_->Fire(); + EXPECT_FALSE(alarm_->IsSet()); + // Reset alarm with in flight packets. + manager_.SetAlarm(clock_.ApproximateNow(), kShouldKeepAlive, + kHasInflightPackets, kPtoDelay); + } + + QuicTime::Delta retransmittable_on_wire_timeout = pto_delay; + + // 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, kPtoDelay); + 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, kPtoDelay); + } + + // 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, kPtoDelay); + 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()); + + // Reset the consecutive retransmittable on wire count for next iteration. + manager_.reset_consecutive_retransmittable_on_wire_count(); + } +} + } // namespace } // namespace test
diff --git a/quiche/quic/test_tools/quic_connection_peer.cc b/quiche/quic/test_tools/quic_connection_peer.cc index 001e690..c0d713a 100644 --- a/quiche/quic/test_tools/quic_connection_peer.cc +++ b/quiche/quic/test_tools/quic_connection_peer.cc
@@ -607,5 +607,12 @@ return connection->can_receive_ack_frequency_immediate_ack_; } +// static +uint8_t QuicConnectionPeer::GetNumPtosForRetransmittableOnWireTimeout( + const QuicConnection* connection) { + return connection->ping_manager_ + .num_ptos_for_retransmittable_on_wire_timeout_; +} + } // namespace test } // namespace quic
diff --git a/quiche/quic/test_tools/quic_connection_peer.h b/quiche/quic/test_tools/quic_connection_peer.h index 06d8c97..c1477a8 100644 --- a/quiche/quic/test_tools/quic_connection_peer.h +++ b/quiche/quic/test_tools/quic_connection_peer.h
@@ -253,6 +253,9 @@ static void OnForwardProgressMade(QuicConnection* connection); static bool CanReceiveAckFrequencyFrames(QuicConnection* connection); + + static uint8_t GetNumPtosForRetransmittableOnWireTimeout( + const QuicConnection* connection); }; } // namespace test