Making candidate pool size behave as decided in JSEP.
To simplify things, the candidate pool is only used in the first
offer/answer.
After setting a local description, the size is frozen, and changing ICE
servers won't refresh the pool.
After setting an answer, the pooled candidates are discarded.
BUG=webrtc:5180
Review-Url: https://codereview.webrtc.org/2717893003
Cr-Original-Commit-Position: refs/heads/master@{#17178}
Cr-Mirrored-From: https://chromium.googlesource.com/external/webrtc
Cr-Mirrored-Commit: 42a426372878850160e1e627478335a880cebd9f
diff --git a/p2p/base/portallocator.cc b/p2p/base/portallocator.cc
index dc4166a..a587aa8 100644
--- a/p2p/base/portallocator.cc
+++ b/p2p/base/portallocator.cc
@@ -40,25 +40,21 @@
turn_servers_ = turn_servers;
prune_turn_ports_ = prune_turn_ports;
- bool candidate_pool_drain_began =
- static_cast<int>(pooled_sessions_.size()) != candidate_pool_size_;
- if (candidate_pool_drain_began &&
- candidate_pool_size != candidate_pool_size_) {
- LOG(LS_ERROR) << "Trying to change candidate pool size after pool started "
- "to be drained.";
- return false;
+ if (candidate_pool_frozen_) {
+ if (candidate_pool_size != candidate_pool_size_) {
+ LOG(LS_ERROR) << "Trying to change candidate pool size after pool was "
+ << "frozen.";
+ return false;
+ }
+ return true;
}
+
if (candidate_pool_size < 0) {
LOG(LS_ERROR) << "Can't set negative pool size.";
return false;
}
- candidate_pool_size_ = candidate_pool_size;
- // If sessions need to be recreated, only recreate as many as the current
- // pool size if the pool has begun to be drained.
- int sessions_needed = candidate_pool_drain_began
- ? static_cast<int>(pooled_sessions_.size())
- : candidate_pool_size_;
+ candidate_pool_size_ = candidate_pool_size;
// If ICE servers changed, throw away any existing pooled sessions and create
// new ones.
@@ -66,16 +62,16 @@
pooled_sessions_.clear();
}
- // If |sessions_needed| is less than the number of pooled sessions, get rid
- // of the extras.
- while (sessions_needed < static_cast<int>(pooled_sessions_.size())) {
+ // If |candidate_pool_size_| is less than the number of pooled sessions, get
+ // rid of the extras.
+ while (candidate_pool_size_ < static_cast<int>(pooled_sessions_.size())) {
pooled_sessions_.front().reset(nullptr);
pooled_sessions_.pop_front();
}
- // If |sessions_needed| is greater than the number of pooled sessions,
+ // If |candidate_pool_size_| is greater than the number of pooled sessions,
// create new sessions.
- while (static_cast<int>(pooled_sessions_.size()) < sessions_needed) {
+ while (static_cast<int>(pooled_sessions_.size()) < candidate_pool_size_) {
PortAllocatorSession* pooled_session = CreateSessionInternal("", 0, "", "");
pooled_session->StartGettingPorts();
pooled_sessions_.push_back(
@@ -122,4 +118,12 @@
return pooled_sessions_.front().get();
}
+void PortAllocator::FreezeCandidatePool() {
+ candidate_pool_frozen_ = true;
+}
+
+void PortAllocator::DiscardCandidatePool() {
+ pooled_sessions_.clear();
+}
+
} // namespace cricket
diff --git a/p2p/base/portallocator.h b/p2p/base/portallocator.h
index eb7308c..eec7580 100644
--- a/p2p/base/portallocator.h
+++ b/p2p/base/portallocator.h
@@ -309,6 +309,7 @@
allow_tcp_listen_(true),
candidate_filter_(CF_ALL) {
}
+
virtual ~PortAllocator() {}
// This should be called on the PortAllocator's thread before the
@@ -318,11 +319,13 @@
// Set STUN and TURN servers to be used in future sessions, and set
// candidate pool size, as described in JSEP.
//
- // If the servers are changing and the candidate pool size is nonzero,
- // existing pooled sessions will be destroyed and new ones created.
+ // If the servers are changing, and the candidate pool size is nonzero, and
+ // FreezeCandidatePool hasn't been called, existing pooled sessions will be
+ // destroyed and new ones created.
//
- // If the servers are not changing but the candidate pool size is,
- // pooled sessions will be either created or destroyed as necessary.
+ // If the servers are not changing but the candidate pool size is, and
+ // FreezeCandidatePool hasn't been called, pooled sessions will be either
+ // created or destroyed as necessary.
//
// Returns true if the configuration could successfully be changed.
bool SetConfiguration(const ServerAddresses& stun_servers,
@@ -365,6 +368,18 @@
// Returns the next session that would be returned by TakePooledSession.
const PortAllocatorSession* GetPooledSession() const;
+ // After FreezeCandidatePool is called, changing the candidate pool size will
+ // no longer be allowed, and changing ICE servers will not cause pooled
+ // sessions to be recreated.
+ //
+ // Expected to be called when SetLocalDescription is called on a
+ // PeerConnection. Can be called safely on any thread as long as not
+ // simultaneously with SetConfiguration.
+ void FreezeCandidatePool();
+
+ // Discard any remaining pooled sessions.
+ void DiscardCandidatePool();
+
uint32_t flags() const { return flags_; }
void set_flags(uint32_t flags) { flags_ = flags; }
@@ -432,6 +447,7 @@
std::vector<RelayServerConfig> turn_servers_;
int candidate_pool_size_ = 0; // Last value passed into SetConfiguration.
std::deque<std::unique_ptr<PortAllocatorSession>> pooled_sessions_;
+ bool candidate_pool_frozen_ = false;
bool prune_turn_ports_ = false;
webrtc::MetricsObserverInterface* metrics_observer_ = nullptr;
diff --git a/p2p/base/portallocator_unittest.cc b/p2p/base/portallocator_unittest.cc
index 74e2a3d..ac1c862 100644
--- a/p2p/base/portallocator_unittest.cc
+++ b/p2p/base/portallocator_unittest.cc
@@ -169,15 +169,6 @@
EXPECT_EQ(1, GetAllPooledSessionsReturnCount());
}
-// Test that after the pool starts to be drained, changing the pool size is not
-// allowed.
-TEST_F(PortAllocatorTest, CantChangePoolSizeAfterTakePooledSession) {
- SetConfigurationWithPoolSize(1);
- TakePooledSession();
- SetConfigurationWithPoolSizeExpectFailure(2);
- SetConfigurationWithPoolSizeExpectFailure(0);
-}
-
// According to JSEP, existing pooled sessions should be destroyed and new
// ones created when the ICE servers change.
TEST_F(PortAllocatorTest,
@@ -205,6 +196,31 @@
EXPECT_EQ(0, GetAllPooledSessionsReturnCount());
}
+// According to JSEP, after SetLocalDescription, setting different ICE servers
+// will not cause the pool to be refilled. This is implemented by the
+// PeerConnection calling FreezeCandidatePool when a local description is set.
+TEST_F(PortAllocatorTest,
+ SetConfigurationDoesNotRecreatePooledSessionsAfterFreezeCandidatePool) {
+ cricket::ServerAddresses stun_servers_1 = {stun_server_1};
+ std::vector<cricket::RelayServerConfig> turn_servers_1 = {turn_server_1};
+ allocator_->SetConfiguration(stun_servers_1, turn_servers_1, 1, false);
+ EXPECT_EQ(stun_servers_1, allocator_->stun_servers());
+ EXPECT_EQ(turn_servers_1, allocator_->turn_servers());
+
+ // Update with a different set of servers, but first freeze the pool.
+ allocator_->FreezeCandidatePool();
+ cricket::ServerAddresses stun_servers_2 = {stun_server_2};
+ std::vector<cricket::RelayServerConfig> turn_servers_2 = {turn_server_2};
+ allocator_->SetConfiguration(stun_servers_2, turn_servers_2, 2, false);
+ EXPECT_EQ(stun_servers_2, allocator_->stun_servers());
+ EXPECT_EQ(turn_servers_2, allocator_->turn_servers());
+ auto session = TakePooledSession();
+ ASSERT_NE(nullptr, session.get());
+ EXPECT_EQ(stun_servers_1, session->stun_servers());
+ EXPECT_EQ(turn_servers_1, session->turn_servers());
+ EXPECT_EQ(0, GetAllPooledSessionsReturnCount());
+}
+
TEST_F(PortAllocatorTest, GetPooledSessionReturnsNextSession) {
SetConfigurationWithPoolSize(2);
auto peeked_session_1 = GetPooledSession();
@@ -247,3 +263,11 @@
auto session = TakePooledSession();
EXPECT_EQ(cricket::CF_RELAY, session->candidate_filter());
}
+
+// Verify that after DiscardCandidatePool, TakePooledSession doesn't return
+// anything.
+TEST_F(PortAllocatorTest, DiscardCandidatePool) {
+ SetConfigurationWithPoolSize(1);
+ allocator_->DiscardCandidatePool();
+ EXPECT_EQ(0, GetAllPooledSessionsReturnCount());
+}
diff --git a/p2p/client/basicportallocator.cc b/p2p/client/basicportallocator.cc
index d76ff56..aebe0f2 100644
--- a/p2p/client/basicportallocator.cc
+++ b/p2p/client/basicportallocator.cc
@@ -169,6 +169,9 @@
}
BasicPortAllocator::~BasicPortAllocator() {
+ // Our created port allocator sessions depend on us, so destroy our remaining
+ // pooled sessions before anything else.
+ DiscardCandidatePool();
}
PortAllocatorSession* BasicPortAllocator::CreateSessionInternal(
diff --git a/pc/peerconnection.cc b/pc/peerconnection.cc
index c5d6591..d79868d 100644
--- a/pc/peerconnection.cc
+++ b/pc/peerconnection.cc
@@ -1261,10 +1261,24 @@
signaling_thread()->Post(RTC_FROM_HERE, this,
MSG_SET_SESSIONDESCRIPTION_SUCCESS, msg);
+ // According to JSEP, after setLocalDescription, changing the candidate pool
+ // size is not allowed, and changing the set of ICE servers will not result
+ // in new candidates being gathered.
+ port_allocator_->FreezeCandidatePool();
+
// MaybeStartGathering needs to be called after posting
// MSG_SET_SESSIONDESCRIPTION_SUCCESS, so that we don't signal any candidates
// before signaling that SetLocalDescription completed.
session_->MaybeStartGathering();
+
+ if (desc->type() == SessionDescriptionInterface::kAnswer) {
+ // TODO(deadbeef): We already had to hop to the network thread for
+ // MaybeStartGathering...
+ network_thread()->Invoke<void>(
+ RTC_FROM_HERE,
+ rtc::Bind(&cricket::PortAllocator::DiscardCandidatePool,
+ port_allocator_.get()));
+ }
}
void PeerConnection::SetRemoteDescription(
@@ -1372,6 +1386,15 @@
SetSessionDescriptionMsg* msg = new SetSessionDescriptionMsg(observer);
signaling_thread()->Post(RTC_FROM_HERE, this,
MSG_SET_SESSIONDESCRIPTION_SUCCESS, msg);
+
+ if (desc->type() == SessionDescriptionInterface::kAnswer) {
+ // TODO(deadbeef): We already had to hop to the network thread for
+ // MaybeStartGathering...
+ network_thread()->Invoke<void>(
+ RTC_FROM_HERE,
+ rtc::Bind(&cricket::PortAllocator::DiscardCandidatePool,
+ port_allocator_.get()));
+ }
}
PeerConnectionInterface::RTCConfiguration PeerConnection::GetConfiguration() {
@@ -1537,6 +1560,10 @@
session_->Close();
event_log_.reset();
+ network_thread()->Invoke<void>(
+ RTC_FROM_HERE,
+ rtc::Bind(&cricket::PortAllocator::DiscardCandidatePool,
+ port_allocator_.get()));
}
void PeerConnection::OnSessionStateChange(WebRtcSession* /*session*/,
diff --git a/pc/peerconnectioninterface_unittest.cc b/pc/peerconnectioninterface_unittest.cc
index 02c7932..ccf1a5e 100644
--- a/pc/peerconnectioninterface_unittest.cc
+++ b/pc/peerconnectioninterface_unittest.cc
@@ -2277,6 +2277,43 @@
EXPECT_EQ(RTCErrorType::INVALID_MODIFICATION, error.type());
}
+// Test that after setting an answer, extra pooled sessions are discarded. The
+// ICE candidate pool is only intended to be used for the first offer/answer.
+TEST_F(PeerConnectionInterfaceTest,
+ ExtraPooledSessionsDiscardedAfterApplyingAnswer) {
+ CreatePeerConnection();
+
+ // Set a larger-than-necessary size.
+ PeerConnectionInterface::RTCConfiguration config;
+ config.ice_candidate_pool_size = 4;
+ EXPECT_TRUE(pc_->SetConfiguration(config));
+
+ // Do offer/answer.
+ CreateOfferAsRemoteDescription();
+ CreateAnswerAsLocalDescription();
+
+ // Expect no pooled sessions to be left.
+ const cricket::PortAllocatorSession* session =
+ port_allocator_->GetPooledSession();
+ EXPECT_EQ(nullptr, session);
+}
+
+// After Close is called, pooled candidates should be discarded so as to not
+// waste network resources.
+TEST_F(PeerConnectionInterfaceTest, PooledSessionsDiscardedAfterClose) {
+ CreatePeerConnection();
+
+ PeerConnectionInterface::RTCConfiguration config;
+ config.ice_candidate_pool_size = 3;
+ EXPECT_TRUE(pc_->SetConfiguration(config));
+ pc_->Close();
+
+ // Expect no pooled sessions to be left.
+ const cricket::PortAllocatorSession* session =
+ port_allocator_->GetPooledSession();
+ EXPECT_EQ(nullptr, session);
+}
+
// Test that SetConfiguration returns an invalid modification error if
// modifying a field in the configuration that isn't allowed to be modified.
TEST_F(PeerConnectionInterfaceTest,