Dampening connection switch.

If the currently selected connection becomes not receiving and if a backup connection
becomes strong first, we will not switch the connection until X milliseconds is passed
but the selected connection is still not receiving and the backup connection is still receiving. This will prevent the connection switching from happening too frequently.

BUG=

Review-Url: https://codereview.webrtc.org/2143653005
Cr-Commit-Position: refs/heads/master@{#13480}
This commit is contained in:
honghaiz
2016-07-14 19:30:28 -07:00
committed by Commit bot
parent 73ab917d27
commit 9ad0db51a6
6 changed files with 299 additions and 92 deletions

View File

@ -80,6 +80,8 @@ const int STABLE_WRITABLE_CONNECTION_PING_INTERVAL = 2500; // ms
static const int MIN_CHECK_RECEIVING_INTERVAL = 50; // ms
static const int RECEIVING_SWITCHING_DELAY = 1000; // ms
// We periodically check if any existing networks do not have any connection
// and regather on those networks.
static const int DEFAULT_REGATHER_ON_FAILED_NETWORKS_INTERVAL = 5 * 60 * 1000;
@ -112,7 +114,8 @@ P2PTransportChannel::P2PTransportChannel(const std::string& transport_name,
false /* prioritize_most_likely_candidate_pairs */,
STABLE_WRITABLE_CONNECTION_PING_INTERVAL,
true /* presume_writable_when_fully_relayed */,
DEFAULT_REGATHER_ON_FAILED_NETWORKS_INTERVAL) {
DEFAULT_REGATHER_ON_FAILED_NETWORKS_INTERVAL,
RECEIVING_SWITCHING_DELAY) {
uint32_t weak_ping_interval = ::strtoul(
webrtc::field_trial::FindFullName("WebRTC-StunInterPacketDelay").c_str(),
nullptr, 10);
@ -184,7 +187,8 @@ void P2PTransportChannel::AddConnection(Connection* connection) {
// TODO(honghaiz): Stop the aggressive nomination on the controlling side and
// implement the ice-renomination option.
bool P2PTransportChannel::ShouldSwitchSelectedConnection(
Connection* new_connection) const {
Connection* new_connection,
bool* missed_receiving_unchanged_threshold) const {
if (!new_connection || selected_connection_ == new_connection) {
return false;
}
@ -193,7 +197,11 @@ bool P2PTransportChannel::ShouldSwitchSelectedConnection(
return true;
}
int cmp = CompareConnections(selected_connection_, new_connection);
rtc::Optional<int64_t> receiving_unchanged_threshold(
rtc::TimeMillis() - config_.receiving_switching_delay.value_or(0));
int cmp = CompareConnections(selected_connection_, new_connection,
receiving_unchanged_threshold,
missed_receiving_unchanged_threshold);
if (cmp != 0) {
return cmp < 0;
}
@ -203,6 +211,28 @@ bool P2PTransportChannel::ShouldSwitchSelectedConnection(
return new_connection->rtt() <= selected_connection_->rtt() - kMinImprovement;
}
bool P2PTransportChannel::MaybeSwitchSelectedConnection(
Connection* new_connection,
const std::string& reason) {
bool missed_receiving_unchanged_threshold = false;
if (ShouldSwitchSelectedConnection(new_connection,
&missed_receiving_unchanged_threshold)) {
LOG(LS_INFO) << "Switching selected connection due to " << reason;
SwitchSelectedConnection(new_connection);
return true;
}
if (missed_receiving_unchanged_threshold &&
config_.receiving_switching_delay) {
// If we do not switch to the connection because it missed the receiving
// threshold, the new connection is in a better receiving state than the
// currently selected connection. So we need to re-check whether it needs
// to be switched at a later time.
thread()->PostDelayed(RTC_FROM_HERE, *config_.receiving_switching_delay,
this, MSG_SORT_AND_UPDATE_STATE);
}
return false;
}
void P2PTransportChannel::SetIceRole(IceRole ice_role) {
ASSERT(worker_thread_ == rtc::Thread::Current());
if (ice_role_ != ice_role) {
@ -369,6 +399,11 @@ void P2PTransportChannel::SetIceConfig(const IceConfig& config) {
LOG(LS_INFO) << "Set regather_on_failed_networks_interval to "
<< *config_.regather_on_failed_networks_interval;
}
if (config.receiving_switching_delay) {
config_.receiving_switching_delay = config.receiving_switching_delay;
LOG(LS_INFO) << "Set receiving_switching_delay to"
<< *config_.receiving_switching_delay;
}
}
const IceConfig& P2PTransportChannel::config() const {
@ -627,20 +662,16 @@ void P2PTransportChannel::OnNominated(Connection* conn) {
return;
}
if (!ShouldSwitchSelectedConnection(conn)) {
if (MaybeSwitchSelectedConnection(conn,
"nomination on the controlled side")) {
// Now that we have selected a connection, it is time to prune other
// connections and update the read/write state of the channel.
RequestSortAndStateUpdate();
} else {
LOG(LS_INFO)
<< "Not switching the selected connection on controlled side yet: "
<< conn->ToString();
return;
}
LOG(LS_INFO)
<< "Switching selected connection on controlled side due to nomination: "
<< conn->ToString();
SwitchSelectedConnection(conn);
// Now that we have selected a connection, it is time to prune other
// connections and update the read/write state of the channel.
RequestSortAndStateUpdate();
}
void P2PTransportChannel::AddRemoteCandidate(const Candidate& candidate) {
@ -987,8 +1018,11 @@ void P2PTransportChannel::MaybeStartPinging() {
// Compare two connections based on their writing, receiving, and connected
// states.
int P2PTransportChannel::CompareConnectionStates(const Connection* a,
const Connection* b) const {
int P2PTransportChannel::CompareConnectionStates(
const Connection* a,
const Connection* b,
rtc::Optional<int64_t> receiving_unchanged_threshold,
bool* missed_receiving_unchanged_threshold) const {
// First, prefer a connection that's writable or presumed writable over
// one that's not writable.
bool a_writable = a->writable() || PresumedWritable(a);
@ -1015,7 +1049,12 @@ int P2PTransportChannel::CompareConnectionStates(const Connection* a,
return a_is_better;
}
if (!a->receiving() && b->receiving()) {
return b_is_better;
if (!receiving_unchanged_threshold ||
(a->receiving_unchanged_since() <= *receiving_unchanged_threshold &&
b->receiving_unchanged_since() <= *receiving_unchanged_threshold)) {
return b_is_better;
}
*missed_receiving_unchanged_threshold = true;
}
// WARNING: Some complexity here about TCP reconnecting.
@ -1082,15 +1121,19 @@ int P2PTransportChannel::CompareConnectionCandidates(
(b->remote_candidate().generation() + b->port()->generation());
}
int P2PTransportChannel::CompareConnections(const Connection* a,
const Connection* b) const {
int P2PTransportChannel::CompareConnections(
const Connection* a,
const Connection* b,
rtc::Optional<int64_t> receiving_unchanged_threshold,
bool* missed_receiving_unchanged_threshold) const {
RTC_CHECK(a != nullptr);
RTC_CHECK(b != nullptr);
// We prefer to switch to a writable and receiving connection over a
// non-writable or non-receiving connection, even if the latter has
// been nominated by the controlling side.
int state_cmp = CompareConnectionStates(a, b);
int state_cmp = CompareConnectionStates(a, b, receiving_unchanged_threshold,
missed_receiving_unchanged_threshold);
if (state_cmp != 0) {
return state_cmp;
}
@ -1143,11 +1186,11 @@ void P2PTransportChannel::SortConnectionsAndUpdateState() {
// need to consider switching to.
std::stable_sort(connections_.begin(), connections_.end(),
[this](const Connection* a, const Connection* b) {
int cmp = CompareConnections(a, b);
int cmp = CompareConnections(
a, b, rtc::Optional<int64_t>(), nullptr);
if (cmp != 0) {
return cmp > 0;
}
// Otherwise, sort based on latency estimate.
return a->rtt() < b->rtt();
});
@ -1164,11 +1207,7 @@ void P2PTransportChannel::SortConnectionsAndUpdateState() {
// If necessary, switch to the new choice. Note that |top_connection| doesn't
// have to be writable to become the selected connection although it will
// have higher priority if it is writable.
if (ShouldSwitchSelectedConnection(top_connection)) {
LOG(LS_INFO) << "Switching selected connection after sorting: "
<< top_connection->ToString();
SwitchSelectedConnection(top_connection);
}
MaybeSwitchSelectedConnection(top_connection, "sorting");
// The controlled side can prune only if the selected connection has been
// nominated because otherwise it may prune the connection that will be
@ -1622,7 +1661,7 @@ void P2PTransportChannel::OnConnectionDestroyed(Connection* connection) {
// we can just set selected to nullptr and re-choose a best assuming that
// there was no selected connection.
if (selected_connection_ == connection) {
LOG(LS_INFO) << "selected connection destroyed. Will choose a new one.";
LOG(LS_INFO) << "Selected connection destroyed. Will choose a new one.";
SwitchSelectedConnection(nullptr);
RequestSortAndStateUpdate();
} else {
@ -1726,11 +1765,8 @@ void P2PTransportChannel::OnReadPacket(Connection* connection,
// May need to switch the sending connection based on the receiving media path
// if this is the controlled side.
if (ice_role_ == ICEROLE_CONTROLLED &&
ShouldSwitchSelectedConnection(connection)) {
LOG(LS_INFO) << "Switching selected connection on controlled side due to "
<< "data received: " << connection->ToString();
SwitchSelectedConnection(connection);
if (ice_role_ == ICEROLE_CONTROLLED) {
MaybeSwitchSelectedConnection(connection, "data received");
}
}

View File

@ -208,7 +208,7 @@ class P2PTransportChannel : public TransportChannelImpl,
}
private:
rtc::Thread* thread() { return worker_thread_; }
rtc::Thread* thread() const { return worker_thread_; }
bool IsGettingPorts() { return allocator_session()->IsGettingPorts(); }
// A transport channel is weak if the current best connection is either
@ -222,10 +222,18 @@ class P2PTransportChannel : public TransportChannelImpl,
// that's pingable.
void MaybeStartPinging();
// The methods below return a positive value if a is preferable to b,
// a negative value if b is preferable, and 0 if they're equally preferable.
int CompareConnectionStates(const cricket::Connection* a,
const cricket::Connection* b) const;
// The methods below return a positive value if |a| is preferable to |b|,
// a negative value if |b| is preferable, and 0 if they're equally preferable.
// If |receiving_unchanged_threshold| is set, then when |b| is receiving and
// |a| is not, returns a negative value only if |b| has been in receiving
// state and |a| has been in not receiving state since
// |receiving_unchanged_threshold| and sets
// |missed_receiving_unchanged_threshold| to true otherwise.
int CompareConnectionStates(
const cricket::Connection* a,
const cricket::Connection* b,
rtc::Optional<int64_t> receiving_unchanged_threshold,
bool* missed_receiving_unchanged_threshold) const;
int CompareConnectionCandidates(const cricket::Connection* a,
const cricket::Connection* b) const;
// Compares two connections based on the connection states
@ -234,12 +242,12 @@ class P2PTransportChannel : public TransportChannelImpl,
// and ShouldSwitchSelectedConnection().
// Returns a positive value if |a| is better than |b|.
int CompareConnections(const cricket::Connection* a,
const cricket::Connection* b) const;
const cricket::Connection* b,
rtc::Optional<int64_t> receiving_unchanged_threshold,
bool* missed_receiving_unchanged_threshold) const;
bool PresumedWritable(const cricket::Connection* conn) const;
bool ShouldSwitchSelectedConnection(const cricket::Connection* selected,
const cricket::Connection* conn) const;
void SortConnectionsAndUpdateState();
void SwitchSelectedConnection(Connection* conn);
void UpdateState();
@ -306,8 +314,16 @@ class P2PTransportChannel : public TransportChannelImpl,
void OnCheckAndPing();
void OnRegatherOnFailedNetworks();
// Returns true if the new_connection should be selected for transmission.
bool ShouldSwitchSelectedConnection(Connection* new_connection) const;
// Returns true if we should switch to the new connection.
// sets |missed_receiving_unchanged_threshold| to true if either
// the selected connection or the new connection missed its
// receiving-unchanged-threshold.
bool ShouldSwitchSelectedConnection(
Connection* new_connection,
bool* missed_receiving_unchanged_threshold) const;
// Returns true if the new_connection is selected for transmission.
bool MaybeSwitchSelectedConnection(Connection* new_connection,
const std::string& reason);
void PruneConnections();
bool IsBackupConnection(const Connection* conn) const;

View File

@ -350,6 +350,8 @@ class P2PTransportChannelTestBase : public testing::Test,
this, &P2PTransportChannelTestBase::OnReadPacket);
channel->SignalRoleConflict.connect(
this, &P2PTransportChannelTestBase::OnRoleConflict);
channel->SignalSelectedCandidatePairChanged.connect(
this, &P2PTransportChannelTestBase::OnSelectedCandidatePairChanged);
channel->SetIceCredentials(local_ice_ufrag, local_ice_pwd);
if (remote_ice_credential_source_ == FROM_SETICECREDENTIALS) {
channel->SetRemoteIceCredentials(remote_ice_ufrag, remote_ice_pwd);
@ -688,6 +690,19 @@ class P2PTransportChannelTestBase : public testing::Test,
new CandidatesData(ch, c));
}
}
void OnSelectedCandidatePairChanged(
TransportChannel* transport_channel,
CandidatePairInterface* selected_candidate_pair,
int last_sent_packet_id,
bool ready_to_send) {
++selected_candidate_pair_switches_;
}
int reset_selected_candidate_pair_switches() {
int switches = selected_candidate_pair_switches_;
selected_candidate_pair_switches_ = 0;
return switches;
}
void PauseCandidates(int endpoint) {
GetEndpoint(endpoint)->save_candidates_ = true;
@ -850,6 +865,7 @@ class P2PTransportChannelTestBase : public testing::Test,
Endpoint ep2_;
RemoteIceCredentialSource remote_ice_credential_source_ = FROM_CANDIDATE;
bool force_relay_;
int selected_candidate_pair_switches_ = 0;
};
// The tests have only a few outcomes, which we predefine.
@ -1867,6 +1883,7 @@ TEST_F(P2PTransportChannelMultihomedTest, DISABLED_TestBasic) {
// Test that we can quickly switch links if an interface goes down.
// The controlled side has two interfaces and one will die.
TEST_F(P2PTransportChannelMultihomedTest, TestFailoverControlledSide) {
rtc::ScopedFakeClock clock;
AddAddress(0, kPublicAddrs[0]);
// Adding alternate address will make sure |kPublicAddrs| has the higher
// priority than others. This is due to FakeNetwork::AddInterface method.
@ -1880,9 +1897,10 @@ TEST_F(P2PTransportChannelMultihomedTest, TestFailoverControlledSide) {
// Create channels and let them go writable, as usual.
CreateChannels(1);
EXPECT_TRUE_WAIT_MARGIN(ep1_ch1()->receiving() && ep1_ch1()->writable() &&
ep2_ch1()->receiving() && ep2_ch1()->writable(),
1000, 1000);
EXPECT_TRUE_SIMULATED_WAIT(ep1_ch1()->receiving() && ep1_ch1()->writable() &&
ep2_ch1()->receiving() &&
ep2_ch1()->writable(),
3000, clock);
EXPECT_TRUE(ep1_ch1()->selected_connection() &&
ep2_ch1()->selected_connection() &&
LocalCandidate(ep1_ch1())->address().EqualIPs(kPublicAddrs[0]) &&
@ -1896,19 +1914,19 @@ TEST_F(P2PTransportChannelMultihomedTest, TestFailoverControlledSide) {
// Blackhole any traffic to or from the public addrs.
LOG(LS_INFO) << "Failing over...";
fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, kPublicAddrs[1]);
// The selected connections will switch, so keep references to them.
// The selected connections may switch, so keep references to them.
const Connection* selected_connection1 = ep1_ch1()->selected_connection();
const Connection* selected_connection2 = ep2_ch1()->selected_connection();
// We should detect loss of receiving within 1 second or so.
EXPECT_TRUE_WAIT(
EXPECT_TRUE_SIMULATED_WAIT(
!selected_connection1->receiving() && !selected_connection2->receiving(),
3000);
3000, clock);
// We should switch over to use the alternate addr immediately on both sides
// We should switch over to use the alternate addr on both sides
// when we are not receiving.
EXPECT_TRUE_WAIT(ep1_ch1()->selected_connection()->receiving() &&
ep2_ch1()->selected_connection()->receiving(),
1000);
EXPECT_TRUE_SIMULATED_WAIT(ep1_ch1()->selected_connection()->receiving() &&
ep2_ch1()->selected_connection()->receiving(),
3000, clock);
EXPECT_TRUE(LocalCandidate(ep1_ch1())->address().EqualIPs(kPublicAddrs[0]));
EXPECT_TRUE(
RemoteCandidate(ep1_ch1())->address().EqualIPs(kAlternateAddrs[1]));
@ -1921,6 +1939,7 @@ TEST_F(P2PTransportChannelMultihomedTest, TestFailoverControlledSide) {
// Test that we can quickly switch links if an interface goes down.
// The controlling side has two interfaces and one will die.
TEST_F(P2PTransportChannelMultihomedTest, TestFailoverControllingSide) {
rtc::ScopedFakeClock clock;
// Adding alternate address will make sure |kPublicAddrs| has the higher
// priority than others. This is due to FakeNetwork::AddInterface method.
AddAddress(0, kAlternateAddrs[0]);
@ -1933,9 +1952,10 @@ TEST_F(P2PTransportChannelMultihomedTest, TestFailoverControllingSide) {
// Create channels and let them go writable, as usual.
CreateChannels(1);
EXPECT_TRUE_WAIT_MARGIN(ep1_ch1()->receiving() && ep1_ch1()->writable() &&
ep2_ch1()->receiving() && ep2_ch1()->writable(),
1000, 1000);
EXPECT_TRUE_SIMULATED_WAIT(ep1_ch1()->receiving() && ep1_ch1()->writable() &&
ep2_ch1()->receiving() &&
ep2_ch1()->writable(),
3000, clock);
EXPECT_TRUE(ep1_ch1()->selected_connection() &&
ep2_ch1()->selected_connection() &&
LocalCandidate(ep1_ch1())->address().EqualIPs(kPublicAddrs[0]) &&
@ -1953,15 +1973,15 @@ TEST_F(P2PTransportChannelMultihomedTest, TestFailoverControllingSide) {
const Connection* selected_connection1 = ep1_ch1()->selected_connection();
const Connection* selected_connection2 = ep2_ch1()->selected_connection();
// We should detect loss of receiving within 1 second or so.
EXPECT_TRUE_WAIT(
EXPECT_TRUE_SIMULATED_WAIT(
!selected_connection1->receiving() && !selected_connection2->receiving(),
3000);
3000, clock);
// We should switch over to use the alternate addr immediately on both sides
// We should switch over to use the alternate addr on both sides
// when we are not receiving.
EXPECT_TRUE_WAIT(ep1_ch1()->selected_connection()->receiving() &&
ep2_ch1()->selected_connection()->receiving(),
1000);
EXPECT_TRUE_SIMULATED_WAIT(ep1_ch1()->selected_connection()->receiving() &&
ep2_ch1()->selected_connection()->receiving(),
3000, clock);
EXPECT_TRUE(
LocalCandidate(ep1_ch1())->address().EqualIPs(kAlternateAddrs[0]));
EXPECT_TRUE(RemoteCandidate(ep1_ch1())->address().EqualIPs(kPublicAddrs[1]));
@ -1971,6 +1991,126 @@ TEST_F(P2PTransportChannelMultihomedTest, TestFailoverControllingSide) {
DestroyChannels();
}
// Test that if an interface fails temporarily and then recovers quickly,
// the selected connection will not switch.
// The case that it will switch over to the backup connection if the selected
// connection does not recover after enough time is covered in
// TestFailoverControlledSide and TestFailoverControllingSide.
TEST_F(P2PTransportChannelMultihomedTest,
TestConnectionSwitchDampeningControlledSide) {
rtc::ScopedFakeClock clock;
AddAddress(0, kPublicAddrs[0]);
// Adding alternate address will make sure |kPublicAddrs| has the higher
// priority than others. This is due to FakeNetwork::AddInterface method.
AddAddress(1, kAlternateAddrs[1]);
AddAddress(1, kPublicAddrs[1]);
// Use only local ports for simplicity.
SetAllocatorFlags(0, kOnlyLocalPorts);
SetAllocatorFlags(1, kOnlyLocalPorts);
// Create channels and let them go writable, as usual.
CreateChannels(1);
EXPECT_TRUE_SIMULATED_WAIT(ep1_ch1()->receiving() && ep1_ch1()->writable() &&
ep2_ch1()->receiving() &&
ep2_ch1()->writable(),
3000, clock);
EXPECT_TRUE(ep1_ch1()->selected_connection() &&
ep2_ch1()->selected_connection() &&
LocalCandidate(ep1_ch1())->address().EqualIPs(kPublicAddrs[0]) &&
RemoteCandidate(ep1_ch1())->address().EqualIPs(kPublicAddrs[1]));
// Make the receiving timeout shorter for testing.
IceConfig config = CreateIceConfig(1000, GATHER_ONCE);
ep1_ch1()->SetIceConfig(config);
ep2_ch1()->SetIceConfig(config);
reset_selected_candidate_pair_switches();
// Blackhole any traffic to or from the public addrs.
LOG(LS_INFO) << "Failing over...";
fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, kPublicAddrs[1]);
// The selected connections may switch, so keep references to them.
const Connection* selected_connection1 = ep1_ch1()->selected_connection();
const Connection* selected_connection2 = ep2_ch1()->selected_connection();
// We should detect loss of receiving within 1 second or so.
EXPECT_TRUE_SIMULATED_WAIT(
!selected_connection1->receiving() && !selected_connection2->receiving(),
3000, clock);
// After a short while, the link recovers itself.
SIMULATED_WAIT(false, 10, clock);
fw()->ClearRules();
// We should remain on the public address on both sides and no connection
// switches should have happened.
EXPECT_TRUE_SIMULATED_WAIT(ep1_ch1()->selected_connection()->receiving() &&
ep2_ch1()->selected_connection()->receiving(),
3000, clock);
EXPECT_TRUE(RemoteCandidate(ep1_ch1())->address().EqualIPs(kPublicAddrs[1]));
EXPECT_TRUE(LocalCandidate(ep2_ch1())->address().EqualIPs(kPublicAddrs[1]));
EXPECT_EQ(0, reset_selected_candidate_pair_switches());
DestroyChannels();
}
// Test that if an interface fails temporarily and then recovers quickly,
// the selected connection will not switch.
TEST_F(P2PTransportChannelMultihomedTest,
TestConnectionSwitchDampeningControllingSide) {
rtc::ScopedFakeClock clock;
// Adding alternate address will make sure |kPublicAddrs| has the higher
// priority than others. This is due to FakeNetwork::AddInterface method.
AddAddress(0, kAlternateAddrs[0]);
AddAddress(0, kPublicAddrs[0]);
AddAddress(1, kPublicAddrs[1]);
// Use only local ports for simplicity.
SetAllocatorFlags(0, kOnlyLocalPorts);
SetAllocatorFlags(1, kOnlyLocalPorts);
// Create channels and let them go writable, as usual.
CreateChannels(1);
EXPECT_TRUE_SIMULATED_WAIT(ep1_ch1()->receiving() && ep1_ch1()->writable() &&
ep2_ch1()->receiving() &&
ep2_ch1()->writable(),
3000, clock);
EXPECT_TRUE(ep1_ch1()->selected_connection() &&
ep2_ch1()->selected_connection() &&
LocalCandidate(ep1_ch1())->address().EqualIPs(kPublicAddrs[0]) &&
RemoteCandidate(ep1_ch1())->address().EqualIPs(kPublicAddrs[1]));
// Make the receiving timeout shorter for testing.
IceConfig config = CreateIceConfig(1000, GATHER_ONCE);
ep1_ch1()->SetIceConfig(config);
ep2_ch1()->SetIceConfig(config);
reset_selected_candidate_pair_switches();
// Blackhole any traffic to or from the public addrs.
LOG(LS_INFO) << "Failing over...";
fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, kPublicAddrs[0]);
// The selected connections may switch, so keep references to them.
const Connection* selected_connection1 = ep1_ch1()->selected_connection();
const Connection* selected_connection2 = ep2_ch1()->selected_connection();
// We should detect loss of receiving within 1 second or so.
EXPECT_TRUE_SIMULATED_WAIT(
!selected_connection1->receiving() && !selected_connection2->receiving(),
3000, clock);
// The link recovers after a short while.
SIMULATED_WAIT(false, 10, clock);
fw()->ClearRules();
// We should not switch to the alternate addr on both sides because of the
// dampening.
EXPECT_TRUE_SIMULATED_WAIT(ep1_ch1()->selected_connection()->receiving() &&
ep2_ch1()->selected_connection()->receiving(),
3000, clock);
EXPECT_TRUE(LocalCandidate(ep1_ch1())->address().EqualIPs(kPublicAddrs[0]));
EXPECT_TRUE(RemoteCandidate(ep2_ch1())->address().EqualIPs(kPublicAddrs[0]));
EXPECT_EQ(0, reset_selected_candidate_pair_switches());
DestroyChannels();
}
// Tests that a Wifi-Wifi connection has the highest precedence.
TEST_F(P2PTransportChannelMultihomedTest, TestPreferWifiToWifiConnection) {
// The interface names are chosen so that |cellular| would have higher
@ -2448,7 +2588,7 @@ class P2PTransportChannelPingTest : public testing::Test,
bool channel_ready_to_send() { return channel_ready_to_send_; }
void reset_channel_ready_to_send() { channel_ready_to_send_ = false; }
TransportChannelState channel_state() { return channel_state_; }
int get_and_reset_selected_candidate_pair_switches() {
int reset_selected_candidate_pair_switches() {
int switches = selected_candidate_pair_switches_;
selected_candidate_pair_switches_ = 0;
return switches;
@ -3055,7 +3195,7 @@ TEST_F(P2PTransportChannelPingTest,
ASSERT_TRUE(conn2 != nullptr);
// Initially, connections are selected based on priority.
EXPECT_EQ(1, get_and_reset_selected_candidate_pair_switches());
EXPECT_EQ(1, reset_selected_candidate_pair_switches());
EXPECT_EQ(conn1, last_selected_candidate_pair());
// conn2 receives data; it becomes selected.
@ -3063,17 +3203,17 @@ TEST_F(P2PTransportChannelPingTest,
// conn2 is larger.
SIMULATED_WAIT(false, 1, clock);
conn2->OnReadPacket("XYZ", 3, rtc::CreatePacketTime(0));
EXPECT_EQ(1, get_and_reset_selected_candidate_pair_switches());
EXPECT_EQ(1, reset_selected_candidate_pair_switches());
EXPECT_EQ(conn2, last_selected_candidate_pair());
// conn1 also receives data; it becomes selected due to priority again.
conn1->OnReadPacket("XYZ", 3, rtc::CreatePacketTime(0));
EXPECT_EQ(1, get_and_reset_selected_candidate_pair_switches());
EXPECT_EQ(1, reset_selected_candidate_pair_switches());
EXPECT_EQ(conn1, last_selected_candidate_pair());
// Make sure sorting won't reselect candidate pair.
SIMULATED_WAIT(false, 10, clock);
EXPECT_EQ(0, get_and_reset_selected_candidate_pair_switches());
EXPECT_EQ(0, reset_selected_candidate_pair_switches());
}
TEST_F(P2PTransportChannelPingTest,
@ -3097,28 +3237,28 @@ TEST_F(P2PTransportChannelPingTest,
// Advance the clock to have a non-zero last-data-receiving time.
SIMULATED_WAIT(false, 1, clock);
conn1->OnReadPacket("XYZ", 3, rtc::CreatePacketTime(0));
EXPECT_EQ(1, get_and_reset_selected_candidate_pair_switches());
EXPECT_EQ(1, reset_selected_candidate_pair_switches());
EXPECT_EQ(conn1, last_selected_candidate_pair());
// conn2 is nominated; it becomes the selected connection.
NominateConnection(conn2);
EXPECT_EQ(1, get_and_reset_selected_candidate_pair_switches());
EXPECT_EQ(1, reset_selected_candidate_pair_switches());
EXPECT_EQ(conn2, last_selected_candidate_pair());
NominateConnection(conn1);
EXPECT_EQ(1, get_and_reset_selected_candidate_pair_switches());
EXPECT_EQ(1, reset_selected_candidate_pair_switches());
EXPECT_EQ(conn1, last_selected_candidate_pair());
// conn2 received data more recently; it is selected now because it
// received data more recently.
SIMULATED_WAIT(false, 1, clock);
conn2->OnReadPacket("XYZ", 3, rtc::CreatePacketTime(0));
EXPECT_EQ(1, get_and_reset_selected_candidate_pair_switches());
EXPECT_EQ(1, reset_selected_candidate_pair_switches());
EXPECT_EQ(conn2, last_selected_candidate_pair());
// Make sure sorting won't reselect candidate pair.
SIMULATED_WAIT(false, 10, clock);
EXPECT_EQ(0, get_and_reset_selected_candidate_pair_switches());
EXPECT_EQ(0, reset_selected_candidate_pair_switches());
}
TEST_F(P2PTransportChannelPingTest,
@ -3139,26 +3279,26 @@ TEST_F(P2PTransportChannelPingTest,
ASSERT_TRUE(conn2 != nullptr);
NominateConnection(conn1);
EXPECT_EQ(1, get_and_reset_selected_candidate_pair_switches());
EXPECT_EQ(1, reset_selected_candidate_pair_switches());
// conn2 becomes writable; it is selected even though it is not nominated.
conn2->ReceivedPingResponse(LOW_RTT);
EXPECT_EQ_SIMULATED_WAIT(1, get_and_reset_selected_candidate_pair_switches(),
EXPECT_EQ_SIMULATED_WAIT(1, reset_selected_candidate_pair_switches(),
kDefaultTimeout, clock);
EXPECT_EQ_SIMULATED_WAIT(conn2, last_selected_candidate_pair(),
kDefaultTimeout, clock);
// If conn1 is also writable, it will become selected.
conn1->ReceivedPingResponse(LOW_RTT);
EXPECT_EQ_SIMULATED_WAIT(1, get_and_reset_selected_candidate_pair_switches(),
EXPECT_EQ_SIMULATED_WAIT(1, reset_selected_candidate_pair_switches(),
kDefaultTimeout, clock);
EXPECT_EQ_SIMULATED_WAIT(conn1, last_selected_candidate_pair(),
kDefaultTimeout, clock);
// Make sure sorting won't reselect candidate pair.
SIMULATED_WAIT(false, 10, clock);
EXPECT_EQ(0, get_and_reset_selected_candidate_pair_switches());
EXPECT_EQ(0, reset_selected_candidate_pair_switches());
}
// Test that if a new remote candidate has the same address and port with
@ -3289,7 +3429,9 @@ TEST_F(P2PTransportChannelPingTest, TestConnectionPrunedAgain) {
FakePortAllocator pa(rtc::Thread::Current(), nullptr);
P2PTransportChannel ch("test channel", 1, &pa);
PrepareChannel(&ch);
ch.SetIceConfig(CreateIceConfig(1000, GATHER_ONCE));
IceConfig config = CreateIceConfig(1000, GATHER_ONCE);
config.receiving_switching_delay = rtc::Optional<int>(800);
ch.SetIceConfig(config);
ch.MaybeStartGathering();
ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 100));
Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1);

View File

@ -898,12 +898,15 @@ void Connection::set_write_state(WriteState value) {
}
}
void Connection::set_receiving(bool value) {
if (value != receiving_) {
LOG_J(LS_VERBOSE, this) << "set_receiving to " << value;
receiving_ = value;
SignalStateChange(this);
void Connection::UpdateReceiving(int64_t now) {
bool receiving = now <= last_received() + receiving_timeout_;
if (receiving_ == receiving) {
return;
}
LOG_J(LS_VERBOSE, this) << "set_receiving to " << receiving;
receiving_ = receiving;
receiving_unchanged_since_ = now;
SignalStateChange(this);
}
void Connection::set_state(State state) {
@ -948,8 +951,8 @@ void Connection::OnReadPacket(
if (!port_->GetStunMessage(data, size, addr, &msg, &remote_ufrag)) {
// The packet did not parse as a valid STUN message
// This is a data packet, pass it along.
set_receiving(true);
last_data_received_ = rtc::TimeMillis();
UpdateReceiving(last_data_received_);
recv_rate_tracker_.AddSamples(size);
SignalReadPacket(this, data, size, packet_time);
@ -1165,10 +1168,8 @@ void Connection::UpdateState(int64_t now) {
set_write_state(STATE_WRITE_TIMEOUT);
}
// Check the receiving state.
int64_t last_recv_time = last_received();
bool receiving = now <= last_recv_time + receiving_timeout_;
set_receiving(receiving);
// Update the receiving state.
UpdateReceiving(now);
if (dead(now)) {
Destroy();
}
@ -1186,8 +1187,8 @@ void Connection::Ping(int64_t now) {
}
void Connection::ReceivedPing() {
set_receiving(true);
last_ping_received_ = rtc::TimeMillis();
UpdateReceiving(last_ping_received_);
}
void Connection::ReceivedPingResponse(int rtt) {
@ -1196,11 +1197,11 @@ void Connection::ReceivedPingResponse(int rtt) {
// So if we're not already, become writable. We may be bringing a pruned
// connection back to life, but if we don't really want it, we can always
// prune it again.
set_receiving(true);
last_ping_response_received_ = rtc::TimeMillis();
UpdateReceiving(last_ping_response_received_);
set_write_state(STATE_WRITABLE);
set_state(STATE_SUCCEEDED);
pings_since_last_response_.clear();
last_ping_response_received_ = rtc::TimeMillis();
rtt_samples_++;
rtt_ = (RTT_RATIO * rtt_ + rtt) / (RTT_RATIO + 1);
}

View File

@ -590,6 +590,10 @@ class Connection : public CandidatePairInterface,
// Returns the last received time of any data, stun request, or stun
// response in milliseconds
int64_t last_received() const;
// Returns the last time when the connection changed its receiving state.
int64_t receiving_unchanged_since() const {
return receiving_unchanged_since_;
}
bool stable(int64_t now) const;
@ -618,7 +622,7 @@ class Connection : public CandidatePairInterface,
// Changes the state and signals if necessary.
void set_write_state(WriteState value);
void set_receiving(bool value);
void UpdateReceiving(int64_t now);
void set_state(State state);
void set_connected(bool value);
@ -648,6 +652,7 @@ class Connection : public CandidatePairInterface,
// side
int64_t last_data_received_;
int64_t last_ping_response_received_;
int64_t receiving_unchanged_since_ = 0;
std::vector<SentPing> pings_since_last_response_;
rtc::RateTracker recv_rate_tracker_;

View File

@ -194,6 +194,11 @@ struct IceConfig {
// active network having no connection on it.
rtc::Optional<int> regather_on_failed_networks_interval;
// The time period in which we will not switch the selected connection
// when a new connection becomes receiving but the selected connection is not
// in case that the selected connection may become receiving soon.
rtc::Optional<int> receiving_switching_delay;
IceConfig() {}
IceConfig(int receiving_timeout_ms,
int backup_connection_ping_interval,
@ -201,7 +206,8 @@ struct IceConfig {
bool prioritize_most_likely_candidate_pairs,
int stable_writable_connection_ping_interval_ms,
bool presume_writable_when_fully_relayed,
int regather_on_failed_networks_interval_ms)
int regather_on_failed_networks_interval_ms,
int receiving_switching_delay_ms)
: receiving_timeout(receiving_timeout_ms),
backup_connection_ping_interval(backup_connection_ping_interval),
continual_gathering_policy(gathering_policy),
@ -212,7 +218,8 @@ struct IceConfig {
presume_writable_when_fully_relayed(
presume_writable_when_fully_relayed),
regather_on_failed_networks_interval(
regather_on_failed_networks_interval_ms) {}
regather_on_failed_networks_interval_ms),
receiving_switching_delay(receiving_switching_delay_ms) {}
};
bool BadTransportDescription(const std::string& desc, std::string* err_desc);