Index: webrtc/p2p/base/p2ptransportchannel.cc |
diff --git a/webrtc/p2p/base/p2ptransportchannel.cc b/webrtc/p2p/base/p2ptransportchannel.cc |
index ee9906053cd772766821e491b4a42c826a211a78..f06473a31dd9a07486d898e6724b1e2ad6463b40 100644 |
--- a/webrtc/p2p/base/p2ptransportchannel.cc |
+++ b/webrtc/p2p/base/p2ptransportchannel.cc |
@@ -22,11 +22,7 @@ |
namespace { |
// messages for queuing up work for ourselves |
-enum { |
- MSG_SORT = 1, |
- MSG_PING, |
- MSG_CHECK_RECEIVING |
-}; |
+enum { MSG_SORT = 1, MSG_CHECK_AND_PING }; |
// When the socket is unwritable, we will use 10 Kbps (ignoring IP+UDP headers) |
// for pinging. When the socket is writable, we will use only 1 Kbps because |
@@ -34,12 +30,17 @@ enum { |
// well on a 28.8K modem, which is the slowest connection on which the voice |
// quality is reasonable at all. |
static const uint32 PING_PACKET_SIZE = 60 * 8; |
-static const uint32 WRITABLE_DELAY = 1000 * PING_PACKET_SIZE / 1000; // 480ms |
-static const uint32 UNWRITABLE_DELAY = 1000 * PING_PACKET_SIZE / 10000; // 50ms |
+// STRONG_PING_DELAY (480ms) is applied when the best connection is not |
+// writable or not receiving. |
+static const uint32 STRONG_PING_DELAY = 1000 * PING_PACKET_SIZE / 1000; |
+// WEAK_PING_DELAY (48ms) is applied when the best connection is both |
+// writable and receiving. |
+static const uint32 WEAK_PING_DELAY = 1000 * PING_PACKET_SIZE / 10000; |
// If there is a current writable connection, then we will also try hard to |
-// make sure it is pinged at this rate. |
-static const uint32 MAX_CURRENT_WRITABLE_DELAY = 900; // 2*WRITABLE_DELAY - bit |
+// make sure it is pinged at this rate (a little less than |
+// 2 * STRONG_PING_DELAY). |
+static const uint32 MAX_CURRENT_WRITABLE_DELAY = 900; |
static const int MIN_CHECK_RECEIVING_DELAY = 50; // ms |
@@ -70,15 +71,20 @@ int CompareConnectionCandidates(cricket::Connection* a, |
(b->remote_candidate().generation() + b->port()->generation()); |
} |
-// Compare two connections based on their connected state, writability and |
-// static preferences. |
-int CompareConnections(cricket::Connection *a, cricket::Connection *b) { |
+// Compare two connections based on their writing, receiving, and connected |
+// states. |
+int CompareConnectionStates(cricket::Connection* a, cricket::Connection* b) { |
// Sort based on write-state. Better states have lower values. |
if (a->write_state() < b->write_state()) |
return 1; |
if (a->write_state() > b->write_state()) |
return -1; |
+ if (a->receiving() && !b->receiving()) |
+ return 1; |
+ if (!a->receiving() && b->receiving()) |
+ return -1; |
+ |
// WARNING: Some complexity here about TCP reconnecting. |
// When a TCP connection fails because of a TCP socket disconnecting, the |
// active side of the connection will attempt to reconnect for 5 seconds while |
@@ -110,7 +116,14 @@ int CompareConnections(cricket::Connection *a, cricket::Connection *b) { |
return -1; |
} |
} |
+ return 0; |
+} |
+int CompareConnections(cricket::Connection* a, cricket::Connection* b) { |
+ int state_cmp = CompareConnectionStates(a, b); |
+ if (state_cmp != 0) { |
+ return state_cmp; |
+ } |
// Compare the candidate information. |
return CompareConnectionCandidates(a, b); |
} |
@@ -148,19 +161,33 @@ class ConnectionCompare { |
}; |
// Determines whether we should switch between two connections, based first on |
-// static preferences and then (if those are equal) on latency estimates. |
-bool ShouldSwitch(cricket::Connection* a_conn, cricket::Connection* b_conn) { |
+// connection states, static preferences, and then (if those are equal) on |
+// latency estimates. |
+bool ShouldSwitch(cricket::Connection* a_conn, |
+ cricket::Connection* b_conn, |
+ cricket::IceRole ice_role) { |
if (a_conn == b_conn) |
return false; |
if (!a_conn || !b_conn) // don't think the latter should happen |
return true; |
- int prefs_cmp = CompareConnections(a_conn, b_conn); |
- if (prefs_cmp < 0) |
- return true; |
- if (prefs_cmp > 0) |
+ // If the RECEIVING/WRITE/CONNECT states are different, we should switch |
+ // regardless of the nominated state. Otherwise, do not switch if |a_conn| is |
+ // nominated. |
+ int state_cmp = CompareConnectionStates(a_conn, b_conn); |
+ if (state_cmp != 0) { |
+ return state_cmp < 0; |
+ } |
+ if (ice_role == cricket::ICEROLE_CONTROLLED && a_conn->nominated()) { |
+ LOG(LS_VERBOSE) << "Controlled side did not switch due to nominated status"; |
return false; |
+ } |
+ |
+ int prefs_cmp = CompareConnectionCandidates(a_conn, b_conn); |
+ if (prefs_cmp != 0) { |
+ return prefs_cmp < 0; |
+ } |
return b_conn->rtt() <= a_conn->rtt() + kMinImprovement; |
} |
@@ -172,25 +199,25 @@ namespace cricket { |
P2PTransportChannel::P2PTransportChannel(const std::string& content_name, |
int component, |
P2PTransport* transport, |
- PortAllocator *allocator) : |
- TransportChannelImpl(content_name, component), |
- transport_(transport), |
- allocator_(allocator), |
- worker_thread_(rtc::Thread::Current()), |
- incoming_only_(false), |
- waiting_for_signaling_(false), |
- error_(0), |
- best_connection_(NULL), |
- pending_best_connection_(NULL), |
- sort_dirty_(false), |
- was_writable_(false), |
- remote_ice_mode_(ICEMODE_FULL), |
- ice_role_(ICEROLE_UNKNOWN), |
- tiebreaker_(0), |
- remote_candidate_generation_(0), |
- check_receiving_delay_(MIN_CHECK_RECEIVING_DELAY * 5), |
- receiving_timeout_(MIN_CHECK_RECEIVING_DELAY * 50) { |
-} |
+ PortAllocator* allocator) |
+ : TransportChannelImpl(content_name, component), |
+ transport_(transport), |
+ allocator_(allocator), |
+ worker_thread_(rtc::Thread::Current()), |
+ incoming_only_(false), |
+ waiting_for_signaling_(false), |
+ error_(0), |
+ best_connection_(nullptr), |
+ pending_best_connection_(nullptr), |
+ sort_dirty_(false), |
+ was_writable_(false), |
+ remote_ice_mode_(ICEMODE_FULL), |
+ ice_role_(ICEROLE_UNKNOWN), |
+ tiebreaker_(0), |
+ remote_candidate_generation_(0), |
+ check_receiving_delay_(MIN_CHECK_RECEIVING_DELAY * 5), |
+ receiving_timeout_(MIN_CHECK_RECEIVING_DELAY * 50), |
+ last_ping_sent_(0) {} |
P2PTransportChannel::~P2PTransportChannel() { |
ASSERT(worker_thread_ == rtc::Thread::Current()); |
@@ -256,23 +283,18 @@ void P2PTransportChannel::SetIceTiebreaker(uint64 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. |
+// more than one connection per Network that is not pruned. |
+// If all connections are pruned, declare transport failure. |
TransportChannelState P2PTransportChannel::GetState() const { |
- std::set<rtc::Network*> networks; |
- |
- if (connections_.size() == 0) { |
- return TransportChannelState::STATE_FAILED; |
- } |
- |
- for (uint32 i = 0; i < connections_.size(); ++i) { |
- rtc::Network* network = connections_[i]->port()->Network(); |
- if (networks.find(network) == networks.end()) { |
- networks.insert(network); |
+ std::set<rtc::Network*> networks_with_unpruned_connections; |
+ for (const Connection* connection : connections_) { |
+ if (connection->pruned()) { |
+ continue; |
+ } |
+ rtc::Network* network = connection->port()->Network(); |
+ if (networks_with_unpruned_connections.find(network) == |
+ networks_with_unpruned_connections.end()) { |
+ networks_with_unpruned_connections.insert(network); |
} else { |
LOG_J(LS_VERBOSE, this) << "Ice not completed yet for this channel as " |
<< network->ToString() |
@@ -280,8 +302,11 @@ TransportChannelState P2PTransportChannel::GetState() const { |
return TransportChannelState::STATE_CONNECTING; |
} |
} |
- LOG_J(LS_VERBOSE, this) << "Ice is completed for this channel."; |
+ if (networks_with_unpruned_connections.size() == 0) { |
+ return TransportChannelState::STATE_FAILED; |
+ } |
+ LOG_J(LS_VERBOSE, this) << "Ice is completed for this channel."; |
return TransportChannelState::STATE_COMPLETED; |
} |
@@ -347,6 +372,9 @@ void P2PTransportChannel::SetReceivingTimeout(int receiving_timeout_ms) { |
} |
LOG(LS_VERBOSE) << "Set ICE receiving timeout to " << receiving_timeout_ |
<< " milliseconds"; |
+ for (Connection* connection : connections_) { |
+ connection->set_receiving_timeout(receiving_timeout_); |
+ } |
} |
// Go into the state of processing candidates, and running in general |
@@ -363,10 +391,7 @@ void P2PTransportChannel::Connect() { |
Allocate(); |
// Start pinging as the ports come in. |
- thread()->Post(this, MSG_PING); |
- |
- thread()->PostDelayed( |
- check_receiving_delay_, this, MSG_CHECK_RECEIVING); |
+ thread()->Post(this, MSG_CHECK_AND_PING); |
} |
// A new port is available, attempt to make connections for it |
@@ -848,6 +873,9 @@ bool P2PTransportChannel::GetStats(ConnectionInfos *infos) { |
std::vector<Connection *>::const_iterator it; |
for (it = connections_.begin(); it != connections_.end(); ++it) { |
Connection *connection = *it; |
+ if (connection->pruned() && !connection->receiving()) { |
+ continue; |
+ } |
ConnectionInfo info; |
info.best_connection = (best_connection_ == connection); |
info.receiving = connection->receiving(); |
@@ -891,10 +919,9 @@ void P2PTransportChannel::Allocate() { |
// Monitor connection states. |
void P2PTransportChannel::UpdateConnectionStates() { |
- uint32 now = rtc::Time(); |
- |
// We need to copy the list of connections since some may delete themselves |
// when we call UpdateState. |
+ uint32 now = rtc::Time(); |
for (uint32 i = 0; i < connections_.size(); ++i) |
connections_[i]->UpdateState(now); |
} |
@@ -925,7 +952,8 @@ void P2PTransportChannel::SortConnections() { |
// need to consider switching to. |
ConnectionCompare cmp; |
std::stable_sort(connections_.begin(), connections_.end(), cmp); |
- LOG(LS_VERBOSE) << "Sorting available connections:"; |
+ LOG(LS_VERBOSE) << "Sorting " << connections_.size() |
+ << " available connections:"; |
for (uint32 i = 0; i < connections_.size(); ++i) { |
LOG(LS_VERBOSE) << connections_[i]->ToString(); |
} |
@@ -936,10 +964,7 @@ void P2PTransportChannel::SortConnections() { |
// If necessary, switch to the new choice. |
// Note that |top_connection| doesn't have to be writable to become the best |
// connection although it will have higher priority if it is writable. |
- // The controlled side can switch the best connection only if the current |
- // |best connection_| has not been nominated by the controlling side yet. |
- if ((ice_role_ == ICEROLE_CONTROLLING || !best_nominated_connection()) && |
- ShouldSwitch(best_connection_, top_connection)) { |
+ if (ShouldSwitch(best_connection_, top_connection, ice_role_)) { |
LOG(LS_INFO) << "Switching best connection: " << top_connection->ToString(); |
SwitchBestConnectionTo(top_connection); |
} |
@@ -987,7 +1012,7 @@ void P2PTransportChannel::PruneConnections() { |
// which point, we would prune out the current best connection). We leave |
// connections on other networks because they may not be using the same |
// resources and they may represent very distinct paths over which we can |
- // switch. If the |primier| connection is not connected, we may be |
+ // switch. If the |premier| connection is not connected, we may be |
// reconnecting a TCP connection and temporarily do not prune connections in |
// this network. See the big comment in CompareConnections. |
@@ -997,14 +1022,14 @@ void P2PTransportChannel::PruneConnections() { |
networks.insert(conn->port()->Network()); |
} |
for (rtc::Network* network : networks) { |
- Connection* primier = GetBestConnectionOnNetwork(network); |
- if (!(primier && primier->writable() && primier->connected())) { |
+ Connection* premier = GetBestConnectionOnNetwork(network); |
+ if (!(premier && premier->writable() && premier->connected())) { |
continue; |
} |
- |
+ premier->Unprune(); // Un-prune the premier connection. |
for (Connection* conn : connections_) { |
- if ((conn != primier) && (conn->port()->Network() == network) && |
- (CompareConnectionCandidates(primier, conn) >= 0)) { |
+ if ((conn != premier) && (conn->port()->Network() == network) && |
+ (CompareConnectionCandidates(premier, conn) >= 0)) { |
conn->Prune(); |
} |
} |
@@ -1034,15 +1059,15 @@ void P2PTransportChannel::SwitchBestConnectionTo(Connection* conn) { |
void P2PTransportChannel::UpdateChannelState() { |
// The Handle* functions already set the writable state. We'll just double- |
// check it here. |
- bool writable = ((best_connection_ != NULL) && |
- (best_connection_->write_state() == |
- Connection::STATE_WRITABLE)); |
+ bool writable = best_connection_ && best_connection_->writable(); |
ASSERT(writable == this->writable()); |
if (writable != this->writable()) |
LOG(LS_ERROR) << "UpdateChannelState: writable state mismatch"; |
- // TODO(honghaiz): The channel receiving state is set in OnCheckReceiving. |
- // Will revisit in a subsequent code change. |
+ bool receiving = |
+ std::any_of(connections_.begin(), connections_.end(), |
+ [](const Connection* conn) { return conn->receiving(); }); |
+ set_receiving(receiving); |
} |
// We checked the status of our connections and we had at least one that |
@@ -1075,6 +1100,10 @@ void P2PTransportChannel::HandleAllTimedOut() { |
HandleNotWritable(); |
} |
+bool P2PTransportChannel::Weak() const { |
+ return !(best_connection_ && best_connection_->receiving() && writable()); |
+} |
+ |
// If we have a best connection, return it, otherwise return top one in the |
// list (later we will mark it best). |
Connection* P2PTransportChannel::GetBestConnectionOnNetwork( |
@@ -1098,11 +1127,8 @@ void P2PTransportChannel::OnMessage(rtc::Message *pmsg) { |
case MSG_SORT: |
OnSort(); |
break; |
- case MSG_PING: |
- OnPing(); |
- break; |
- case MSG_CHECK_RECEIVING: |
- OnCheckReceiving(); |
+ case MSG_CHECK_AND_PING: |
+ OnCheckAndPing(); |
break; |
default: |
ASSERT(false); |
@@ -1116,30 +1142,22 @@ void P2PTransportChannel::OnSort() { |
SortConnections(); |
} |
-// Handle queued up ping request |
-void P2PTransportChannel::OnPing() { |
+// Handle queued up check-and-ping request |
+void P2PTransportChannel::OnCheckAndPing() { |
// Make sure the states of the connections are up-to-date (since this affects |
// which ones are pingable). |
UpdateConnectionStates(); |
- |
- // Find the oldest pingable connection and have it do a ping. |
- Connection* conn = FindNextPingableConnection(); |
- if (conn) |
- PingConnection(conn); |
- |
- // Post ourselves a message to perform the next ping. |
- uint32 delay = writable() ? WRITABLE_DELAY : UNWRITABLE_DELAY; |
- thread()->PostDelayed(delay, this, MSG_PING); |
-} |
- |
-void P2PTransportChannel::OnCheckReceiving() { |
- if (best_connection_) { |
- bool receiving = rtc::Time() <= |
- best_connection_->last_received() + receiving_timeout_; |
- set_receiving(receiving); |
+ // 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; |
+ if (rtc::Time() >= last_ping_sent_ + ping_delay) { |
+ Connection* conn = FindNextPingableConnection(); |
+ if (conn) { |
+ PingConnection(conn); |
+ } |
} |
- |
- thread()->PostDelayed(check_receiving_delay_, this, MSG_CHECK_RECEIVING); |
+ int check_delay = std::min(ping_delay, check_receiving_delay_); |
+ thread()->PostDelayed(check_delay, this, MSG_CHECK_AND_PING); |
} |
// Is the connection in a state for us to even consider pinging the other side? |
@@ -1161,9 +1179,9 @@ bool P2PTransportChannel::IsPingable(Connection* conn) { |
return false; |
} |
- // If the channel is not writable, ping all candidates. Otherwise, we only |
- // want to ping connections that have not timed out on writing. |
- return !writable() || conn->write_state() != Connection::STATE_WRITE_TIMEOUT; |
+ // If the channel is weak, ping all candidates. Otherwise, ping only |
+ // un-pruned and not-write-timed-out candidates. |
+ return Weak() || (!conn->pruned() && !conn->write_timed_out()); |
} |
// Returns the next pingable connection to ping. This will be the oldest |
@@ -1175,7 +1193,7 @@ bool P2PTransportChannel::IsPingable(Connection* conn) { |
Connection* P2PTransportChannel::FindNextPingableConnection() { |
uint32 now = rtc::Time(); |
if (best_connection_ && best_connection_->connected() && |
- (best_connection_->write_state() == Connection::STATE_WRITABLE) && |
+ best_connection_->writable() && |
(best_connection_->last_ping_sent() + MAX_CURRENT_WRITABLE_DELAY <= |
now)) { |
return best_connection_; |
@@ -1236,7 +1254,8 @@ void P2PTransportChannel::PingConnection(Connection* conn) { |
use_candidate = best_connection_->writable(); |
} |
conn->set_use_candidate_attr(use_candidate); |
- conn->Ping(rtc::Time()); |
+ last_ping_sent_ = rtc::Time(); |
+ conn->Ping(last_ping_sent_); |
} |
// When a connection's state changes, we need to figure out who to use as |
@@ -1323,10 +1342,13 @@ void P2PTransportChannel::OnReadPacket( |
// Let the client know of an incoming packet |
SignalReadPacket(this, data, len, packet_time, 0); |
- // May need to switch the sending connection based on the receiving media path |
- // if this is the controlled side. |
- if (ice_role_ == ICEROLE_CONTROLLED && !best_nominated_connection() && |
- connection->writable() && best_connection_ != connection) { |
+ // If the receiving connection is different from the sending connection, |
+ // may need to switch the sending connection based on the receiving media |
+ // path if this is the controlled side. It will not switch if the sending |
+ // connection is nominated but the receiving connection is not nominated. |
+ if (best_connection_ != connection && ice_role_ == ICEROLE_CONTROLLED && |
+ (!best_nominated_connection() || connection->nominated()) && |
+ connection->writable()) { |
SwitchBestConnectionTo(connection); |
} |
} |