gfe-relnote: In QUIC, use IdleNetworkDetector to detect handshake and idle network timeout. Protected by gfe2_reloadable_flag_quic_use_idle_network_detector.
PiperOrigin-RevId: 303103349
Change-Id: I3e5d6c3a2585284a69fa680b698ea14e0c86174e
diff --git a/quic/core/quic_connection.cc b/quic/core/quic_connection.cc
index dcb8ef8..f83dbdd 100644
--- a/quic/core/quic_connection.cc
+++ b/quic/core/quic_connection.cc
@@ -333,7 +333,11 @@
bytes_received_before_address_validation_(0),
bytes_sent_before_address_validation_(0),
address_validated_(false),
- blackhole_detector_(this, &arena_, alarm_factory_) {
+ blackhole_detector_(this, &arena_, alarm_factory_),
+ idle_network_detector_(this,
+ clock_->ApproximateNow(),
+ &arena_,
+ alarm_factory_) {
QUIC_DLOG(INFO) << ENDPOINT << "Created connection with server connection ID "
<< server_connection_id
<< " and version: " << ParsedQuicVersionToString(version());
@@ -875,7 +879,7 @@
// frames, since the processing may result in sending a bundled ack.
uber_received_packet_manager_.RecordPacketReceived(
last_decrypted_packet_level_, last_header_,
- time_of_last_received_packet_);
+ GetTimeOfLastReceivedPacket());
DCHECK(connected_);
return true;
}
@@ -976,7 +980,7 @@
}
processing_ack_frame_ = true;
sent_packet_manager_.OnAckFrameStart(largest_acked, ack_delay_time,
- time_of_last_received_packet_);
+ GetTimeOfLastReceivedPacket());
return true;
}
@@ -1022,7 +1026,7 @@
const bool one_rtt_packet_was_acked =
sent_packet_manager_.one_rtt_packet_acked();
const AckResult ack_result = sent_packet_manager_.OnAckFrameEnd(
- time_of_last_received_packet_, last_header_.packet_number,
+ GetTimeOfLastReceivedPacket(), last_header_.packet_number,
last_decrypted_packet_level_);
if (ack_result != PACKETS_NEWLY_ACKED &&
ack_result != NO_PACKETS_NEWLY_ACKED) {
@@ -1298,7 +1302,7 @@
UpdatePacketContent(NOT_PADDED_PING);
if (debug_visitor_ != nullptr) {
- debug_visitor_->OnWindowUpdateFrame(frame, time_of_last_received_packet_);
+ debug_visitor_->OnWindowUpdateFrame(frame, GetTimeOfLastReceivedPacket());
}
QUIC_DVLOG(1) << ENDPOINT << "WINDOW_UPDATE_FRAME received " << frame;
visitor_->OnWindowUpdateFrame(frame);
@@ -1455,7 +1459,7 @@
// never process a packet while an ACK for it cannot be encrypted.
uber_received_packet_manager_.MaybeUpdateAckTimeout(
should_last_packet_instigate_acks_, last_decrypted_packet_level_,
- last_header_.packet_number, time_of_last_received_packet_,
+ last_header_.packet_number, GetTimeOfLastReceivedPacket(),
clock_->ApproximateNow(), sent_packet_manager_.GetRttStats());
ClearLastFrames();
@@ -1791,9 +1795,14 @@
<< " too far from current time:"
<< clock_->ApproximateNow().ToDebuggingValue();
}
- time_of_last_received_packet_ = packet.receipt_time();
+ if (use_idle_network_detector_) {
+ QUIC_RELOADABLE_FLAG_COUNT_N(quic_use_idle_network_detector, 1, 6);
+ idle_network_detector_.OnPacketReceived(packet.receipt_time());
+ } else {
+ time_of_last_received_packet_ = packet.receipt_time();
+ }
QUIC_DVLOG(1) << ENDPOINT << "time of last received packet: "
- << time_of_last_received_packet_.ToDebuggingValue();
+ << GetTimeOfLastReceivedPacket().ToDebuggingValue();
ScopedPacketFlusher flusher(this);
if (!framer_.ProcessPacket(packet)) {
@@ -2358,12 +2367,15 @@
SetPathDegradingAlarm();
}
- // Update |time_of_first_packet_sent_after_receiving_| if this is the
- // first packet sent after the last packet was received. If it were
- // updated on every sent packet, then sending into a black hole might
- // never timeout.
- if (time_of_first_packet_sent_after_receiving_ <
- time_of_last_received_packet_) {
+ if (use_idle_network_detector_) {
+ idle_network_detector_.OnPacketSent(packet_send_time);
+ QUIC_RELOADABLE_FLAG_COUNT_N(quic_use_idle_network_detector, 2, 6);
+ } else if (time_of_first_packet_sent_after_receiving_ <
+ time_of_last_received_packet_) {
+ // Update |time_of_first_packet_sent_after_receiving_| if this is the
+ // first packet sent after the last packet was received. If it were
+ // updated on every sent packet, then sending into a black hole might
+ // never timeout.
time_of_first_packet_sent_after_receiving_ = packet_send_time;
}
}
@@ -3058,6 +3070,10 @@
QUIC_RELOADABLE_FLAG_COUNT_N(quic_use_blackhole_detector, 4, 4);
blackhole_detector_.StopDetection();
}
+ if (use_idle_network_detector_) {
+ QUIC_RELOADABLE_FLAG_COUNT_N(quic_use_idle_network_detector, 3, 6);
+ idle_network_detector_.StopDetection();
+ }
}
QuicByteCount QuicConnection::max_packet_length() const {
@@ -3102,6 +3118,11 @@
} else if (idle_timeout > QuicTime::Delta::FromSeconds(1)) {
idle_timeout = idle_timeout - QuicTime::Delta::FromSeconds(1);
}
+ if (use_idle_network_detector_) {
+ QUIC_RELOADABLE_FLAG_COUNT_N(quic_use_idle_network_detector, 4, 6);
+ idle_network_detector_.SetTimeouts(handshake_timeout, idle_timeout);
+ return;
+ }
handshake_timeout_ = handshake_timeout;
idle_network_timeout_ = idle_timeout;
@@ -3109,6 +3130,7 @@
}
void QuicConnection::CheckForTimeout() {
+ DCHECK(!use_idle_network_detector_);
QuicTime now = clock_->ApproximateNow();
if (!handshake_timeout_.IsInfinite()) {
QuicTime::Delta connected_duration = now - stats_.connection_creation_time;
@@ -3163,6 +3185,7 @@
}
void QuicConnection::SetTimeoutAlarm() {
+ DCHECK(!use_idle_network_detector_);
QuicTime time_of_last_packet =
std::max(time_of_last_received_packet_,
time_of_first_packet_sent_after_receiving_);
@@ -4190,6 +4213,43 @@
ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
}
+void QuicConnection::OnHandshakeTimeout() {
+ QUIC_RELOADABLE_FLAG_COUNT_N(quic_use_idle_network_detector, 5, 6);
+ DCHECK(use_idle_network_detector_);
+ const QuicTime::Delta duration =
+ clock_->ApproximateNow() - stats_.connection_creation_time;
+ const std::string error_details = quiche::QuicheStrCat(
+ "Handshake timeout expired after ", duration.ToDebuggingValue(),
+ ". Timeout:",
+ idle_network_detector_.handshake_timeout().ToDebuggingValue());
+ QUIC_DVLOG(1) << ENDPOINT << error_details;
+ CloseConnection(QUIC_HANDSHAKE_TIMEOUT, error_details,
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+}
+
+void QuicConnection::OnIdleNetworkDetected() {
+ QUIC_RELOADABLE_FLAG_COUNT_N(quic_use_idle_network_detector, 6, 6);
+ DCHECK(use_idle_network_detector_);
+ const QuicTime::Delta duration =
+ clock_->ApproximateNow() -
+ idle_network_detector_.last_network_activity_time();
+ const std::string error_details = quiche::QuicheStrCat(
+ "No recent network activity after ", duration.ToDebuggingValue(),
+ ". Timeout:",
+ idle_network_detector_.idle_network_timeout().ToDebuggingValue());
+ QUIC_DVLOG(1) << ENDPOINT << error_details;
+ if ((sent_packet_manager_.GetConsecutiveTlpCount() > 0 ||
+ sent_packet_manager_.GetConsecutiveRtoCount() > 0 ||
+ sent_packet_manager_.GetConsecutivePtoCount() > 0 ||
+ visitor_->ShouldKeepConnectionAlive())) {
+ CloseConnection(QUIC_NETWORK_IDLE_TIMEOUT, error_details,
+ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
+ return;
+ }
+ CloseConnection(QUIC_NETWORK_IDLE_TIMEOUT, error_details,
+ idle_timeout_connection_close_behavior_);
+}
+
QuicTime QuicConnection::GetPathDegradingDeadline() const {
DCHECK(use_blackhole_detector_);
if (!ShouldDetectPathDegrading()) {
@@ -4201,8 +4261,14 @@
bool QuicConnection::ShouldDetectPathDegrading() const {
DCHECK(use_blackhole_detector_);
- return connected_ && handshake_timeout_.IsInfinite() &&
- perspective_ == Perspective::IS_CLIENT && !is_path_degrading_;
+ if (!connected_) {
+ return false;
+ }
+ // No path degrading detection before handshake completes.
+ if (!GetHandshakeTimeout().IsInfinite()) {
+ return false;
+ }
+ return perspective_ == Perspective::IS_CLIENT && !is_path_degrading_;
}
QuicTime QuicConnection::GetNetworkBlackholeDeadline() const {
@@ -4219,13 +4285,27 @@
if (!connected_) {
return false;
}
- if (!handshake_timeout_.IsInfinite()) {
- // No blackhole detection before handshake completes.
+ // No blackhole detection before handshake completes.
+ if (!GetHandshakeTimeout().IsInfinite()) {
return false;
}
return close_connection_after_five_rtos_ ||
(sent_packet_manager_.pto_enabled() && max_consecutive_ptos_ > 0);
}
+QuicTime::Delta QuicConnection::GetHandshakeTimeout() const {
+ if (use_idle_network_detector_) {
+ return idle_network_detector_.handshake_timeout();
+ }
+ return handshake_timeout_;
+}
+
+QuicTime QuicConnection::GetTimeOfLastReceivedPacket() const {
+ if (use_idle_network_detector_) {
+ return idle_network_detector_.time_of_last_received_packet();
+ }
+ return time_of_last_received_packet_;
+}
+
#undef ENDPOINT // undef for jumbo builds
} // namespace quic
diff --git a/quic/core/quic_connection.h b/quic/core/quic_connection.h
index cd478a6..a6be992 100644
--- a/quic/core/quic_connection.h
+++ b/quic/core/quic_connection.h
@@ -35,6 +35,7 @@
#include "net/third_party/quiche/src/quic/core/quic_connection_id.h"
#include "net/third_party/quiche/src/quic/core/quic_connection_stats.h"
#include "net/third_party/quiche/src/quic/core/quic_framer.h"
+#include "net/third_party/quiche/src/quic/core/quic_idle_network_detector.h"
#include "net/third_party/quiche/src/quic/core/quic_mtu_discovery.h"
#include "net/third_party/quiche/src/quic/core/quic_network_blackhole_detector.h"
#include "net/third_party/quiche/src/quic/core/quic_one_block_arena.h"
@@ -350,7 +351,8 @@
public QuicBlockedWriterInterface,
public QuicPacketCreator::DelegateInterface,
public QuicSentPacketManager::NetworkChangeVisitor,
- public QuicNetworkBlackholeDetector::Delegate {
+ public QuicNetworkBlackholeDetector::Delegate,
+ public QuicIdleNetworkDetector::Delegate {
public:
// Constructs a new QuicConnection for |connection_id| and
// |initial_peer_address| using |writer| to write packets. |owns_writer|
@@ -581,6 +583,10 @@
void OnPathDegradingDetected() override;
void OnBlackholeDetected() override;
+ // QuicIdleNetworkDetector::Delegate
+ void OnHandshakeTimeout() override;
+ void OnIdleNetworkDetected() override;
+
// Please note, this is not a const function. For logging purpose, please use
// ack_frame().
const QuicFrame GetUpdatedAckFrame();
@@ -1241,6 +1247,10 @@
// Returns true if network blackhole should be detected.
bool ShouldDetectBlackhole() const;
+ // Remove these two when deprecating quic_use_idle_network_detector.
+ QuicTime::Delta GetHandshakeTimeout() const;
+ QuicTime GetTimeOfLastReceivedPacket() const;
+
QuicFramer framer_;
// Contents received in the current packet, especially used to identify
@@ -1393,6 +1403,7 @@
// An alarm that is scheduled when the connection can still write and there
// may be more data to send.
// An alarm that fires when the connection may have timed out.
+ // TODO(fayang): Remove this when deprecating quic_use_idle_network_detector.
QuicArenaScopedPtr<QuicAlarm> timeout_alarm_;
// An alarm that fires when a ping should be sent.
QuicArenaScopedPtr<QuicAlarm> ping_alarm_;
@@ -1411,6 +1422,8 @@
QuicPacketCreator packet_creator_;
+ // TODO(fayang): Remove these two when deprecating
+ // quic_use_idle_network_detector.
// Network idle time before this connection is closed.
QuicTime::Delta idle_network_timeout_;
// The connection will wait this long for the handshake to complete.
@@ -1422,6 +1435,8 @@
// Timestamps used for timeouts.
// The time of the first retransmittable packet that was sent after the most
// recently received packet.
+ // TODO(fayang): Remove these two when deprecating
+ // quic_use_idle_network_detector.
QuicTime time_of_first_packet_sent_after_receiving_;
// The time that a packet is received for this connection. Initialized to
// connection creation time.
@@ -1587,8 +1602,14 @@
QuicNetworkBlackholeDetector blackhole_detector_;
+ QuicIdleNetworkDetector idle_network_detector_;
+
const bool use_blackhole_detector_ =
GetQuicReloadableFlag(quic_use_blackhole_detector);
+
+ const bool use_idle_network_detector_ =
+ use_blackhole_detector_ &&
+ GetQuicReloadableFlag(quic_use_idle_network_detector);
};
} // namespace quic
diff --git a/quic/core/quic_connection_test.cc b/quic/core/quic_connection_test.cc
index 1b1511b..e5faa35 100644
--- a/quic/core/quic_connection_test.cc
+++ b/quic/core/quic_connection_test.cc
@@ -844,6 +844,11 @@
}
TestAlarmFactory::TestAlarm* GetTimeoutAlarm() {
+ if (GetQuicReloadableFlag(quic_use_blackhole_detector) &&
+ GetQuicReloadableFlag(quic_use_idle_network_detector)) {
+ return reinterpret_cast<TestAlarmFactory::TestAlarm*>(
+ QuicConnectionPeer::GetIdleNetworkDetectorAlarm(this));
+ }
return reinterpret_cast<TestAlarmFactory::TestAlarm*>(
QuicConnectionPeer::GetTimeoutAlarm(this));
}
@@ -4777,7 +4782,7 @@
EXPECT_TRUE(connection_.connected());
// Advance the time and send the first packet to the peer.
- clock_.AdvanceTime(QuicTime::Delta::FromMicroseconds(20));
+ clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(20));
QuicPacketNumber last_packet;
SendStreamDataToPeer(1, "foo", 0, NO_FIN, &last_packet);
EXPECT_EQ(QuicPacketNumber(1u), last_packet);
@@ -4790,7 +4795,10 @@
EXPECT_CALL(visitor_, OnConnectionClosed(_, _)).Times(0);
QuicTime::Delta delay = initial_ddl - clock_.ApproximateNow();
clock_.AdvanceTime(delay);
- connection_.GetTimeoutAlarm()->Fire();
+ if (!GetQuicReloadableFlag(quic_use_blackhole_detector) ||
+ !GetQuicReloadableFlag(quic_use_idle_network_detector)) {
+ connection_.GetTimeoutAlarm()->Fire();
+ }
// Verify the timeout alarm deadline is updated.
EXPECT_TRUE(connection_.connected());
EXPECT_TRUE(connection_.GetTimeoutAlarm()->IsSet());
@@ -4879,8 +4887,11 @@
EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _, _));
ProcessAckPacket(&frame);
- // Fire early to verify it wouldn't timeout yet.
- connection_.GetTimeoutAlarm()->Fire();
+ if (!GetQuicReloadableFlag(quic_use_blackhole_detector) ||
+ !GetQuicReloadableFlag(quic_use_idle_network_detector)) {
+ // Fire early to verify it wouldn't timeout yet.
+ connection_.GetTimeoutAlarm()->Fire();
+ }
EXPECT_TRUE(connection_.GetTimeoutAlarm()->IsSet());
EXPECT_TRUE(connection_.connected());
@@ -5627,7 +5638,13 @@
SendStreamDataToPeer(
GetNthClientInitiatedStreamId(1, connection_.transport_version()), "foo",
0, FIN, nullptr);
- EXPECT_EQ(default_timeout, connection_.GetTimeoutAlarm()->deadline());
+ if (GetQuicReloadableFlag(quic_use_blackhole_detector) &&
+ GetQuicReloadableFlag(quic_use_idle_network_detector)) {
+ EXPECT_EQ(default_timeout + five_ms,
+ connection_.GetTimeoutAlarm()->deadline());
+ } else {
+ EXPECT_EQ(default_timeout, connection_.GetTimeoutAlarm()->deadline());
+ }
// Now send more data. This will not move the timeout because
// no data has been received since the previous write.
@@ -5635,13 +5652,22 @@
SendStreamDataToPeer(
GetNthClientInitiatedStreamId(1, connection_.transport_version()), "foo",
3, FIN, nullptr);
- EXPECT_EQ(default_timeout, connection_.GetTimeoutAlarm()->deadline());
+ if (GetQuicReloadableFlag(quic_use_blackhole_detector) &&
+ GetQuicReloadableFlag(quic_use_idle_network_detector)) {
+ EXPECT_EQ(default_timeout + five_ms,
+ connection_.GetTimeoutAlarm()->deadline());
+ } else {
+ EXPECT_EQ(default_timeout, connection_.GetTimeoutAlarm()->deadline());
+ }
// The original alarm will fire. We should not time out because we had a
// network event at t=5ms. The alarm will reregister.
clock_.AdvanceTime(initial_idle_timeout - five_ms - five_ms);
EXPECT_EQ(default_timeout, clock_.ApproximateNow());
- connection_.GetTimeoutAlarm()->Fire();
+ if (!GetQuicReloadableFlag(quic_use_blackhole_detector) ||
+ !GetQuicReloadableFlag(quic_use_idle_network_detector)) {
+ connection_.GetTimeoutAlarm()->Fire();
+ }
EXPECT_TRUE(connection_.GetTimeoutAlarm()->IsSet());
EXPECT_TRUE(connection_.connected());
EXPECT_EQ(default_timeout + five_ms,
@@ -5690,7 +5716,13 @@
SendStreamDataToPeer(
GetNthClientInitiatedStreamId(1, connection_.transport_version()), "foo",
0, FIN, nullptr);
- EXPECT_EQ(default_timeout, connection_.GetTimeoutAlarm()->deadline());
+ if (GetQuicReloadableFlag(quic_use_blackhole_detector) &&
+ GetQuicReloadableFlag(quic_use_idle_network_detector)) {
+ EXPECT_EQ(default_timeout + five_ms,
+ connection_.GetTimeoutAlarm()->deadline());
+ } else {
+ EXPECT_EQ(default_timeout, connection_.GetTimeoutAlarm()->deadline());
+ }
// Move forward 5 ms and receive a packet, which will move the timeout
// forward 5 ms more (but will not reschedule the alarm).
@@ -5719,7 +5751,10 @@
ASSERT_EQ(default_timeout.ToDebuggingValue(),
clock_.Now().ToDebuggingValue());
EXPECT_EQ(default_timeout, clock_.Now());
- connection_.GetTimeoutAlarm()->Fire();
+ if (!GetQuicReloadableFlag(quic_use_blackhole_detector) ||
+ !GetQuicReloadableFlag(quic_use_idle_network_detector)) {
+ connection_.GetTimeoutAlarm()->Fire();
+ }
EXPECT_TRUE(connection_.GetTimeoutAlarm()->IsSet());
EXPECT_TRUE(connection_.connected());
ASSERT_EQ(final_timeout.ToDebuggingValue(),
@@ -5775,7 +5810,13 @@
SendStreamDataToPeer(
GetNthClientInitiatedStreamId(1, connection_.transport_version()), "foo",
0, FIN, nullptr);
- EXPECT_EQ(default_timeout, connection_.GetTimeoutAlarm()->deadline());
+ if (GetQuicReloadableFlag(quic_use_blackhole_detector) &&
+ GetQuicReloadableFlag(quic_use_idle_network_detector)) {
+ EXPECT_EQ(default_timeout + five_ms,
+ connection_.GetTimeoutAlarm()->deadline());
+ } else {
+ EXPECT_EQ(default_timeout, connection_.GetTimeoutAlarm()->deadline());
+ }
// Now send more data. This will not move the timeout because
// no data has been received since the previous write.
@@ -5783,13 +5824,22 @@
SendStreamDataToPeer(
GetNthClientInitiatedStreamId(1, connection_.transport_version()), "foo",
3, FIN, nullptr);
- EXPECT_EQ(default_timeout, connection_.GetTimeoutAlarm()->deadline());
+ if (GetQuicReloadableFlag(quic_use_blackhole_detector) &&
+ GetQuicReloadableFlag(quic_use_idle_network_detector)) {
+ EXPECT_EQ(default_timeout + five_ms,
+ connection_.GetTimeoutAlarm()->deadline());
+ } else {
+ EXPECT_EQ(default_timeout, connection_.GetTimeoutAlarm()->deadline());
+ }
// The original alarm will fire. We should not time out because we had a
// network event at t=5ms. The alarm will reregister.
clock_.AdvanceTime(default_idle_timeout - five_ms - five_ms);
EXPECT_EQ(default_timeout, clock_.ApproximateNow());
- connection_.GetTimeoutAlarm()->Fire();
+ if (!GetQuicReloadableFlag(quic_use_blackhole_detector) ||
+ !GetQuicReloadableFlag(quic_use_idle_network_detector)) {
+ connection_.GetTimeoutAlarm()->Fire();
+ }
EXPECT_TRUE(connection_.GetTimeoutAlarm()->IsSet());
EXPECT_TRUE(connection_.connected());
EXPECT_EQ(default_timeout + five_ms,
@@ -5852,7 +5902,13 @@
SendStreamDataToPeer(
GetNthClientInitiatedStreamId(1, connection_.transport_version()), "foo",
0, FIN, nullptr);
- EXPECT_EQ(default_timeout, connection_.GetTimeoutAlarm()->deadline());
+ if (GetQuicReloadableFlag(quic_use_blackhole_detector) &&
+ GetQuicReloadableFlag(quic_use_idle_network_detector)) {
+ EXPECT_EQ(default_timeout + five_ms,
+ connection_.GetTimeoutAlarm()->deadline());
+ } else {
+ EXPECT_EQ(default_timeout, connection_.GetTimeoutAlarm()->deadline());
+ }
// Retransmit the packet via tail loss probe.
clock_.AdvanceTime(connection_.GetRetransmissionAlarm()->deadline() -
@@ -5909,7 +5965,13 @@
SendStreamDataToPeer(
GetNthClientInitiatedStreamId(1, connection_.transport_version()), "foo",
0, FIN, nullptr);
- EXPECT_EQ(default_timeout, connection_.GetTimeoutAlarm()->deadline());
+ if (GetQuicReloadableFlag(quic_use_blackhole_detector) &&
+ GetQuicReloadableFlag(quic_use_idle_network_detector)) {
+ EXPECT_EQ(default_timeout + five_ms,
+ connection_.GetTimeoutAlarm()->deadline());
+ } else {
+ EXPECT_EQ(default_timeout, connection_.GetTimeoutAlarm()->deadline());
+ }
// Indicate streams are still open.
EXPECT_CALL(visitor_, ShouldKeepConnectionAlive())
@@ -5960,7 +6022,10 @@
// network event at t=5ms. The alarm will reregister.
clock_.AdvanceTime(initial_idle_timeout - five_ms);
EXPECT_EQ(default_timeout, clock_.ApproximateNow());
- connection_.GetTimeoutAlarm()->Fire();
+ if (!GetQuicReloadableFlag(quic_use_blackhole_detector) ||
+ !GetQuicReloadableFlag(quic_use_idle_network_detector)) {
+ connection_.GetTimeoutAlarm()->Fire();
+ }
EXPECT_TRUE(connection_.connected());
EXPECT_TRUE(connection_.GetTimeoutAlarm()->IsSet());
EXPECT_EQ(default_timeout + five_ms,
@@ -6017,7 +6082,10 @@
// network event at t=5ms. The alarm will reregister.
clock_.AdvanceTime(initial_idle_timeout - five_ms);
EXPECT_EQ(default_timeout, clock_.ApproximateNow());
- connection_.GetTimeoutAlarm()->Fire();
+ if (!GetQuicReloadableFlag(quic_use_blackhole_detector) ||
+ !GetQuicReloadableFlag(quic_use_idle_network_detector)) {
+ connection_.GetTimeoutAlarm()->Fire();
+ }
EXPECT_TRUE(connection_.connected());
EXPECT_TRUE(connection_.GetTimeoutAlarm()->IsSet());
EXPECT_EQ(default_timeout + five_ms,
diff --git a/quic/core/quic_idle_network_detector.cc b/quic/core/quic_idle_network_detector.cc
new file mode 100644
index 0000000..8d76fd2
--- /dev/null
+++ b/quic/core/quic_idle_network_detector.cc
@@ -0,0 +1,109 @@
+// Copyright (c) 2020 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 "net/third_party/quiche/src/quic/core/quic_idle_network_detector.h"
+
+#include "net/third_party/quiche/src/quic/core/quic_constants.h"
+
+namespace quic {
+
+namespace {
+
+class AlarmDelegate : public QuicAlarm::Delegate {
+ public:
+ explicit AlarmDelegate(QuicIdleNetworkDetector* detector)
+ : detector_(detector) {}
+ AlarmDelegate(const AlarmDelegate&) = delete;
+ AlarmDelegate& operator=(const AlarmDelegate&) = delete;
+
+ void OnAlarm() override { detector_->OnAlarm(); }
+
+ private:
+ QuicIdleNetworkDetector* detector_;
+};
+
+} // namespace
+
+QuicIdleNetworkDetector::QuicIdleNetworkDetector(
+ Delegate* delegate,
+ QuicTime now,
+ QuicConnectionArena* arena,
+ QuicAlarmFactory* alarm_factory)
+ : delegate_(delegate),
+ start_time_(now),
+ handshake_timeout_(QuicTime::Delta::Infinite()),
+ time_of_last_received_packet_(now),
+ time_of_first_packet_sent_after_receiving_(QuicTime::Zero()),
+ idle_network_timeout_(QuicTime::Delta::Infinite()),
+ alarm_(
+ alarm_factory->CreateAlarm(arena->New<AlarmDelegate>(this), arena)) {}
+
+void QuicIdleNetworkDetector::OnAlarm() {
+ if (handshake_timeout_.IsInfinite()) {
+ delegate_->OnIdleNetworkDetected();
+ return;
+ }
+ if (idle_network_timeout_.IsInfinite()) {
+ delegate_->OnHandshakeTimeout();
+ return;
+ }
+ if (last_network_activity_time() + idle_network_timeout_ >
+ start_time_ + handshake_timeout_) {
+ delegate_->OnHandshakeTimeout();
+ return;
+ }
+ delegate_->OnIdleNetworkDetected();
+}
+
+void QuicIdleNetworkDetector::SetTimeouts(
+ QuicTime::Delta handshake_timeout,
+ QuicTime::Delta idle_network_timeout) {
+ handshake_timeout_ = handshake_timeout;
+ idle_network_timeout_ = idle_network_timeout;
+
+ SetAlarm();
+}
+
+void QuicIdleNetworkDetector::StopDetection() {
+ alarm_->Cancel();
+ handshake_timeout_ = QuicTime::Delta::Infinite();
+ idle_network_timeout_ = QuicTime::Delta::Infinite();
+}
+
+void QuicIdleNetworkDetector::OnPacketSent(QuicTime now) {
+ if (time_of_first_packet_sent_after_receiving_ >
+ time_of_last_received_packet_) {
+ return;
+ }
+ time_of_first_packet_sent_after_receiving_ =
+ std::max(time_of_first_packet_sent_after_receiving_, now);
+
+ SetAlarm();
+}
+
+void QuicIdleNetworkDetector::OnPacketReceived(QuicTime now) {
+ time_of_last_received_packet_ = std::max(time_of_last_received_packet_, now);
+
+ SetAlarm();
+}
+
+void QuicIdleNetworkDetector::SetAlarm() {
+ // Set alarm to the nearer deadline.
+ QuicTime new_deadline = QuicTime::Zero();
+ if (!handshake_timeout_.IsInfinite()) {
+ new_deadline = start_time_ + handshake_timeout_;
+ }
+ if (!idle_network_timeout_.IsInfinite()) {
+ const QuicTime idle_network_deadline =
+ last_network_activity_time() + idle_network_timeout_;
+ if (new_deadline.IsInitialized()) {
+ new_deadline = std::min(new_deadline, idle_network_deadline);
+ } else {
+ new_deadline = idle_network_deadline;
+ }
+ }
+ alarm_->Update(new_deadline, kAlarmGranularity);
+}
+
+} // namespace quic
diff --git a/quic/core/quic_idle_network_detector.h b/quic/core/quic_idle_network_detector.h
new file mode 100644
index 0000000..83beb29
--- /dev/null
+++ b/quic/core/quic_idle_network_detector.h
@@ -0,0 +1,103 @@
+// Copyright (c) 2020 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_IDLE_NETWORK_DETECTOR_H_
+#define QUICHE_QUIC_CORE_QUIC_IDLE_NETWORK_DETECTOR_H_
+
+#include "net/third_party/quiche/src/quic/core/quic_alarm.h"
+#include "net/third_party/quiche/src/quic/core/quic_alarm_factory.h"
+#include "net/third_party/quiche/src/quic/core/quic_one_block_arena.h"
+#include "net/third_party/quiche/src/quic/core/quic_time.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+
+namespace quic {
+
+namespace test {
+class QuicConnectionPeer;
+class QuicIdleNetworkDetectorTestPeer;
+} // namespace test
+
+// QuicIdleNetworkDetector detects handshake timeout and idle network timeout.
+// Handshake timeout detection is disabled after handshake completes. Idle
+// network deadline is extended by network activity (e.g., sending or receiving
+// packets).
+class QUIC_EXPORT_PRIVATE QuicIdleNetworkDetector {
+ public:
+ class QUIC_EXPORT_PRIVATE Delegate {
+ public:
+ virtual ~Delegate() {}
+
+ // Called when handshake times out.
+ virtual void OnHandshakeTimeout() = 0;
+
+ // Called when idle network has been detected.
+ virtual void OnIdleNetworkDetected() = 0;
+ };
+
+ QuicIdleNetworkDetector(Delegate* delegate,
+ QuicTime now,
+ QuicConnectionArena* arena,
+ QuicAlarmFactory* alarm_factory);
+
+ void OnAlarm();
+
+ // Called to set handshake_timeout_ and idle_network_timeout_.
+ void SetTimeouts(QuicTime::Delta handshake_timeout,
+ QuicTime::Delta idle_network_timeout);
+
+ void StopDetection();
+
+ // Called when a packet gets sent.
+ void OnPacketSent(QuicTime now);
+
+ // Called when a packet gets received.
+ void OnPacketReceived(QuicTime now);
+
+ QuicTime::Delta handshake_timeout() const { return handshake_timeout_; }
+
+ QuicTime time_of_last_received_packet() const {
+ return time_of_last_received_packet_;
+ }
+
+ QuicTime last_network_activity_time() const {
+ return std::max(time_of_last_received_packet_,
+ time_of_first_packet_sent_after_receiving_);
+ }
+
+ QuicTime::Delta idle_network_timeout() const { return idle_network_timeout_; }
+
+ private:
+ friend class test::QuicConnectionPeer;
+ friend class test::QuicIdleNetworkDetectorTestPeer;
+
+ void SetAlarm();
+
+ Delegate* delegate_; // Not owned.
+
+ // Start time of the detector, handshake deadline = start_time_ +
+ // handshake_timeout_.
+ const QuicTime start_time_;
+
+ // Handshake timeout. Infinit means handshake has completed.
+ QuicTime::Delta handshake_timeout_;
+
+ // Time that last packet is received for this connection. Initialized to
+ // start_time_.
+ QuicTime time_of_last_received_packet_;
+
+ // Time that the first packet gets sent after the received packet. idle
+ // network deadline = std::max(time_of_last_received_packet_,
+ // time_of_first_packet_sent_after_receiving_) + idle_network_timeout_.
+ // Initialized to 0.
+ QuicTime time_of_first_packet_sent_after_receiving_;
+
+ // Idle network timeout. Infinit means no idle network timeout.
+ QuicTime::Delta idle_network_timeout_;
+
+ QuicArenaScopedPtr<QuicAlarm> alarm_;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_CORE_QUIC_IDLE_NETWORK_DETECTOR_H_
diff --git a/quic/core/quic_idle_network_detector_test.cc b/quic/core/quic_idle_network_detector_test.cc
new file mode 100644
index 0000000..16941ef
--- /dev/null
+++ b/quic/core/quic_idle_network_detector_test.cc
@@ -0,0 +1,148 @@
+// Copyright (c) 2020 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 "net/third_party/quiche/src/quic/core/quic_idle_network_detector.h"
+
+#include "net/third_party/quiche/src/quic/core/quic_one_block_arena.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h"
+
+namespace quic {
+namespace test {
+
+class QuicIdleNetworkDetectorTestPeer {
+ public:
+ static QuicAlarm* GetAlarm(QuicIdleNetworkDetector* detector) {
+ return detector->alarm_.get();
+ }
+};
+
+namespace {
+
+class MockDelegate : public QuicIdleNetworkDetector::Delegate {
+ public:
+ MOCK_METHOD0(OnHandshakeTimeout, void());
+ MOCK_METHOD0(OnIdleNetworkDetected, void());
+};
+
+class QuicIdleNetworkDetectorTest : public QuicTest {
+ public:
+ QuicIdleNetworkDetectorTest() {
+ clock_.AdvanceTime(QuicTime::Delta::FromSeconds(1));
+ detector_ = std::make_unique<QuicIdleNetworkDetector>(
+ &delegate_, clock_.Now(), &arena_, &alarm_factory_);
+ alarm_ = static_cast<MockAlarmFactory::TestAlarm*>(
+ QuicIdleNetworkDetectorTestPeer::GetAlarm(detector_.get()));
+ }
+
+ protected:
+ testing::StrictMock<MockDelegate> delegate_;
+ QuicConnectionArena arena_;
+ MockAlarmFactory alarm_factory_;
+
+ std::unique_ptr<QuicIdleNetworkDetector> detector_;
+
+ MockAlarmFactory::TestAlarm* alarm_;
+ MockClock clock_;
+};
+
+TEST_F(QuicIdleNetworkDetectorTest,
+ IdleNetworkDetectedBeforeHandshakeCompletes) {
+ 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());
+
+ // No network activity for 20s.
+ clock_.AdvanceTime(QuicTime::Delta::FromSeconds(20));
+ EXPECT_CALL(delegate_, OnIdleNetworkDetected());
+ alarm_->Fire();
+}
+
+TEST_F(QuicIdleNetworkDetectorTest, HandshakeTimeout) {
+ EXPECT_FALSE(alarm_->IsSet());
+ detector_->SetTimeouts(
+ /*handshake_timeout=*/QuicTime::Delta::FromSeconds(30),
+ /*idle_network_timeout=*/QuicTime::Delta::FromSeconds(20));
+ EXPECT_TRUE(alarm_->IsSet());
+
+ // Has network activity after 15s.
+ clock_.AdvanceTime(QuicTime::Delta::FromSeconds(15));
+ detector_->OnPacketReceived(clock_.Now());
+ EXPECT_EQ(clock_.Now() + QuicTime::Delta::FromSeconds(15),
+ alarm_->deadline());
+ // Handshake does not complete for another 15s.
+ clock_.AdvanceTime(QuicTime::Delta::FromSeconds(15));
+ EXPECT_CALL(delegate_, OnHandshakeTimeout());
+ alarm_->Fire();
+}
+
+TEST_F(QuicIdleNetworkDetectorTest,
+ IdleNetworkDetectedAfterHandshakeCompletes) {
+ EXPECT_FALSE(alarm_->IsSet());
+ detector_->SetTimeouts(
+ /*handshake_timeout=*/QuicTime::Delta::FromSeconds(30),
+ /*idle_network_timeout=*/QuicTime::Delta::FromSeconds(20));
+ EXPECT_TRUE(alarm_->IsSet());
+
+ // Handshake completes in 200ms.
+ clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(200));
+ detector_->OnPacketReceived(clock_.Now());
+ detector_->SetTimeouts(
+ /*handshake_timeout=*/QuicTime::Delta::Infinite(),
+ /*idle_network_timeout=*/QuicTime::Delta::FromSeconds(600));
+ EXPECT_EQ(clock_.Now() + QuicTime::Delta::FromSeconds(600),
+ alarm_->deadline());
+
+ // No network activity for 600s.
+ clock_.AdvanceTime(QuicTime::Delta::FromSeconds(600));
+ EXPECT_CALL(delegate_, OnIdleNetworkDetected());
+ alarm_->Fire();
+}
+
+TEST_F(QuicIdleNetworkDetectorTest,
+ DoNotExtendIdleDeadlineOnConsecutiveSentPackets) {
+ EXPECT_FALSE(alarm_->IsSet());
+ detector_->SetTimeouts(
+ /*handshake_timeout=*/QuicTime::Delta::FromSeconds(30),
+ /*idle_network_timeout=*/QuicTime::Delta::FromSeconds(20));
+ EXPECT_TRUE(alarm_->IsSet());
+
+ // Handshake completes in 200ms.
+ clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(200));
+ detector_->OnPacketReceived(clock_.Now());
+ detector_->SetTimeouts(
+ /*handshake_timeout=*/QuicTime::Delta::Infinite(),
+ /*idle_network_timeout=*/QuicTime::Delta::FromSeconds(600));
+ EXPECT_EQ(clock_.Now() + QuicTime::Delta::FromSeconds(600),
+ alarm_->deadline());
+
+ // Sent packets after 200ms.
+ clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(200));
+ detector_->OnPacketSent(clock_.Now());
+ const QuicTime packet_sent_time = clock_.Now();
+ EXPECT_EQ(packet_sent_time + QuicTime::Delta::FromSeconds(600),
+ alarm_->deadline());
+
+ // Sent another packet after 200ms
+ clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(200));
+ detector_->OnPacketSent(clock_.Now());
+ // Verify idle network deadline does not extend.
+ EXPECT_EQ(packet_sent_time + QuicTime::Delta::FromSeconds(600),
+ alarm_->deadline());
+
+ // No network activity for 600s.
+ clock_.AdvanceTime(QuicTime::Delta::FromSeconds(600) -
+ QuicTime::Delta::FromMilliseconds(200));
+ EXPECT_CALL(delegate_, OnIdleNetworkDetected());
+ alarm_->Fire();
+}
+
+} // namespace
+
+} // namespace test
+} // namespace quic
diff --git a/quic/core/quic_one_block_arena.h b/quic/core/quic_one_block_arena.h
index 41842f3..210892a 100644
--- a/quic/core/quic_one_block_arena.h
+++ b/quic/core/quic_one_block_arena.h
@@ -75,7 +75,9 @@
// 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<1024>;
+// TODO(fayang): Switch this back to 1024 when deprecating
+// quic_use_blackhole_detector or quic_use_idle_network_detector.
+using QuicConnectionArena = QuicOneBlockArena<1200>;
} // namespace quic
diff --git a/quic/test_tools/quic_connection_peer.cc b/quic/test_tools/quic_connection_peer.cc
index 21a0105..6b6387f 100644
--- a/quic/test_tools/quic_connection_peer.cc
+++ b/quic/test_tools/quic_connection_peer.cc
@@ -51,6 +51,9 @@
// static
QuicTime::Delta QuicConnectionPeer::GetNetworkTimeout(
QuicConnection* connection) {
+ if (connection->use_idle_network_detector_) {
+ return connection->idle_network_detector_.idle_network_timeout_;
+ }
return connection->idle_network_timeout_;
}
@@ -372,5 +375,11 @@
return connection->blackhole_detector_.blackhole_deadline_;
}
+// static
+QuicAlarm* QuicConnectionPeer::GetIdleNetworkDetectorAlarm(
+ QuicConnection* connection) {
+ return connection->idle_network_detector_.alarm_.get();
+}
+
} // namespace test
} // namespace quic
diff --git a/quic/test_tools/quic_connection_peer.h b/quic/test_tools/quic_connection_peer.h
index ab14190..d6d6b3c 100644
--- a/quic/test_tools/quic_connection_peer.h
+++ b/quic/test_tools/quic_connection_peer.h
@@ -144,6 +144,8 @@
static QuicTime GetPathDegradingDeadline(QuicConnection* connection);
static QuicTime GetBlackholeDetectionDeadline(QuicConnection* connection);
+
+ static QuicAlarm* GetIdleNetworkDetectorAlarm(QuicConnection* connection);
};
} // namespace test