diff options
Diffstat (limited to 'net')
-rw-r--r-- | net/http/http_network_session.cc | 15 | ||||
-rw-r--r-- | net/http/http_network_session.h | 12 | ||||
-rw-r--r-- | net/socket/client_socket_pool_base.cc | 77 | ||||
-rw-r--r-- | net/socket/client_socket_pool_base.h | 49 | ||||
-rw-r--r-- | net/socket/client_socket_pool_base_unittest.cc | 368 | ||||
-rw-r--r-- | net/socket/tcp_client_socket_pool.cc | 3 | ||||
-rw-r--r-- | net/socket/tcp_client_socket_pool.h | 3 | ||||
-rw-r--r-- | net/socket/tcp_client_socket_pool_unittest.cc | 5 |
8 files changed, 441 insertions, 91 deletions
diff --git a/net/http/http_network_session.cc b/net/http/http_network_session.cc index a498ed0..b705766 100644 --- a/net/http/http_network_session.cc +++ b/net/http/http_network_session.cc @@ -9,8 +9,23 @@ namespace net { // static +int HttpNetworkSession::max_sockets_ = 100; + +// static int HttpNetworkSession::max_sockets_per_group_ = 6; +HttpNetworkSession::HttpNetworkSession( + HostResolver* host_resolver, + ProxyService* proxy_service, + ClientSocketFactory* client_socket_factory) + : connection_pool_(new TCPClientSocketPool( + max_sockets_, max_sockets_per_group_, host_resolver, + client_socket_factory)), + host_resolver_(host_resolver), + proxy_service_(proxy_service) { + DCHECK(proxy_service); +} + // static void HttpNetworkSession::set_max_sockets_per_group(int socket_count) { DCHECK(0 < socket_count); diff --git a/net/http/http_network_session.h b/net/http/http_network_session.h index 3529bc8..487ade4 100644 --- a/net/http/http_network_session.h +++ b/net/http/http_network_session.h @@ -5,7 +5,6 @@ #ifndef NET_HTTP_HTTP_NETWORK_SESSION_H_ #define NET_HTTP_HTTP_NETWORK_SESSION_H_ -#include "base/logging.h" #include "base/ref_counted.h" #include "net/base/ssl_client_auth_cache.h" #include "net/base/ssl_config_service.h" @@ -22,13 +21,7 @@ class ProxyService; class HttpNetworkSession : public base::RefCounted<HttpNetworkSession> { public: HttpNetworkSession(HostResolver* host_resolver, ProxyService* proxy_service, - ClientSocketFactory* client_socket_factory) - : connection_pool_(new TCPClientSocketPool( - max_sockets_per_group_, host_resolver, client_socket_factory)), - host_resolver_(host_resolver), - proxy_service_(proxy_service) { - DCHECK(proxy_service); - } + ClientSocketFactory* client_socket_factory); HttpAuthCache* auth_cache() { return &auth_cache_; } SSLClientAuthCache* ssl_client_auth_cache() { @@ -46,6 +39,9 @@ class HttpNetworkSession : public base::RefCounted<HttpNetworkSession> { private: FRIEND_TEST(HttpNetworkTransactionTest, GroupNameForProxyConnections); + // Total limit of sockets. Not a constant to allow experiments. + static int max_sockets_; + // Default to allow up to 6 connections per host. Experiment and tuning may // try other values (greater than 0). Too large may cause many problems, such // as home routers blocking the connections!?!? diff --git a/net/socket/client_socket_pool_base.cc b/net/socket/client_socket_pool_base.cc index 313e046..d10cf60 100644 --- a/net/socket/client_socket_pool_base.cc +++ b/net/socket/client_socket_pool_base.cc @@ -47,11 +47,19 @@ ConnectJob::ConnectJob(const std::string& group_name, ConnectJob::~ConnectJob() {} ClientSocketPoolBase::ClientSocketPoolBase( + int max_sockets, int max_sockets_per_group, ConnectJobFactory* connect_job_factory) : idle_socket_count_(0), + connecting_socket_count_(0), + handed_out_socket_count_(0), + max_sockets_(max_sockets), max_sockets_per_group_(max_sockets_per_group), - connect_job_factory_(connect_job_factory) {} + may_have_stalled_group_(false), + connect_job_factory_(connect_job_factory) { + DCHECK_LE(0, max_sockets_per_group); + DCHECK_LE(max_sockets_per_group, max_sockets); +} ClientSocketPoolBase::~ClientSocketPoolBase() { if (g_late_binding) @@ -89,7 +97,13 @@ int ClientSocketPoolBase::RequestSocket( Group& group = group_map_[group_name]; // Can we make another active socket now? - if (!group.HasAvailableSocketSlot(max_sockets_per_group_)) { + if (ReachedMaxSocketsLimit() || + !group.HasAvailableSocketSlot(max_sockets_per_group_)) { + if (ReachedMaxSocketsLimit()) { + // We could check if we really have a stalled group here, but it requires + // a scan of all groups, so just flip a flag here, and do the check later. + may_have_stalled_group_ = true; + } CHECK(callback); Request r(handle, callback, priority, resolve_info); InsertRequestIntoQueue(r, &group.pending_requests); @@ -120,6 +134,8 @@ int ClientSocketPoolBase::RequestSocket( HandOutSocket(connect_job->ReleaseSocket(), false /* not reused */, handle, &group); } else if (rv == ERR_IO_PENDING) { + connecting_socket_count_++; + ConnectJob* job = connect_job.release(); if (g_late_binding) { CHECK(!ContainsKey(connect_job_map_, handle)); @@ -291,6 +307,9 @@ void ClientSocketPoolBase::DoReleaseSocket(const std::string& group_name, Group& group = i->second; + CHECK(handed_out_socket_count_ > 0); + handed_out_socket_count_--; + CHECK(group.active_socket_count > 0); group.active_socket_count--; @@ -304,6 +323,38 @@ void ClientSocketPoolBase::DoReleaseSocket(const std::string& group_name, OnAvailableSocketSlot(group_name, &group); } +// Search for the highest priority pending request, amongst the groups that +// are not at the |max_sockets_per_group_| limit. Note: for requests with +// the same priority, the winner is based on group hash ordering (and not +// insertion order). +int ClientSocketPoolBase::FindTopStalledGroup(Group** group, + std::string* group_name) { + Group* top_group = NULL; + const std::string* top_group_name = NULL; + int stalled_group_count = 0; + for (GroupMap::iterator i = group_map_.begin(); + i != group_map_.end(); ++i) { + Group& group = i->second; + const RequestQueue& queue = group.pending_requests; + if (queue.empty()) + continue; + bool has_slot = group.HasAvailableSocketSlot(max_sockets_per_group_); + if (has_slot) + stalled_group_count++; + bool has_higher_priority = !top_group || + group.TopPendingPriority() > top_group->TopPendingPriority(); + if (has_slot && has_higher_priority) { + top_group = &group; + top_group_name = &i->first; + } + } + if (top_group) { + *group = top_group; + *group_name = *top_group_name; + } + return stalled_group_count; +} + void ClientSocketPoolBase::OnConnectJobComplete(int result, ConnectJob* job) { DCHECK_NE(ERR_IO_PENDING, result); const std::string group_name = job->group_name(); @@ -369,6 +420,9 @@ void ClientSocketPoolBase::EnableLateBindingOfSockets(bool enabled) { void ClientSocketPoolBase::RemoveConnectJob( const ClientSocketHandle* handle, ConnectJob *job, Group* group) { + CHECK(connecting_socket_count_ > 0); + connecting_socket_count_--; + if (g_late_binding) { DCHECK(job); delete job; @@ -399,7 +453,15 @@ void ClientSocketPoolBase::MaybeOnAvailableSocketSlot( void ClientSocketPoolBase::OnAvailableSocketSlot(const std::string& group_name, Group* group) { - if (!group->pending_requests.empty()) { + if (may_have_stalled_group_) { + std::string top_group_name; + Group* top_group; + int stalled_group_count = FindTopStalledGroup(&top_group, &top_group_name); + if (stalled_group_count <= 1) + may_have_stalled_group_ = false; + if (stalled_group_count >= 1) + ProcessPendingRequest(top_group_name, top_group); + } else if (!group->pending_requests.empty()) { ProcessPendingRequest(group_name, group); // |group| may no longer be valid after this point. Be careful not to // access it again. @@ -435,6 +497,8 @@ void ClientSocketPoolBase::HandOutSocket( DCHECK(socket); handle->set_socket(socket); handle->set_is_reused(reused); + + handed_out_socket_count_++; group->active_socket_count++; } @@ -465,4 +529,11 @@ void ClientSocketPoolBase::CancelAllConnectJobs() { } } +bool ClientSocketPoolBase::ReachedMaxSocketsLimit() const { + // Each connecting socket will eventually connect and be handed out. + int total = handed_out_socket_count_ + connecting_socket_count_; + DCHECK_LE(total, max_sockets_); + return total == max_sockets_; +} + } // namespace net diff --git a/net/socket/client_socket_pool_base.h b/net/socket/client_socket_pool_base.h index fc3582d..36281b4 100644 --- a/net/socket/client_socket_pool_base.h +++ b/net/socket/client_socket_pool_base.h @@ -127,7 +127,8 @@ class ClientSocketPoolBase DISALLOW_COPY_AND_ASSIGN(ConnectJobFactory); }; - ClientSocketPoolBase(int max_sockets_per_group, + ClientSocketPoolBase(int max_sockets, + int max_sockets_per_group, ConnectJobFactory* connect_job_factory); ~ClientSocketPoolBase(); @@ -150,10 +151,6 @@ class ClientSocketPoolBase return idle_socket_count_; } - int max_sockets_per_group() const { - return max_sockets_per_group_; - } - int IdleSocketCountInGroup(const std::string& group_name) const; LoadState GetLoadState(const std::string& group_name, @@ -169,6 +166,9 @@ class ClientSocketPoolBase // afterward and receive the socket from the job). static void EnableLateBindingOfSockets(bool enabled); + // For testing. + bool may_have_stalled_group() const { return may_have_stalled_group_; } + private: // Entry for a persistent socket which became idle at time |start_time|. struct IdleSocket { @@ -204,6 +204,10 @@ class ClientSocketPoolBase max_sockets_per_group; } + int TopPendingPriority() const { + return pending_requests.front().priority; + } + std::deque<IdleSocket> idle_sockets; std::set<const ConnectJob*> jobs; RequestQueue pending_requests; @@ -230,6 +234,12 @@ class ClientSocketPoolBase // Called via PostTask by ReleaseSocket. void DoReleaseSocket(const std::string& group_name, ClientSocket* socket); + // Scans the group map for groups which have an available socket slot and + // at least one pending request. Returns number of groups found, and if found + // at least one, fills |group| and |group_name| with data of the stalled group + // having highest priority. + int FindTopStalledGroup(Group** group, std::string* group_name); + // Called when timer_ fires. This method scans the idle sockets removing // sockets that timed out or can't be reused. void OnCleanupTimerFired() { @@ -269,6 +279,10 @@ class ClientSocketPoolBase // longer needed. void CancelAllConnectJobs(); + // Returns true if we can't create any more sockets due to the total limit. + // TODO(phajdan.jr): Also take idle sockets into account. + bool ReachedMaxSocketsLimit() const; + GroupMap group_map_; ConnectJobMap connect_job_map_; @@ -280,9 +294,34 @@ class ClientSocketPoolBase // The total number of idle sockets in the system. int idle_socket_count_; + // Number of connecting sockets across all groups. + int connecting_socket_count_; + + // Number of connected sockets we handed out across all groups. + int handed_out_socket_count_; + + // The maximum total number of sockets. See ReachedMaxSocketsLimit. + const int max_sockets_; + // The maximum number of sockets kept per group. const int max_sockets_per_group_; + // Until the maximum number of sockets limit is reached, a group can only + // have pending requests if it exceeds the "max sockets per group" limit. + // + // This means when a socket is released, the only pending requests that can + // be started next belong to the same group. + // + // However once the |max_sockets_| limit is reached, this stops being true: + // groups can now have pending requests without having first reached the + // |max_sockets_per_group_| limit. So choosing the next request involves + // selecting the highest priority request across *all* groups. + // + // Since reaching the maximum number of sockets is an edge case, we make note + // of when it happens, and thus avoid doing the slower "scan all groups" + // in the common case. + bool may_have_stalled_group_; + const scoped_ptr<ConnectJobFactory> connect_job_factory_; // Controls whether or not we use late binding of sockets. diff --git a/net/socket/client_socket_pool_base_unittest.cc b/net/socket/client_socket_pool_base_unittest.cc index 5f6c6fc..f5ad77e 100644 --- a/net/socket/client_socket_pool_base_unittest.cc +++ b/net/socket/client_socket_pool_base_unittest.cc @@ -18,6 +18,8 @@ namespace net { namespace { +const int kDefaultMaxSockets = 4; + const int kDefaultMaxSocketsPerGroup = 2; const int kDefaultPriority = 5; @@ -255,10 +257,11 @@ class TestConnectJobFactory : public ClientSocketPoolBase::ConnectJobFactory { class TestClientSocketPool : public ClientSocketPool { public: TestClientSocketPool( + int max_sockets, int max_sockets_per_group, ClientSocketPoolBase::ConnectJobFactory* connect_job_factory) : base_(new ClientSocketPoolBase( - max_sockets_per_group, connect_job_factory)) {} + max_sockets, max_sockets_per_group, connect_job_factory)) {} virtual int RequestSocket( const std::string& group_name, @@ -297,9 +300,7 @@ class TestClientSocketPool : public ClientSocketPool { return base_->GetLoadState(group_name, handle); } - int MaxSocketsPerGroup() const { - return base_->max_sockets_per_group(); - } + const ClientSocketPoolBase* base() const { return base_.get(); } private: const scoped_refptr<ClientSocketPoolBase> base_; @@ -322,9 +323,10 @@ class ClientSocketPoolBaseTest : public testing::Test { connect_job_factory_( new TestConnectJobFactory(&client_socket_factory_)) {} - void CreatePool(int max_sockets_per_group) { + void CreatePool(int max_sockets, int max_sockets_per_group) { DCHECK(!pool_.get()); - pool_ = new TestClientSocketPool(max_sockets_per_group, + pool_ = new TestClientSocketPool(max_sockets, + max_sockets_per_group, connect_job_factory_); } @@ -336,9 +338,14 @@ class ClientSocketPoolBaseTest : public testing::Test { // The tests often call Reset() on handles at the end which may post // DoReleaseSocket() tasks. MessageLoop::current()->RunAllPending(); - // Need to delete |pool_| before we turn late binding back off. - // TODO(willchan): Remove this line when late binding becomes the default. + + // Need to delete |pool_| before we turn late binding back off. We also need + // to delete |requests_| because the pool is reference counted and requests + // keep reference to it. + // TODO(willchan): Remove this part when late binding becomes the default. pool_ = NULL; + requests_.reset(); + ClientSocketPoolBase::EnableLateBindingOfSockets(false); } @@ -374,20 +381,24 @@ class ClientSocketPoolBaseTest : public testing::Test { NO_KEEP_ALIVE, }; + bool ReleaseOneConnection(KeepAlive keep_alive) { + ScopedVector<TestSocketRequest>::iterator i; + for (i = requests_.begin(); i != requests_.end(); ++i) { + if ((*i)->handle.is_initialized()) { + if (keep_alive == NO_KEEP_ALIVE) + (*i)->handle.socket()->Disconnect(); + (*i)->handle.Reset(); + MessageLoop::current()->RunAllPending(); + return true; + } + } + return false; + } + void ReleaseAllConnections(KeepAlive keep_alive) { bool released_one; do { - released_one = false; - ScopedVector<TestSocketRequest>::iterator i; - for (i = requests_.begin(); i != requests_.end(); ++i) { - if ((*i)->handle.is_initialized()) { - if (keep_alive == NO_KEEP_ALIVE) - (*i)->handle.socket()->Disconnect(); - (*i)->handle.Reset(); - MessageLoop::current()->RunAllPending(); - released_one = true; - } - } + released_one = ReleaseOneConnection(keep_alive); } while (released_one); } @@ -406,7 +417,7 @@ const int ClientSocketPoolBaseTest::kIndexOutOfBounds = -1; const int ClientSocketPoolBaseTest::kRequestNotFound = -2; TEST_F(ClientSocketPoolBaseTest, BasicSynchronous) { - CreatePool(kDefaultMaxSocketsPerGroup); + CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); TestCompletionCallback callback; ClientSocketHandle handle(pool_.get()); @@ -418,7 +429,7 @@ TEST_F(ClientSocketPoolBaseTest, BasicSynchronous) { } TEST_F(ClientSocketPoolBaseTest, BasicAsynchronous) { - CreatePool(kDefaultMaxSocketsPerGroup); + CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); TestSocketRequest req(pool_.get(), &request_order_); @@ -432,7 +443,7 @@ TEST_F(ClientSocketPoolBaseTest, BasicAsynchronous) { } TEST_F(ClientSocketPoolBaseTest, InitConnectionFailure) { - CreatePool(kDefaultMaxSocketsPerGroup); + CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockFailingJob); TestSocketRequest req(pool_.get(), &request_order_); @@ -442,7 +453,7 @@ TEST_F(ClientSocketPoolBaseTest, InitConnectionFailure) { } TEST_F(ClientSocketPoolBaseTest, InitConnectionAsynchronousFailure) { - CreatePool(kDefaultMaxSocketsPerGroup); + CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingFailingJob); TestSocketRequest req(pool_.get(), &request_order_); @@ -452,8 +463,225 @@ TEST_F(ClientSocketPoolBaseTest, InitConnectionAsynchronousFailure) { EXPECT_EQ(ERR_CONNECTION_FAILED, req.WaitForResult()); } +TEST_F(ClientSocketPoolBaseTest, TotalLimit) { + CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); + + EXPECT_EQ(OK, StartRequest("a", kDefaultPriority)); + EXPECT_EQ(OK, StartRequest("b", kDefaultPriority)); + EXPECT_EQ(OK, StartRequest("c", kDefaultPriority)); + EXPECT_EQ(OK, StartRequest("d", kDefaultPriority)); + + EXPECT_EQ(static_cast<int>(requests_.size()), + client_socket_factory_.allocation_count()); + EXPECT_EQ(requests_.size() - kDefaultMaxSockets, + TestSocketRequest::completion_count); + + EXPECT_EQ(ERR_IO_PENDING, StartRequest("e", kDefaultPriority)); + EXPECT_EQ(ERR_IO_PENDING, StartRequest("f", kDefaultPriority)); + EXPECT_EQ(ERR_IO_PENDING, StartRequest("g", kDefaultPriority)); + + ReleaseAllConnections(KEEP_ALIVE); + + EXPECT_EQ(static_cast<int>(requests_.size()), + client_socket_factory_.allocation_count()); + EXPECT_EQ(requests_.size() - kDefaultMaxSockets, + TestSocketRequest::completion_count); + + EXPECT_EQ(1, GetOrderOfRequest(1)); + EXPECT_EQ(2, GetOrderOfRequest(2)); + EXPECT_EQ(3, GetOrderOfRequest(3)); + EXPECT_EQ(4, GetOrderOfRequest(4)); + EXPECT_EQ(5, GetOrderOfRequest(5)); + EXPECT_EQ(6, GetOrderOfRequest(6)); + EXPECT_EQ(7, GetOrderOfRequest(7)); +} + +TEST_F(ClientSocketPoolBaseTest, TotalLimitReachedNewGroup) { + CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); + + // Reach all limits: max total sockets, and max sockets per group. + EXPECT_EQ(OK, StartRequest("a", kDefaultPriority)); + EXPECT_EQ(OK, StartRequest("a", kDefaultPriority)); + EXPECT_EQ(OK, StartRequest("b", kDefaultPriority)); + EXPECT_EQ(OK, StartRequest("b", kDefaultPriority)); + + EXPECT_EQ(static_cast<int>(requests_.size()), + client_socket_factory_.allocation_count()); + EXPECT_EQ(requests_.size() - kDefaultMaxSockets, + TestSocketRequest::completion_count); + + // Now create a new group and verify that we don't starve it. + EXPECT_EQ(ERR_IO_PENDING, StartRequest("c", kDefaultPriority)); + + ReleaseAllConnections(KEEP_ALIVE); + + EXPECT_EQ(static_cast<int>(requests_.size()), + client_socket_factory_.allocation_count()); + EXPECT_EQ(requests_.size() - kDefaultMaxSockets, + TestSocketRequest::completion_count); + + EXPECT_EQ(1, GetOrderOfRequest(1)); + EXPECT_EQ(2, GetOrderOfRequest(2)); + EXPECT_EQ(3, GetOrderOfRequest(3)); + EXPECT_EQ(4, GetOrderOfRequest(4)); + EXPECT_EQ(5, GetOrderOfRequest(5)); +} + +TEST_F(ClientSocketPoolBaseTest, TotalLimitRespectsPriority) { + CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); + + EXPECT_EQ(OK, StartRequest("b", 3)); + EXPECT_EQ(OK, StartRequest("a", 3)); + EXPECT_EQ(OK, StartRequest("b", 6)); + EXPECT_EQ(OK, StartRequest("a", 6)); + + EXPECT_EQ(static_cast<int>(requests_.size()), + client_socket_factory_.allocation_count()); + + EXPECT_EQ(ERR_IO_PENDING, StartRequest("c", 4)); + EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", 5)); + EXPECT_EQ(ERR_IO_PENDING, StartRequest("b", 7)); + + ReleaseAllConnections(KEEP_ALIVE); + + // We're re-using one socket for group "a", and one for "b". + EXPECT_EQ(static_cast<int>(requests_.size()) - 2, + client_socket_factory_.allocation_count()); + EXPECT_EQ(requests_.size() - kDefaultMaxSockets, + TestSocketRequest::completion_count); + + // First 4 requests don't have to wait, and finish in order. + EXPECT_EQ(1, GetOrderOfRequest(1)); + EXPECT_EQ(2, GetOrderOfRequest(2)); + EXPECT_EQ(3, GetOrderOfRequest(3)); + EXPECT_EQ(4, GetOrderOfRequest(4)); + + // Request ("b", 7) has the highest priority, then ("a", 5), + // and then ("c", 4). + EXPECT_EQ(7, GetOrderOfRequest(5)); + EXPECT_EQ(6, GetOrderOfRequest(6)); + EXPECT_EQ(5, GetOrderOfRequest(7)); +} + +TEST_F(ClientSocketPoolBaseTest, TotalLimitRespectsGroupLimit) { + CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); + + EXPECT_EQ(OK, StartRequest("a", 3)); + EXPECT_EQ(OK, StartRequest("a", 6)); + EXPECT_EQ(OK, StartRequest("b", 3)); + EXPECT_EQ(OK, StartRequest("b", 6)); + + EXPECT_EQ(static_cast<int>(requests_.size()), + client_socket_factory_.allocation_count()); + + EXPECT_EQ(ERR_IO_PENDING, StartRequest("c", 6)); + EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", 4)); + EXPECT_EQ(ERR_IO_PENDING, StartRequest("b", 7)); + + ReleaseAllConnections(KEEP_ALIVE); + + // We're re-using one socket for group "a", and one for "b". + EXPECT_EQ(static_cast<int>(requests_.size()) - 2, + client_socket_factory_.allocation_count()); + EXPECT_EQ(requests_.size() - kDefaultMaxSockets, + TestSocketRequest::completion_count); + + // First 4 requests don't have to wait, and finish in order. + EXPECT_EQ(1, GetOrderOfRequest(1)); + EXPECT_EQ(2, GetOrderOfRequest(2)); + EXPECT_EQ(3, GetOrderOfRequest(3)); + EXPECT_EQ(4, GetOrderOfRequest(4)); + + // Request ("b", 7) has the highest priority, but we can't make new socket for + // group "b", because it has reached the per-group limit. Then we make + // socket for ("c", 6), because it has higher priority than ("a", 4), + // and we still can't make a socket for group "b". + EXPECT_EQ(5, GetOrderOfRequest(5)); + EXPECT_EQ(6, GetOrderOfRequest(6)); + EXPECT_EQ(7, GetOrderOfRequest(7)); +} + +// Make sure that we count connecting sockets against the total limit. +TEST_F(ClientSocketPoolBaseTest, TotalLimitCountsConnectingSockets) { + CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); + + EXPECT_EQ(OK, StartRequest("a", kDefaultPriority)); + EXPECT_EQ(OK, StartRequest("b", kDefaultPriority)); + EXPECT_EQ(OK, StartRequest("c", kDefaultPriority)); + + // Create one asynchronous request. + connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); + EXPECT_EQ(ERR_IO_PENDING, StartRequest("d", kDefaultPriority)); + + // The next synchronous request should wait for its turn. + connect_job_factory_->set_job_type(TestConnectJob::kMockJob); + EXPECT_EQ(ERR_IO_PENDING, StartRequest("e", kDefaultPriority)); + + ReleaseAllConnections(KEEP_ALIVE); + + EXPECT_EQ(static_cast<int>(requests_.size()), + client_socket_factory_.allocation_count()); + + EXPECT_EQ(1, GetOrderOfRequest(1)); + EXPECT_EQ(2, GetOrderOfRequest(2)); + EXPECT_EQ(3, GetOrderOfRequest(3)); + EXPECT_EQ(4, GetOrderOfRequest(4)); +} + +// Inside ClientSocketPoolBase we have a may_have_stalled_group flag, +// which tells it to use more expensive, but accurate, group selection +// algorithm. Make sure it doesn't get stuck in the "on" state. +TEST_F(ClientSocketPoolBaseTest, MayHaveStalledGroupReset) { + CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); + + EXPECT_FALSE(pool_->base()->may_have_stalled_group()); + + // Reach group socket limit. + EXPECT_EQ(OK, StartRequest("a", kDefaultPriority)); + EXPECT_EQ(OK, StartRequest("a", kDefaultPriority)); + EXPECT_FALSE(pool_->base()->may_have_stalled_group()); + + // Reach total limit, but don't request more sockets. + EXPECT_EQ(OK, StartRequest("b", kDefaultPriority)); + EXPECT_EQ(OK, StartRequest("b", kDefaultPriority)); + EXPECT_FALSE(pool_->base()->may_have_stalled_group()); + + // Request one more socket while we are at the maximum sockets limit. + // This should flip the may_have_stalled_group flag. + EXPECT_EQ(ERR_IO_PENDING, StartRequest("c", kDefaultPriority)); + EXPECT_TRUE(pool_->base()->may_have_stalled_group()); + + // After releasing first connection for "a", we're still at the + // maximum sockets limit, but every group's pending queue is empty, + // so we reset the flag. + EXPECT_TRUE(ReleaseOneConnection(KEEP_ALIVE)); + EXPECT_FALSE(pool_->base()->may_have_stalled_group()); + + // Requesting additional socket while at the total limit should + // flip the flag back to "on". + EXPECT_EQ(ERR_IO_PENDING, StartRequest("c", kDefaultPriority)); + EXPECT_TRUE(pool_->base()->may_have_stalled_group()); + + // We'll request one more socket to verify that we don't reset the flag + // too eagerly. + EXPECT_EQ(ERR_IO_PENDING, StartRequest("d", kDefaultPriority)); + EXPECT_TRUE(pool_->base()->may_have_stalled_group()); + + // We're at the maximum socket limit, and still have one request pending + // for "d". Flag should be "on". + EXPECT_TRUE(ReleaseOneConnection(KEEP_ALIVE)); + EXPECT_TRUE(pool_->base()->may_have_stalled_group()); + + // Now every group's pending queue should be empty again. + EXPECT_TRUE(ReleaseOneConnection(KEEP_ALIVE)); + EXPECT_FALSE(pool_->base()->may_have_stalled_group()); + + ReleaseAllConnections(KEEP_ALIVE); + EXPECT_FALSE(pool_->base()->may_have_stalled_group()); +} + TEST_F(ClientSocketPoolBaseTest, PendingRequests) { - CreatePool(kDefaultMaxSocketsPerGroup); + CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); EXPECT_EQ(OK, StartRequest("a", kDefaultPriority)); EXPECT_EQ(OK, StartRequest("a", kDefaultPriority)); @@ -480,7 +708,7 @@ TEST_F(ClientSocketPoolBaseTest, PendingRequests) { } TEST_F(ClientSocketPoolBaseTest, PendingRequests_NoKeepAlive) { - CreatePool(kDefaultMaxSocketsPerGroup); + CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); EXPECT_EQ(OK, StartRequest("a", kDefaultPriority)); EXPECT_EQ(OK, StartRequest("a", kDefaultPriority)); @@ -505,7 +733,7 @@ TEST_F(ClientSocketPoolBaseTest, PendingRequests_NoKeepAlive) { // The pending connect job will be cancelled and should not call back into // ClientSocketPoolBase. TEST_F(ClientSocketPoolBaseTest, CancelRequestClearGroup) { - CreatePool(kDefaultMaxSocketsPerGroup); + CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); TestSocketRequest req(pool_.get(), &request_order_); @@ -516,7 +744,7 @@ TEST_F(ClientSocketPoolBaseTest, CancelRequestClearGroup) { } TEST_F(ClientSocketPoolBaseTest, TwoRequestsCancelOne) { - CreatePool(kDefaultMaxSocketsPerGroup); + CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); TestSocketRequest req(pool_.get(), &request_order_); @@ -536,7 +764,7 @@ TEST_F(ClientSocketPoolBaseTest, TwoRequestsCancelOne) { } TEST_F(ClientSocketPoolBaseTest, ConnectCancelConnect) { - CreatePool(kDefaultMaxSocketsPerGroup); + CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); ClientSocketHandle handle(pool_.get()); @@ -561,7 +789,7 @@ TEST_F(ClientSocketPoolBaseTest, ConnectCancelConnect) { } TEST_F(ClientSocketPoolBaseTest, CancelRequest) { - CreatePool(kDefaultMaxSocketsPerGroup); + CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); EXPECT_EQ(OK, StartRequest("a", kDefaultPriority)); EXPECT_EQ(OK, StartRequest("a", kDefaultPriority)); @@ -640,7 +868,7 @@ class RequestSocketCallback : public CallbackRunner< Tuple1<int> > { }; TEST_F(ClientSocketPoolBaseTest, RequestPendingJobTwice) { - CreatePool(kDefaultMaxSocketsPerGroup); + CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); ClientSocketHandle handle(pool_.get()); @@ -655,7 +883,7 @@ TEST_F(ClientSocketPoolBaseTest, RequestPendingJobTwice) { } TEST_F(ClientSocketPoolBaseTest, RequestPendingJobThenSynchronous) { - CreatePool(kDefaultMaxSocketsPerGroup); + CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); ClientSocketHandle handle(pool_.get()); @@ -672,7 +900,7 @@ TEST_F(ClientSocketPoolBaseTest, RequestPendingJobThenSynchronous) { // Make sure that pending requests get serviced after active requests get // cancelled. TEST_F(ClientSocketPoolBaseTest, CancelActiveRequestWithPendingRequests) { - CreatePool(kDefaultMaxSocketsPerGroup); + CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); @@ -703,26 +931,24 @@ TEST_F(ClientSocketPoolBaseTest, CancelActiveRequestWithPendingRequests) { // Make sure that pending requests get serviced after active requests fail. TEST_F(ClientSocketPoolBaseTest, FailingActiveRequestWithPendingRequests) { - CreatePool(kDefaultMaxSocketsPerGroup); + const size_t kMaxSockets = 5; + CreatePool(kMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingFailingJob); - scoped_ptr<TestSocketRequest> reqs[kDefaultMaxSocketsPerGroup * 2 + 1]; + const size_t kNumberOfRequests = 2 * kDefaultMaxSocketsPerGroup + 1; + ASSERT_LE(kNumberOfRequests, kMaxSockets); // Otherwise the test will hang. // Queue up all the requests - for (size_t i = 0; i < arraysize(reqs); ++i) { - reqs[i].reset(new TestSocketRequest(pool_.get(), &request_order_)); - int rv = reqs[i]->handle.Init("a", ignored_request_info_, - kDefaultPriority, reqs[i].get()); - EXPECT_EQ(ERR_IO_PENDING, rv); - } + for (size_t i = 0; i < kNumberOfRequests; ++i) + EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority)); - for (size_t i = 0; i < arraysize(reqs); ++i) - EXPECT_EQ(ERR_CONNECTION_FAILED, reqs[i]->WaitForResult()); + for (size_t i = 0; i < kNumberOfRequests; ++i) + EXPECT_EQ(ERR_CONNECTION_FAILED, requests_[i]->WaitForResult()); } TEST_F(ClientSocketPoolBaseTest, CancelActiveRequestThenRequestSocket) { - CreatePool(kDefaultMaxSocketsPerGroup); + CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); @@ -747,7 +973,7 @@ TEST_F(ClientSocketPoolBaseTest, CancelActiveRequestThenRequestSocket) { // next job finishes synchronously. The callback for the asynchronous job // should be first though. TEST_F(ClientSocketPoolBaseTest, PendingJobCompletionOrder) { - CreatePool(kDefaultMaxSocketsPerGroup); + CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); // First two jobs are async. connect_job_factory_->set_job_type(TestConnectJob::kMockPendingFailingJob); @@ -788,7 +1014,7 @@ TEST_F(ClientSocketPoolBaseTest, PendingJobCompletionOrder) { // When a ConnectJob is coupled to a request, even if a free socket becomes // available, the request will be serviced by the ConnectJob. TEST_F(ClientSocketPoolBaseTest, ReleaseSockets) { - CreatePool(kDefaultMaxSocketsPerGroup); + CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); ClientSocketPoolBase::EnableLateBindingOfSockets(false); // Start job 1 (async OK) @@ -834,7 +1060,7 @@ class ClientSocketPoolBaseTest_LateBinding : public ClientSocketPoolBaseTest { }; TEST_F(ClientSocketPoolBaseTest_LateBinding, BasicSynchronous) { - CreatePool(kDefaultMaxSocketsPerGroup); + CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); TestCompletionCallback callback; ClientSocketHandle handle(pool_.get()); @@ -846,7 +1072,7 @@ TEST_F(ClientSocketPoolBaseTest_LateBinding, BasicSynchronous) { } TEST_F(ClientSocketPoolBaseTest_LateBinding, BasicAsynchronous) { - CreatePool(kDefaultMaxSocketsPerGroup); + CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); TestSocketRequest req(pool_.get(), &request_order_); @@ -860,7 +1086,7 @@ TEST_F(ClientSocketPoolBaseTest_LateBinding, BasicAsynchronous) { } TEST_F(ClientSocketPoolBaseTest_LateBinding, InitConnectionFailure) { - CreatePool(kDefaultMaxSocketsPerGroup); + CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockFailingJob); TestSocketRequest req(pool_.get(), &request_order_); @@ -871,7 +1097,7 @@ TEST_F(ClientSocketPoolBaseTest_LateBinding, InitConnectionFailure) { TEST_F(ClientSocketPoolBaseTest_LateBinding, InitConnectionAsynchronousFailure) { - CreatePool(kDefaultMaxSocketsPerGroup); + CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingFailingJob); TestSocketRequest req(pool_.get(), &request_order_); @@ -882,7 +1108,7 @@ TEST_F(ClientSocketPoolBaseTest_LateBinding, } TEST_F(ClientSocketPoolBaseTest_LateBinding, PendingRequests) { - CreatePool(kDefaultMaxSocketsPerGroup); + CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); EXPECT_EQ(OK, StartRequest("a", kDefaultPriority)); EXPECT_EQ(OK, StartRequest("a", kDefaultPriority)); @@ -909,7 +1135,7 @@ TEST_F(ClientSocketPoolBaseTest_LateBinding, PendingRequests) { } TEST_F(ClientSocketPoolBaseTest_LateBinding, PendingRequests_NoKeepAlive) { - CreatePool(kDefaultMaxSocketsPerGroup); + CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); EXPECT_EQ(OK, StartRequest("a", kDefaultPriority)); EXPECT_EQ(OK, StartRequest("a", kDefaultPriority)); @@ -934,7 +1160,7 @@ TEST_F(ClientSocketPoolBaseTest_LateBinding, PendingRequests_NoKeepAlive) { // The pending connect job will be cancelled and should not call back into // ClientSocketPoolBase. TEST_F(ClientSocketPoolBaseTest_LateBinding, CancelRequestClearGroup) { - CreatePool(kDefaultMaxSocketsPerGroup); + CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); TestSocketRequest req(pool_.get(), &request_order_); @@ -945,7 +1171,7 @@ TEST_F(ClientSocketPoolBaseTest_LateBinding, CancelRequestClearGroup) { } TEST_F(ClientSocketPoolBaseTest_LateBinding, TwoRequestsCancelOne) { - CreatePool(kDefaultMaxSocketsPerGroup); + CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); TestSocketRequest req(pool_.get(), &request_order_); @@ -965,7 +1191,7 @@ TEST_F(ClientSocketPoolBaseTest_LateBinding, TwoRequestsCancelOne) { } TEST_F(ClientSocketPoolBaseTest_LateBinding, ConnectCancelConnect) { - CreatePool(kDefaultMaxSocketsPerGroup); + CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); ClientSocketHandle handle(pool_.get()); @@ -990,7 +1216,7 @@ TEST_F(ClientSocketPoolBaseTest_LateBinding, ConnectCancelConnect) { } TEST_F(ClientSocketPoolBaseTest_LateBinding, CancelRequest) { - CreatePool(kDefaultMaxSocketsPerGroup); + CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); EXPECT_EQ(OK, StartRequest("a", kDefaultPriority)); EXPECT_EQ(OK, StartRequest("a", kDefaultPriority)); @@ -1022,7 +1248,7 @@ TEST_F(ClientSocketPoolBaseTest_LateBinding, CancelRequest) { } TEST_F(ClientSocketPoolBaseTest_LateBinding, RequestPendingJobTwice) { - CreatePool(kDefaultMaxSocketsPerGroup); + CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); ClientSocketHandle handle(pool_.get()); @@ -1037,7 +1263,7 @@ TEST_F(ClientSocketPoolBaseTest_LateBinding, RequestPendingJobTwice) { } TEST_F(ClientSocketPoolBaseTest_LateBinding, RequestPendingJobThenSynchronous) { - CreatePool(kDefaultMaxSocketsPerGroup); + CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); ClientSocketHandle handle(pool_.get()); @@ -1055,7 +1281,7 @@ TEST_F(ClientSocketPoolBaseTest_LateBinding, RequestPendingJobThenSynchronous) { // cancelled. TEST_F(ClientSocketPoolBaseTest_LateBinding, CancelActiveRequestWithPendingRequests) { - CreatePool(kDefaultMaxSocketsPerGroup); + CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); @@ -1087,27 +1313,25 @@ TEST_F(ClientSocketPoolBaseTest_LateBinding, // Make sure that pending requests get serviced after active requests fail. TEST_F(ClientSocketPoolBaseTest_LateBinding, FailingActiveRequestWithPendingRequests) { - CreatePool(kDefaultMaxSocketsPerGroup); + const int kMaxSockets = 5; + CreatePool(kMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingFailingJob); - scoped_ptr<TestSocketRequest> reqs[kDefaultMaxSocketsPerGroup * 2 + 1]; + const int kNumberOfRequests = 2 * kDefaultMaxSocketsPerGroup + 1; + ASSERT_LE(kNumberOfRequests, kMaxSockets); // Otherwise the test hangs. // Queue up all the requests - for (size_t i = 0; i < arraysize(reqs); ++i) { - reqs[i].reset(new TestSocketRequest(pool_.get(), &request_order_)); - int rv = reqs[i]->handle.Init("a", ignored_request_info_, - kDefaultPriority, reqs[i].get()); - EXPECT_EQ(ERR_IO_PENDING, rv); - } + for (int i = 0; i < kNumberOfRequests; ++i) + EXPECT_EQ(ERR_IO_PENDING, StartRequest("a", kDefaultPriority)); - for (size_t i = 0; i < arraysize(reqs); ++i) - EXPECT_EQ(ERR_CONNECTION_FAILED, reqs[i]->WaitForResult()); + for (int i = 0; i < kNumberOfRequests; ++i) + EXPECT_EQ(ERR_CONNECTION_FAILED, requests_[i]->WaitForResult()); } TEST_F(ClientSocketPoolBaseTest_LateBinding, CancelActiveRequestThenRequestSocket) { - CreatePool(kDefaultMaxSocketsPerGroup); + CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); @@ -1131,7 +1355,7 @@ TEST_F(ClientSocketPoolBaseTest_LateBinding, // When requests and ConnectJobs are not coupled, the request will get serviced // by whatever comes first. TEST_F(ClientSocketPoolBaseTest_LateBinding, ReleaseSockets) { - CreatePool(kDefaultMaxSocketsPerGroup); + CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); // Start job 1 (async OK) connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob); @@ -1175,7 +1399,7 @@ TEST_F(ClientSocketPoolBaseTest_LateBinding, ReleaseSockets) { // The requests are not coupled to the jobs. So, the requests should finish in // their priority / insertion order. TEST_F(ClientSocketPoolBaseTest_LateBinding, PendingJobCompletionOrder) { - CreatePool(kDefaultMaxSocketsPerGroup); + CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); // First two jobs are async. connect_job_factory_->set_job_type(TestConnectJob::kMockPendingFailingJob); @@ -1205,7 +1429,7 @@ TEST_F(ClientSocketPoolBaseTest_LateBinding, PendingJobCompletionOrder) { } TEST_F(ClientSocketPoolBaseTest_LateBinding, DISABLED_LoadState) { - CreatePool(kDefaultMaxSocketsPerGroup); + CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup); connect_job_factory_->set_job_type( TestConnectJob::kMockAdvancingLoadStateJob); diff --git a/net/socket/tcp_client_socket_pool.cc b/net/socket/tcp_client_socket_pool.cc index 6b3fd05..decc23f1 100644 --- a/net/socket/tcp_client_socket_pool.cc +++ b/net/socket/tcp_client_socket_pool.cc @@ -132,11 +132,12 @@ ConnectJob* TCPClientSocketPool::TCPConnectJobFactory::NewConnectJob( } TCPClientSocketPool::TCPClientSocketPool( + int max_sockets, int max_sockets_per_group, HostResolver* host_resolver, ClientSocketFactory* client_socket_factory) : base_(new ClientSocketPoolBase( - max_sockets_per_group, + max_sockets, max_sockets_per_group, new TCPConnectJobFactory(client_socket_factory, host_resolver))) {} TCPClientSocketPool::~TCPClientSocketPool() {} diff --git a/net/socket/tcp_client_socket_pool.h b/net/socket/tcp_client_socket_pool.h index 33c38bb..1906dcc 100644 --- a/net/socket/tcp_client_socket_pool.h +++ b/net/socket/tcp_client_socket_pool.h @@ -70,7 +70,8 @@ class TCPConnectJob : public ConnectJob { class TCPClientSocketPool : public ClientSocketPool { public: - TCPClientSocketPool(int max_sockets_per_group, + TCPClientSocketPool(int max_sockets, + int max_sockets_per_group, HostResolver* host_resolver, ClientSocketFactory* client_socket_factory); diff --git a/net/socket/tcp_client_socket_pool_unittest.cc b/net/socket/tcp_client_socket_pool_unittest.cc index d81137d..a31afcc 100644 --- a/net/socket/tcp_client_socket_pool_unittest.cc +++ b/net/socket/tcp_client_socket_pool_unittest.cc @@ -18,6 +18,7 @@ namespace net { namespace { +const int kMaxSockets = 32; const int kMaxSocketsPerGroup = 6; // Note that the first and the last are the same, the first should be handled @@ -225,7 +226,8 @@ class TCPClientSocketPoolTest : public testing::Test { protected: TCPClientSocketPoolTest() : host_resolver_(new MockHostResolver), - pool_(new TCPClientSocketPool(kMaxSocketsPerGroup, + pool_(new TCPClientSocketPool(kMaxSockets, + kMaxSocketsPerGroup, host_resolver_, &client_socket_factory_)) { } @@ -618,6 +620,7 @@ TEST_F(TCPClientSocketPoolTest, FailingActiveRequestWithPendingRequests) { MockClientSocketFactory::MOCK_PENDING_FAILING_CLIENT_SOCKET); scoped_ptr<TestSocketRequest> reqs[kMaxSocketsPerGroup * 2 + 1]; + ASSERT_LE(static_cast<int>(arraysize(reqs)), kMaxSockets); // Queue up all the requests |