Delete a connection only if it has timed out on writing and not receiving for 10 seconds.
BUG=webrtc:5034,webrtc:4937
R=pthatcher@webrtc.org
Review URL: https://codereview.webrtc.org/1371623003 .
Cr-Commit-Position: refs/heads/master@{#10119}
diff --git a/webrtc/p2p/base/p2ptransportchannel.cc b/webrtc/p2p/base/p2ptransportchannel.cc
index cce20f4..1b7cb58 100644
--- a/webrtc/p2p/base/p2ptransportchannel.cc
+++ b/webrtc/p2p/base/p2ptransportchannel.cc
@@ -286,23 +286,26 @@
tiebreaker_ = tiebreaker;
}
-// Currently a channel is considered ICE completed once there is no
-// more than one connection per Network. This works for a single NIC
-// with both IPv4 and IPv6 enabled. However, this condition won't
-// happen when there are multiple NICs and all of them have
-// connectivity.
-// TODO(guoweis): Change Completion to be driven by a channel level
-// timer.
+// A channel is considered ICE completed once there is at most one active
+// connection per network and at least one active connection.
TransportChannelState P2PTransportChannel::GetState() const {
- std::set<rtc::Network*> networks;
-
- if (connections_.empty()) {
- return had_connection_ ? TransportChannelState::STATE_FAILED
- : TransportChannelState::STATE_INIT;
+ if (!had_connection_) {
+ return TransportChannelState::STATE_INIT;
}
- for (uint32 i = 0; i < connections_.size(); ++i) {
- rtc::Network* network = connections_[i]->port()->Network();
+ std::vector<Connection*> active_connections;
+ for (Connection* connection : connections_) {
+ if (connection->active()) {
+ active_connections.push_back(connection);
+ }
+ }
+ if (active_connections.empty()) {
+ return TransportChannelState::STATE_FAILED;
+ }
+
+ std::set<rtc::Network*> networks;
+ for (Connection* connection : active_connections) {
+ rtc::Network* network = connection->port()->Network();
if (networks.find(network) == networks.end()) {
networks.insert(network);
} else {
@@ -312,8 +315,8 @@
return TransportChannelState::STATE_CONNECTING;
}
}
- LOG_J(LS_VERBOSE, this) << "Ice is completed for this channel.";
+ LOG_J(LS_VERBOSE, this) << "Ice is completed for this channel.";
return TransportChannelState::STATE_COMPLETED;
}
@@ -872,8 +875,7 @@
infos->clear();
std::vector<Connection *>::const_iterator it;
- for (it = connections_.begin(); it != connections_.end(); ++it) {
- Connection* connection = *it;
+ for (Connection* connection : connections_) {
ConnectionInfo info;
info.best_connection = (best_connection_ == connection);
info.receiving = connection->receiving();
@@ -1016,7 +1018,7 @@
Connection* premier = GetBestConnectionOnNetwork(network);
// Do not prune connections if the current best connection is weak on this
// network. Otherwise, it may delete connections prematurely.
- if (!premier || premier->Weak()) {
+ if (!premier || premier->weak()) {
continue;
}
@@ -1105,8 +1107,8 @@
HandleNotWritable();
}
-bool P2PTransportChannel::Weak() const {
- return !best_connection_ || best_connection_->Weak();
+bool P2PTransportChannel::weak() const {
+ return !best_connection_ || best_connection_->weak();
}
// If we have a best connection, return it, otherwise return top one in the
@@ -1154,7 +1156,7 @@
UpdateConnectionStates();
// When the best connection is either not receiving or not writable,
// switch to weak ping delay.
- int ping_delay = Weak() ? WEAK_PING_DELAY : STRONG_PING_DELAY;
+ int ping_delay = weak() ? WEAK_PING_DELAY : STRONG_PING_DELAY;
if (rtc::Time() >= last_ping_sent_ms_ + ping_delay) {
Connection* conn = FindNextPingableConnection();
if (conn) {
@@ -1186,7 +1188,7 @@
// If the channel is weak, ping all candidates. Otherwise, we only
// want to ping connections that have not timed out on writing.
- return Weak() || conn->write_state() != Connection::STATE_WRITE_TIMEOUT;
+ return weak() || conn->write_state() != Connection::STATE_WRITE_TIMEOUT;
}
// Returns the next pingable connection to ping. This will be the oldest
diff --git a/webrtc/p2p/base/p2ptransportchannel.h b/webrtc/p2p/base/p2ptransportchannel.h
index 3f7b696..b3e7bc1 100644
--- a/webrtc/p2p/base/p2ptransportchannel.h
+++ b/webrtc/p2p/base/p2ptransportchannel.h
@@ -164,7 +164,7 @@
// A transport channel is weak if the current best connection is either
// not receiving or not writable, or if there is no best connection at all.
- bool Weak() const;
+ bool weak() const;
void UpdateConnectionStates();
void RequestSort();
void SortConnections();
diff --git a/webrtc/p2p/base/p2ptransportchannel_unittest.cc b/webrtc/p2p/base/p2ptransportchannel_unittest.cc
index 4add040..56635dc 100644
--- a/webrtc/p2p/base/p2ptransportchannel_unittest.cc
+++ b/webrtc/p2p/base/p2ptransportchannel_unittest.cc
@@ -1112,17 +1112,24 @@
TestSendRecv(1);
cricket::ConnectionInfos infos;
ASSERT_TRUE(ep1_ch1()->GetStats(&infos));
- ASSERT_EQ(1U, infos.size());
- EXPECT_TRUE(infos[0].new_connection);
- EXPECT_TRUE(infos[0].best_connection);
- EXPECT_TRUE(infos[0].receiving);
- EXPECT_TRUE(infos[0].writable);
- EXPECT_FALSE(infos[0].timeout);
- EXPECT_EQ(10U, infos[0].sent_total_packets);
- EXPECT_EQ(0U, infos[0].sent_discarded_packets);
- EXPECT_EQ(10 * 36U, infos[0].sent_total_bytes);
- EXPECT_EQ(10 * 36U, infos[0].recv_total_bytes);
- EXPECT_GT(infos[0].rtt, 0U);
+ ASSERT_TRUE(infos.size() >= 1);
+ cricket::ConnectionInfo* best_conn_info = nullptr;
+ for (cricket::ConnectionInfo& info : infos) {
+ if (info.best_connection) {
+ best_conn_info = &info;
+ break;
+ }
+ }
+ ASSERT_TRUE(best_conn_info != nullptr);
+ EXPECT_TRUE(best_conn_info->new_connection);
+ EXPECT_TRUE(best_conn_info->receiving);
+ EXPECT_TRUE(best_conn_info->writable);
+ EXPECT_FALSE(best_conn_info->timeout);
+ EXPECT_EQ(10U, best_conn_info->sent_total_packets);
+ EXPECT_EQ(0U, best_conn_info->sent_discarded_packets);
+ EXPECT_EQ(10 * 36U, best_conn_info->sent_total_bytes);
+ EXPECT_EQ(10 * 36U, best_conn_info->recv_total_bytes);
+ EXPECT_GT(best_conn_info->rtt, 0U);
DestroyChannels();
}
@@ -1620,6 +1627,20 @@
DestroyChannels();
}
+TEST_F(P2PTransportChannelMultihomedTest, TestGetState) {
+ AddAddress(0, kAlternateAddrs[0]);
+ AddAddress(0, kPublicAddrs[0]);
+ AddAddress(1, kPublicAddrs[1]);
+ // Create channels and let them go writable, as usual.
+ CreateChannels(1);
+
+ // Both transport channels will reach STATE_COMPLETED quickly.
+ EXPECT_EQ_WAIT(cricket::TransportChannelState::STATE_COMPLETED,
+ ep1_ch1()->GetState(), 1000);
+ EXPECT_EQ_WAIT(cricket::TransportChannelState::STATE_COMPLETED,
+ ep2_ch1()->GetState(), 1000);
+}
+
/*
TODO(pthatcher): Once have a way to handle network interfaces changes
@@ -1796,9 +1817,11 @@
conn2->ReceivedPing();
conn2->ReceivedPingResponse();
- // Wait for conn1 to be destroyed.
- EXPECT_TRUE_WAIT(GetConnectionTo(&ch, "1.1.1.1", 1) == nullptr, 3000);
- cricket::Port* port = GetPort(&ch);
+ // Wait for conn1 to be pruned.
+ EXPECT_TRUE_WAIT(conn1->pruned(), 3000);
+ // Destroy the connection to test SignalUnknownAddress.
+ conn1->Destroy();
+ EXPECT_TRUE_WAIT(GetConnectionTo(&ch, "1.1.1.1", 1) == nullptr, 1000);
// Create a minimal STUN message with prflx priority.
cricket::IceMessage request;
@@ -1810,6 +1833,7 @@
cricket::STUN_ATTR_PRIORITY, prflx_priority));
EXPECT_NE(prflx_priority, remote_priority);
+ cricket::Port* port = GetPort(&ch);
// conn1 should be resurrected with original priority.
port->SignalUnknownAddress(port, rtc::SocketAddress("1.1.1.1", 1),
cricket::PROTO_UDP, &request, kIceUfrag[1], false);
@@ -2068,3 +2092,70 @@
WAIT(conn3->pruned(), 1000);
EXPECT_FALSE(conn3->pruned());
}
+
+// Test that GetState returns the state correctly.
+TEST_F(P2PTransportChannelPingTest, TestGetState) {
+ cricket::FakePortAllocator pa(rtc::Thread::Current(), nullptr);
+ cricket::P2PTransportChannel ch("test channel", 1, nullptr, &pa);
+ PrepareChannel(&ch);
+ ch.Connect();
+ ch.MaybeStartGathering();
+ EXPECT_EQ(cricket::TransportChannelState::STATE_INIT, ch.GetState());
+ ch.AddRemoteCandidate(CreateCandidate("1.1.1.1", 1, 100));
+ ch.AddRemoteCandidate(CreateCandidate("2.2.2.2", 2, 1));
+ cricket::Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1);
+ cricket::Connection* conn2 = WaitForConnectionTo(&ch, "2.2.2.2", 2);
+ ASSERT_TRUE(conn1 != nullptr);
+ ASSERT_TRUE(conn2 != nullptr);
+ // Now there are two connections, so the transport channel is connecting.
+ EXPECT_EQ(cricket::TransportChannelState::STATE_CONNECTING, ch.GetState());
+ // |conn1| becomes writable and receiving; it then should prune |conn2|.
+ conn1->ReceivedPingResponse();
+ EXPECT_TRUE_WAIT(conn2->pruned(), 1000);
+ EXPECT_EQ(cricket::TransportChannelState::STATE_COMPLETED, ch.GetState());
+ conn1->Prune(); // All connections are pruned.
+ EXPECT_EQ(cricket::TransportChannelState::STATE_FAILED, ch.GetState());
+}
+
+// Test that when a low-priority connection is pruned, it is not deleted
+// right away, and it can become active and be pruned again.
+TEST_F(P2PTransportChannelPingTest, TestConnectionPrunedAgain) {
+ cricket::FakePortAllocator pa(rtc::Thread::Current(), nullptr);
+ cricket::P2PTransportChannel ch("test channel", 1, nullptr, &pa);
+ PrepareChannel(&ch);
+ ch.SetIceConfig(CreateIceConfig(1000, false));
+ ch.Connect();
+ ch.MaybeStartGathering();
+ ch.AddRemoteCandidate(CreateCandidate("1.1.1.1", 1, 100));
+ cricket::Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1);
+ ASSERT_TRUE(conn1 != nullptr);
+ EXPECT_EQ(conn1, ch.best_connection());
+ conn1->ReceivedPingResponse(); // Becomes writable and receiving
+
+ // Add a low-priority connection |conn2|, which will be pruned, but it will
+ // not be deleted right away. Once the current best connection becomes not
+ // receiving, |conn2| will start to ping and upon receiving the ping response,
+ // it will become the best connection.
+ ch.AddRemoteCandidate(CreateCandidate("2.2.2.2", 2, 1));
+ cricket::Connection* conn2 = WaitForConnectionTo(&ch, "2.2.2.2", 2);
+ ASSERT_TRUE(conn2 != nullptr);
+ EXPECT_TRUE_WAIT(!conn2->active(), 1000);
+ // |conn2| should not send a ping yet.
+ EXPECT_EQ(cricket::Connection::STATE_WAITING, conn2->state());
+ EXPECT_EQ(cricket::TransportChannelState::STATE_COMPLETED, ch.GetState());
+ // Wait for |conn1| becoming not receiving.
+ EXPECT_TRUE_WAIT(!conn1->receiving(), 3000);
+ // Make sure conn2 is not deleted.
+ conn2 = WaitForConnectionTo(&ch, "2.2.2.2", 2);
+ ASSERT_TRUE(conn2 != nullptr);
+ EXPECT_EQ_WAIT(cricket::Connection::STATE_INPROGRESS, conn2->state(), 1000);
+ conn2->ReceivedPingResponse();
+ EXPECT_EQ_WAIT(conn2, ch.best_connection(), 1000);
+ EXPECT_EQ(cricket::TransportChannelState::STATE_CONNECTING, ch.GetState());
+
+ // When |conn1| comes back again, |conn2| will be pruned again.
+ conn1->ReceivedPingResponse();
+ EXPECT_EQ_WAIT(conn1, ch.best_connection(), 1000);
+ EXPECT_TRUE_WAIT(!conn2->active(), 1000);
+ EXPECT_EQ(cricket::TransportChannelState::STATE_COMPLETED, ch.GetState());
+}
diff --git a/webrtc/p2p/base/port.cc b/webrtc/p2p/base/port.cc
index 842a26e..a5c7770 100644
--- a/webrtc/p2p/base/port.cc
+++ b/webrtc/p2p/base/port.cc
@@ -801,7 +801,8 @@
sent_packets_total_(0),
reported_(false),
state_(STATE_WAITING),
- receiving_timeout_(WEAK_CONNECTION_RECEIVE_TIMEOUT) {
+ receiving_timeout_(WEAK_CONNECTION_RECEIVE_TIMEOUT),
+ time_created_ms_(rtc::Time()) {
// All of our connections start in WAITING state.
// TODO(mallinath) - Start connections from STATE_FROZEN.
// Wire up to send stun packets
@@ -849,7 +850,6 @@
LOG_J(LS_VERBOSE, this) << "set_write_state from: " << old_value << " to "
<< value;
SignalStateChange(this);
- CheckTimeout();
}
}
@@ -858,7 +858,6 @@
LOG_J(LS_VERBOSE, this) << "set_receiving to " << value;
receiving_ = value;
SignalStateChange(this);
- CheckTimeout();
}
}
@@ -999,7 +998,7 @@
}
void Connection::Prune() {
- if (!pruned_) {
+ if (!pruned_ || active()) {
LOG_J(LS_VERBOSE, this) << "Connection pruned";
pruned_ = true;
requests_.Clear();
@@ -1089,6 +1088,9 @@
uint32 last_recv_time = last_received();
bool receiving = now <= last_recv_time + receiving_timeout_;
set_receiving(receiving);
+ if (dead(now)) {
+ Destroy();
+ }
}
void Connection::Ping(uint32 now) {
@@ -1119,6 +1121,28 @@
last_ping_response_received_ = rtc::Time();
}
+bool Connection::dead(uint32 now) const {
+ if (now < (time_created_ms_ + MIN_CONNECTION_LIFETIME)) {
+ // A connection that hasn't passed its minimum lifetime is still alive.
+ // We do this to prevent connections from being pruned too quickly
+ // during a network change event when two networks would be up
+ // simultaneously but only for a brief period.
+ return false;
+ }
+
+ if (receiving_) {
+ // A connection that is receiving is alive.
+ return false;
+ }
+
+ // A connection is alive until it is inactive.
+ return !active();
+
+ // TODO(honghaiz): Move from using the write state to using the receiving
+ // state with something like the following:
+ // return (now > (last_received() + DEAD_CONNECTION_RECEIVE_TIMEOUT));
+}
+
std::string Connection::ToDebugId() const {
std::stringstream ss;
ss << std::hex << this;
@@ -1230,7 +1254,7 @@
LOG_J(LS_ERROR, this) << "Received STUN error response, code="
<< error_code << "; killing connection";
set_state(STATE_FAILED);
- set_write_state(STATE_WRITE_TIMEOUT);
+ Destroy();
}
}
@@ -1251,13 +1275,6 @@
<< ", use_candidate=" << use_candidate;
}
-void Connection::CheckTimeout() {
- // If write has timed out and it is not receiving, remove the connection.
- if (!receiving_ && write_state_ == STATE_WRITE_TIMEOUT) {
- Destroy();
- }
-}
-
void Connection::HandleRoleConflictFromPeer() {
port_->SignalRoleConflict(port_);
}
diff --git a/webrtc/p2p/base/port.h b/webrtc/p2p/base/port.h
index 47903be..4616963 100644
--- a/webrtc/p2p/base/port.h
+++ b/webrtc/p2p/base/port.h
@@ -50,9 +50,9 @@
extern const char TCPTYPE_PASSIVE_STR[];
extern const char TCPTYPE_SIMOPEN_STR[];
-// If a connection does not receive anything for this long, it is considered
-// dead.
-const uint32 DEAD_CONNECTION_RECEIVE_TIMEOUT = 30 * 1000; // 30 seconds.
+// The minimum time we will wait before destroying a connection after creating
+// it.
+const uint32 MIN_CONNECTION_LIFETIME = 10 * 1000; // 10 seconds.
// The timeout duration when a connection does not receive anything.
const uint32 WEAK_CONNECTION_RECEIVE_TIMEOUT = 2500; // 2.5 seconds
@@ -435,8 +435,13 @@
// Determines whether the connection has finished connecting. This can only
// be false for TCP connections.
bool connected() const { return connected_; }
-
- bool Weak() const { return !(writable() && receiving() && connected()); }
+ bool weak() const { return !(writable() && receiving() && connected()); }
+ bool active() const {
+ // TODO(honghaiz): Move from using |write_state_| to using |pruned_|.
+ return write_state_ != STATE_WRITE_TIMEOUT;
+ }
+ // A connection is dead if it can be safely deleted.
+ bool dead(uint32 now) const;
// Estimate of the round-trip time over this connection.
uint32 rtt() const { return rtt_; }
@@ -572,9 +577,6 @@
void set_state(State state);
void set_connected(bool value);
- // Checks if this connection is useless, and hence, should be destroyed.
- void CheckTimeout();
-
void OnMessage(rtc::Message *pmsg);
Port* port_;
@@ -615,6 +617,7 @@
State state_;
// Time duration to switch from receiving to not receiving.
uint32 receiving_timeout_;
+ uint32 time_created_ms_;
friend class Port;
friend class ConnectionRequest;