ICE : add field trial for initial select dampening
The existing ICE stack will choose *the first* connection
that becomes writable.
It is possible that waiting a fixed time will choose a
better connection, avoiding a switch, and making the experience better
in total.
This patch is add two field trials to *explore*
that dimension. I.e the code will be rolled back once
experiments has been performed.
- initial_select_dampening, delays selection by X ms.
- initial_select_dampening_ping_received, delays selection for
candidate that has received ping by X ms.
BUG=webrtc:11054
Change-Id: Ifcdde5183f318815e0f5db5802fbf6b542a95f5b
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/158410
Reviewed-by: Honghai Zhang <honghaiz@webrtc.org>
Commit-Queue: Jonas Oreland <jonaso@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#29623}
diff --git a/p2p/base/p2p_transport_channel.cc b/p2p/base/p2p_transport_channel.cc
index f95e7ab..7f2b37a 100644
--- a/p2p/base/p2p_transport_channel.cc
+++ b/p2p/base/p2p_transport_channel.cc
@@ -278,10 +278,80 @@
return new_connection->rtt() <= selected_connection_->rtt() - kMinImprovement;
}
+bool P2PTransportChannel::HandleInitialSelectDampening(
+ Connection* new_connection,
+ const std::string& reason) {
+ RTC_DCHECK_RUN_ON(network_thread_);
+ if (!field_trials_.initial_select_dampening.has_value() &&
+ !field_trials_.initial_select_dampening_ping_received.has_value()) {
+ // experiment not enabled.
+ return true;
+ }
+
+ int64_t now = rtc::TimeMillis();
+ int64_t max_delay = 0;
+ if (new_connection->last_ping_received() > 0 &&
+ field_trials_.initial_select_dampening_ping_received.has_value()) {
+ max_delay = *field_trials_.initial_select_dampening_ping_received;
+ } else if (field_trials_.initial_select_dampening.has_value()) {
+ max_delay = *field_trials_.initial_select_dampening;
+ }
+
+ int64_t start_wait =
+ initial_select_timestamp_ms_ == 0 ? now : initial_select_timestamp_ms_;
+ int64_t max_wait_until = start_wait + max_delay;
+
+ if (now >= max_wait_until) {
+ RTC_LOG(LS_INFO) << "reset initial_select_timestamp_ = "
+ << initial_select_timestamp_ms_
+ << " selection delayed by: " << (now - start_wait) << "ms";
+ initial_select_timestamp_ms_ = 0;
+ return true;
+ }
+
+ // We are not yet ready to select first connection...
+ if (initial_select_timestamp_ms_ == 0) {
+ // Set timestamp on first time...
+ // but run the delayed invokation everytime to
+ // avoid possibility that we miss it.
+ initial_select_timestamp_ms_ = now;
+ RTC_LOG(LS_INFO) << "set initial_select_timestamp_ms_ = "
+ << initial_select_timestamp_ms_;
+ }
+
+ int min_delay = max_delay;
+ if (field_trials_.initial_select_dampening.has_value()) {
+ min_delay = std::min(min_delay, *field_trials_.initial_select_dampening);
+ }
+ if (field_trials_.initial_select_dampening_ping_received.has_value()) {
+ min_delay = std::min(min_delay,
+ *field_trials_.initial_select_dampening_ping_received);
+ }
+
+ const std::string reason_to_sort =
+ reason + " (after initial select dampening interval: " +
+ std::to_string(max_delay) + ")";
+ invoker_.AsyncInvokeDelayed<void>(
+ RTC_FROM_HERE, thread(),
+ rtc::Bind(&P2PTransportChannel::SortConnectionsAndUpdateState, this,
+ reason_to_sort),
+ min_delay);
+ RTC_LOG(LS_INFO) << "delay initial selection up to " << min_delay << "ms";
+ return false;
+}
+
bool P2PTransportChannel::MaybeSwitchSelectedConnection(
Connection* new_connection,
const std::string& reason) {
RTC_DCHECK_RUN_ON(network_thread_);
+
+ if (selected_connection_ == nullptr && ReadyToSend(new_connection)) {
+ if (!HandleInitialSelectDampening(new_connection, reason)) {
+ // Delay the initial selection a while waiting for a better connection.
+ return false;
+ }
+ }
+
bool missed_receiving_unchanged_threshold = false;
if (ShouldSwitchSelectedConnection(new_connection,
&missed_receiving_unchanged_threshold)) {
@@ -699,7 +769,10 @@
webrtc::StructParametersParser::Create(
"skip_relay_to_non_relay_connections",
&field_trials_.skip_relay_to_non_relay_connections,
- "max_outstanding_pings", &field_trials_.max_outstanding_pings)
+ "max_outstanding_pings", &field_trials_.max_outstanding_pings,
+ "initial_select_dampening", &field_trials_.initial_select_dampening,
+ "initial_select_dampening_ping_received",
+ &field_trials_.initial_select_dampening_ping_received)
->Parse(webrtc::field_trial::FindFullName("WebRTC-IceFieldTrials"));
if (field_trials_.skip_relay_to_non_relay_connections) {
@@ -711,6 +784,16 @@
<< *field_trials_.max_outstanding_pings;
}
+ if (field_trials_.initial_select_dampening.has_value()) {
+ RTC_LOG(LS_INFO) << "Set initial_select_dampening: "
+ << *field_trials_.initial_select_dampening;
+ }
+
+ if (field_trials_.initial_select_dampening_ping_received.has_value()) {
+ RTC_LOG(LS_INFO) << "Set initial_select_dampening_ping_received: "
+ << *field_trials_.initial_select_dampening_ping_received;
+ }
+
webrtc::BasicRegatheringController::Config regathering_config(
config_.regather_all_networks_interval_range,
config_.regather_on_failed_networks_interval_or_default());
diff --git a/p2p/base/p2p_transport_channel.h b/p2p/base/p2p_transport_channel.h
index a7a1fbe..7ce0651 100644
--- a/p2p/base/p2p_transport_channel.h
+++ b/p2p/base/p2p_transport_channel.h
@@ -78,6 +78,16 @@
struct IceFieldTrials {
bool skip_relay_to_non_relay_connections = false;
absl::optional<int> max_outstanding_pings;
+
+ // Wait X ms before selecting a connection when having none.
+ // This will make media slower, but will give us chance to find
+ // a better connection before starting.
+ absl::optional<int> initial_select_dampening;
+
+ // If the connection has recevied a ping-request, delay by
+ // maximum this delay. This will make media slower, but will
+ // give us chance to find a better connection before starting.
+ absl::optional<int> initial_select_dampening_ping_received;
};
// P2PTransportChannel manages the candidates and connection process to keep
@@ -418,6 +428,9 @@
// 2. Peer-reflexive remote candidates.
Candidate SanitizeRemoteCandidate(const Candidate& c) const;
+ bool HandleInitialSelectDampening(Connection* new_connection,
+ const std::string& reason);
+
std::string transport_name_ RTC_GUARDED_BY(network_thread_);
int component_ RTC_GUARDED_BY(network_thread_);
PortAllocator* allocator_ RTC_GUARDED_BY(network_thread_);
@@ -508,6 +521,9 @@
IceFieldTrials field_trials_;
+ // Timestamp for when we got the first selectable connection.
+ int64_t initial_select_timestamp_ms_ = 0;
+
RTC_DISALLOW_COPY_AND_ASSIGN(P2PTransportChannel);
};
diff --git a/p2p/base/p2p_transport_channel_unittest.cc b/p2p/base/p2p_transport_channel_unittest.cc
index 0c3474b..3919b3f 100644
--- a/p2p/base/p2p_transport_channel_unittest.cc
+++ b/p2p/base/p2p_transport_channel_unittest.cc
@@ -2142,7 +2142,7 @@
CreateUdpCandidate(RELAY_PORT_TYPE, "2.2.2.2", 2, 0));
// Expect that the TURN-TURN candidate pair will be prioritized since it's
// "probably writable".
- EXPECT_TRUE(ep1_ch1()->selected_connection() != nullptr);
+ EXPECT_TRUE_WAIT(ep1_ch1()->selected_connection() != nullptr, kShortTimeout);
EXPECT_EQ(RELAY_PORT_TYPE, LocalCandidate(ep1_ch1())->type());
EXPECT_EQ(RELAY_PORT_TYPE, RemoteCandidate(ep1_ch1())->type());
// Also expect that the channel instantly indicates that it's writable since
@@ -2265,7 +2265,7 @@
ep1_ch1()->AddRemoteCandidate(
CreateUdpCandidate(RELAY_PORT_TYPE, "1.1.1.1", 1, 0));
// Sanity checking the type of the connection.
- EXPECT_TRUE(ep1_ch1()->selected_connection() != nullptr);
+ EXPECT_TRUE_WAIT(ep1_ch1()->selected_connection() != nullptr, kShortTimeout);
EXPECT_EQ(RELAY_PORT_TYPE, LocalCandidate(ep1_ch1())->type());
EXPECT_EQ(RELAY_PORT_TYPE, RemoteCandidate(ep1_ch1())->type());
@@ -5575,4 +5575,105 @@
test_invariants();
}
+TEST_F(P2PTransportChannelPingTest, TestInitialSelectDampening0) {
+ webrtc::test::ScopedFieldTrials field_trials(
+ "WebRTC-IceFieldTrials/initial_select_dampening:0/");
+
+ constexpr int kMargin = 10;
+ rtc::ScopedFakeClock clock;
+ clock.AdvanceTime(webrtc::TimeDelta::seconds(1));
+
+ FakePortAllocator pa(rtc::Thread::Current(), nullptr);
+ P2PTransportChannel ch("test channel", 1, &pa);
+ PrepareChannel(&ch);
+ ch.SetIceConfig(ch.config());
+ ch.MaybeStartGathering();
+
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 100));
+ Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1, &clock);
+ ASSERT_TRUE(conn1 != nullptr);
+ EXPECT_EQ(nullptr, ch.selected_connection());
+ conn1->ReceivedPingResponse(LOW_RTT, "id"); // Becomes writable and receiving
+ // It shall not be selected until 0ms has passed....i.e it should be connected
+ // directly.
+ EXPECT_EQ_SIMULATED_WAIT(conn1, ch.selected_connection(), kMargin, clock);
+}
+
+TEST_F(P2PTransportChannelPingTest, TestInitialSelectDampening) {
+ webrtc::test::ScopedFieldTrials field_trials(
+ "WebRTC-IceFieldTrials/initial_select_dampening:100/");
+
+ constexpr int kMargin = 10;
+ rtc::ScopedFakeClock clock;
+ clock.AdvanceTime(webrtc::TimeDelta::seconds(1));
+
+ FakePortAllocator pa(rtc::Thread::Current(), nullptr);
+ P2PTransportChannel ch("test channel", 1, &pa);
+ PrepareChannel(&ch);
+ ch.SetIceConfig(ch.config());
+ ch.MaybeStartGathering();
+
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 100));
+ Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1, &clock);
+ ASSERT_TRUE(conn1 != nullptr);
+ EXPECT_EQ(nullptr, ch.selected_connection());
+ conn1->ReceivedPingResponse(LOW_RTT, "id"); // Becomes writable and receiving
+ // It shall not be selected until 100ms has passed.
+ SIMULATED_WAIT(conn1 == ch.selected_connection(), 100 - kMargin, clock);
+ EXPECT_EQ_SIMULATED_WAIT(conn1, ch.selected_connection(), 2 * kMargin, clock);
+}
+
+TEST_F(P2PTransportChannelPingTest, TestInitialSelectDampeningPingReceived) {
+ webrtc::test::ScopedFieldTrials field_trials(
+ "WebRTC-IceFieldTrials/initial_select_dampening_ping_received:100/");
+
+ constexpr int kMargin = 10;
+ rtc::ScopedFakeClock clock;
+ clock.AdvanceTime(webrtc::TimeDelta::seconds(1));
+
+ FakePortAllocator pa(rtc::Thread::Current(), nullptr);
+ P2PTransportChannel ch("test channel", 1, &pa);
+ PrepareChannel(&ch);
+ ch.SetIceConfig(ch.config());
+ ch.MaybeStartGathering();
+
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 100));
+ Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1, &clock);
+ ASSERT_TRUE(conn1 != nullptr);
+ EXPECT_EQ(nullptr, ch.selected_connection());
+ conn1->ReceivedPingResponse(LOW_RTT, "id"); // Becomes writable and receiving
+ conn1->ReceivedPing("id1"); //
+ // It shall not be selected until 100ms has passed.
+ SIMULATED_WAIT(conn1 == ch.selected_connection(), 100 - kMargin, clock);
+ EXPECT_EQ_SIMULATED_WAIT(conn1, ch.selected_connection(), 2 * kMargin, clock);
+}
+
+TEST_F(P2PTransportChannelPingTest, TestInitialSelectDampeningBoth) {
+ webrtc::test::ScopedFieldTrials field_trials(
+ "WebRTC-IceFieldTrials/"
+ "initial_select_dampening:100,initial_select_dampening_ping_received:"
+ "50/");
+
+ constexpr int kMargin = 10;
+ rtc::ScopedFakeClock clock;
+ clock.AdvanceTime(webrtc::TimeDelta::seconds(1));
+
+ FakePortAllocator pa(rtc::Thread::Current(), nullptr);
+ P2PTransportChannel ch("test channel", 1, &pa);
+ PrepareChannel(&ch);
+ ch.SetIceConfig(ch.config());
+ ch.MaybeStartGathering();
+
+ ch.AddRemoteCandidate(CreateUdpCandidate(LOCAL_PORT_TYPE, "1.1.1.1", 1, 100));
+ Connection* conn1 = WaitForConnectionTo(&ch, "1.1.1.1", 1, &clock);
+ ASSERT_TRUE(conn1 != nullptr);
+ EXPECT_EQ(nullptr, ch.selected_connection());
+ conn1->ReceivedPingResponse(LOW_RTT, "id"); // Becomes writable and receiving
+ // It shall not be selected until 100ms has passed....but only wait ~50 now.
+ SIMULATED_WAIT(conn1 == ch.selected_connection(), 50 - kMargin, clock);
+ // Now receiving ping and new timeout should kick in.
+ conn1->ReceivedPing("id1"); //
+ EXPECT_EQ_SIMULATED_WAIT(conn1, ch.selected_connection(), 2 * kMargin, clock);
+}
+
} // namespace cricket