summaryrefslogtreecommitdiffstats
path: root/net/socket/tcp_client_socket_pool.cc
diff options
context:
space:
mode:
Diffstat (limited to 'net/socket/tcp_client_socket_pool.cc')
-rw-r--r--net/socket/tcp_client_socket_pool.cc555
1 files changed, 555 insertions, 0 deletions
diff --git a/net/socket/tcp_client_socket_pool.cc b/net/socket/tcp_client_socket_pool.cc
new file mode 100644
index 0000000..534d10a
--- /dev/null
+++ b/net/socket/tcp_client_socket_pool.cc
@@ -0,0 +1,555 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/socket/tcp_client_socket_pool.h"
+
+#include "base/compiler_specific.h"
+#include "base/field_trial.h"
+#include "base/message_loop.h"
+#include "base/time.h"
+#include "base/stl_util-inl.h"
+#include "net/base/net_errors.h"
+#include "net/socket/client_socket_factory.h"
+#include "net/socket/client_socket_handle.h"
+#include "net/socket/tcp_client_socket.h"
+
+using base::TimeDelta;
+
+namespace {
+
+// The timeout value, in seconds, used to clean up idle sockets that can't be
+// reused.
+//
+// Note: It's important to close idle sockets that have received data as soon
+// as possible because the received data may cause BSOD on Windows XP under
+// some conditions. See http://crbug.com/4606.
+const int kCleanupInterval = 10; // DO NOT INCREASE THIS TIMEOUT.
+
+// The maximum duration, in seconds, to keep idle persistent sockets alive.
+const int kIdleTimeout = 300; // 5 minutes.
+
+} // namespace
+
+namespace net {
+
+TCPConnectingSocket::TCPConnectingSocket(
+ const std::string& group_name,
+ const HostResolver::RequestInfo& resolve_info,
+ const ClientSocketHandle* handle,
+ ClientSocketFactory* client_socket_factory,
+ ClientSocketPoolBase* pool)
+ : group_name_(group_name),
+ resolve_info_(resolve_info),
+ handle_(handle),
+ client_socket_factory_(client_socket_factory),
+ ALLOW_THIS_IN_INITIALIZER_LIST(
+ callback_(this,
+ &TCPConnectingSocket::OnIOComplete)),
+ pool_(pool),
+ resolver_(pool->GetHostResolver()) {}
+
+TCPConnectingSocket::~TCPConnectingSocket() {
+ // We don't worry about cancelling the host resolution and TCP connect, since
+ // ~SingleRequestHostResolver and ~ClientSocket will take care of it.
+}
+
+int TCPConnectingSocket::Connect() {
+ int rv = resolver_.Resolve(resolve_info_, &addresses_, &callback_);
+ if (rv != ERR_IO_PENDING)
+ rv = OnIOCompleteInternal(rv, true /* synchronous */);
+ return rv;
+}
+
+void TCPConnectingSocket::OnIOComplete(int result) {
+ OnIOCompleteInternal(result, false /* asynchronous */);
+}
+
+int TCPConnectingSocket::OnIOCompleteInternal(
+ int result, bool synchronous) {
+ CHECK(result != ERR_IO_PENDING);
+
+ ClientSocketPoolBase::Request* request = pool_->GetConnectingRequest(
+ group_name_, handle_);
+ CHECK(request);
+
+ if (result == OK && request->load_state == LOAD_STATE_RESOLVING_HOST) {
+ request->load_state = LOAD_STATE_CONNECTING;
+ socket_.reset(client_socket_factory_->CreateTCPClientSocket(addresses_));
+ connect_start_time_ = base::TimeTicks::Now();
+ result = socket_->Connect(&callback_);
+ if (result == ERR_IO_PENDING)
+ return result;
+ }
+
+ if (result == OK) {
+ CHECK(request->load_state == LOAD_STATE_CONNECTING);
+ CHECK(connect_start_time_ != base::TimeTicks());
+ base::TimeDelta connect_duration =
+ base::TimeTicks::Now() - connect_start_time_;
+
+ UMA_HISTOGRAM_CLIPPED_TIMES("Net.TCP_Connection_Latency",
+ connect_duration,
+ base::TimeDelta::FromMilliseconds(1),
+ base::TimeDelta::FromMinutes(10),
+ 100);
+ }
+
+ // Now, we either succeeded at Connect()'ing, or we failed at host resolution
+ // or Connect()'ing. Either way, we'll run the callback to alert the client.
+
+ CompletionCallback* callback = NULL;
+
+ if (result == OK) {
+ callback = pool_->OnConnectingRequestComplete(
+ group_name_,
+ handle_,
+ false /* don't deactivate socket */,
+ socket_.release());
+ } else {
+ callback = pool_->OnConnectingRequestComplete(
+ group_name_,
+ handle_,
+ true /* deactivate socket */,
+ NULL /* no connected socket to give */);
+ }
+
+ // |this| is deleted after this point.
+
+ CHECK(callback);
+
+ if (!synchronous)
+ callback->Run(result);
+ return result;
+}
+
+ClientSocketPoolBase::ClientSocketPoolBase(
+ int max_sockets_per_group,
+ HostResolver* host_resolver,
+ ConnectingSocketFactory* connecting_socket_factory)
+ : idle_socket_count_(0),
+ max_sockets_per_group_(max_sockets_per_group),
+ host_resolver_(host_resolver),
+ connecting_socket_factory_(connecting_socket_factory) {}
+
+ClientSocketPoolBase::~ClientSocketPoolBase() {
+ // Clean up any idle sockets. Assert that we have no remaining active
+ // sockets or pending requests. They should have all been cleaned up prior
+ // to the manager being destroyed.
+ CloseIdleSockets();
+ DCHECK(group_map_.empty());
+ DCHECK(connecting_socket_map_.empty());
+}
+
+// InsertRequestIntoQueue inserts the request into the queue based on
+// priority. Highest priorities are closest to the front. Older requests are
+// prioritized over requests of equal priority.
+//
+// static
+void ClientSocketPoolBase::InsertRequestIntoQueue(
+ const Request& r, RequestQueue* pending_requests) {
+ RequestQueue::iterator it = pending_requests->begin();
+ while (it != pending_requests->end() && r.priority <= it->priority)
+ ++it;
+ pending_requests->insert(it, r);
+}
+
+int ClientSocketPoolBase::RequestSocket(
+ const std::string& group_name,
+ const HostResolver::RequestInfo& resolve_info,
+ int priority,
+ ClientSocketHandle* handle,
+ CompletionCallback* callback) {
+ DCHECK(!resolve_info.hostname().empty());
+ DCHECK_GE(priority, 0);
+ Group& group = group_map_[group_name];
+
+ CheckSocketCounts(group);
+
+ // Can we make another active socket now?
+ if (group.active_socket_count == max_sockets_per_group_) {
+ CHECK(callback);
+ Request r(handle, callback, priority, resolve_info, LOAD_STATE_IDLE);
+ InsertRequestIntoQueue(r, &group.pending_requests);
+ return ERR_IO_PENDING;
+ }
+
+ // OK, we are going to activate one.
+ group.active_socket_count++;
+
+ while (!group.idle_sockets.empty()) {
+ IdleSocket idle_socket = group.idle_sockets.back();
+ group.idle_sockets.pop_back();
+ DecrementIdleCount();
+ if (idle_socket.socket->IsConnectedAndIdle()) {
+ // We found one we can reuse!
+ handle->set_socket(idle_socket.socket);
+ handle->set_is_reused(true);
+ group.sockets_handed_out_count++;
+ CheckSocketCounts(group);
+ return OK;
+ }
+ delete idle_socket.socket;
+ }
+
+ // We couldn't find a socket to reuse, so allocate and connect a new one.
+
+ CHECK(callback);
+ Request r(handle, callback, priority, resolve_info,
+ LOAD_STATE_RESOLVING_HOST);
+ group.connecting_requests[handle] = r;
+
+ CHECK(!ContainsKey(connecting_socket_map_, handle));
+
+ ConnectingSocket* connecting_socket =
+ connecting_socket_factory_->NewConnectingSocket(group_name, r, this);
+ connecting_socket_map_[handle] = connecting_socket;
+ int rv = connecting_socket->Connect();
+
+ CheckSocketCounts(group);
+ return rv;
+}
+
+void ClientSocketPoolBase::CancelRequest(const std::string& group_name,
+ const ClientSocketHandle* handle) {
+ CHECK(ContainsKey(group_map_, group_name));
+
+ Group& group = group_map_[group_name];
+
+ CheckSocketCounts(group);
+
+ // Search pending_requests for matching handle.
+ RequestQueue::iterator it = group.pending_requests.begin();
+ for (; it != group.pending_requests.end(); ++it) {
+ if (it->handle == handle) {
+ group.pending_requests.erase(it);
+ return;
+ }
+ }
+
+ // It's invalid to cancel a non-existent request.
+ CHECK(ContainsKey(group.connecting_requests, handle));
+
+ RequestMap::iterator map_it = group.connecting_requests.find(handle);
+ if (map_it != group.connecting_requests.end()) {
+ RemoveConnectingSocket(handle);
+
+ group.connecting_requests.erase(map_it);
+ group.active_socket_count--;
+
+ if (!group.pending_requests.empty()) {
+ ProcessPendingRequest(group_name, &group);
+ return; // |group| may be invalid after this, so return to be safe.
+ }
+
+ // Delete group if no longer needed.
+ if (group.active_socket_count == 0 && group.idle_sockets.empty()) {
+ CHECK(group.pending_requests.empty());
+ CHECK(group.connecting_requests.empty());
+ group_map_.erase(group_name);
+ return; // |group| is invalid after this, so return to be safe.
+ }
+ }
+
+ CheckSocketCounts(group);
+}
+
+void ClientSocketPoolBase::ReleaseSocket(const std::string& group_name,
+ ClientSocket* socket) {
+ // Run this asynchronously to allow the caller to finish before we let
+ // another to begin doing work. This also avoids nasty recursion issues.
+ // NOTE: We cannot refer to the handle argument after this method returns.
+ MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &ClientSocketPoolBase::DoReleaseSocket, group_name, socket));
+}
+
+void ClientSocketPoolBase::CloseIdleSockets() {
+ CleanupIdleSockets(true);
+}
+
+int ClientSocketPoolBase::IdleSocketCountInGroup(
+ const std::string& group_name) const {
+ GroupMap::const_iterator i = group_map_.find(group_name);
+ CHECK(i != group_map_.end());
+
+ return i->second.idle_sockets.size();
+}
+
+LoadState ClientSocketPoolBase::GetLoadState(
+ const std::string& group_name,
+ const ClientSocketHandle* handle) const {
+ if (!ContainsKey(group_map_, group_name)) {
+ NOTREACHED() << "ClientSocketPool does not contain group: " << group_name
+ << " for handle: " << handle;
+ return LOAD_STATE_IDLE;
+ }
+
+ // Can't use operator[] since it is non-const.
+ const Group& group = group_map_.find(group_name)->second;
+
+ // Search connecting_requests for matching handle.
+ RequestMap::const_iterator map_it = group.connecting_requests.find(handle);
+ if (map_it != group.connecting_requests.end()) {
+ const LoadState load_state = map_it->second.load_state;
+ CHECK(load_state == LOAD_STATE_RESOLVING_HOST ||
+ load_state == LOAD_STATE_CONNECTING);
+ return load_state;
+ }
+
+ // Search pending_requests for matching handle.
+ RequestQueue::const_iterator it = group.pending_requests.begin();
+ for (; it != group.pending_requests.end(); ++it) {
+ if (it->handle == handle) {
+ CHECK(LOAD_STATE_IDLE == it->load_state);
+ // TODO(wtc): Add a state for being on the wait list.
+ // See http://www.crbug.com/5077.
+ return LOAD_STATE_IDLE;
+ }
+ }
+
+ NOTREACHED();
+ return LOAD_STATE_IDLE;
+}
+
+bool ClientSocketPoolBase::IdleSocket::ShouldCleanup(
+ base::TimeTicks now) const {
+ bool timed_out = (now - start_time) >=
+ base::TimeDelta::FromSeconds(kIdleTimeout);
+ return timed_out || !socket->IsConnectedAndIdle();
+}
+
+void ClientSocketPoolBase::CleanupIdleSockets(bool force) {
+ if (idle_socket_count_ == 0)
+ return;
+
+ // Current time value. Retrieving it once at the function start rather than
+ // inside the inner loop, since it shouldn't change by any meaningful amount.
+ base::TimeTicks now = base::TimeTicks::Now();
+
+ GroupMap::iterator i = group_map_.begin();
+ while (i != group_map_.end()) {
+ Group& group = i->second;
+
+ std::deque<IdleSocket>::iterator j = group.idle_sockets.begin();
+ while (j != group.idle_sockets.end()) {
+ if (force || j->ShouldCleanup(now)) {
+ delete j->socket;
+ j = group.idle_sockets.erase(j);
+ DecrementIdleCount();
+ } else {
+ ++j;
+ }
+ }
+
+ // Delete group if no longer needed.
+ if (group.active_socket_count == 0 && group.idle_sockets.empty()) {
+ CHECK(group.pending_requests.empty());
+ CHECK(group.connecting_requests.empty());
+ group_map_.erase(i++);
+ } else {
+ ++i;
+ }
+ }
+}
+
+void ClientSocketPoolBase::IncrementIdleCount() {
+ if (++idle_socket_count_ == 1)
+ timer_.Start(TimeDelta::FromSeconds(kCleanupInterval), this,
+ &ClientSocketPoolBase::OnCleanupTimerFired);
+}
+
+void ClientSocketPoolBase::DecrementIdleCount() {
+ if (--idle_socket_count_ == 0)
+ timer_.Stop();
+}
+
+void ClientSocketPoolBase::DoReleaseSocket(const std::string& group_name,
+ ClientSocket* socket) {
+ GroupMap::iterator i = group_map_.find(group_name);
+ CHECK(i != group_map_.end());
+
+ Group& group = i->second;
+
+ CHECK(group.active_socket_count > 0);
+ CheckSocketCounts(group);
+
+ group.active_socket_count--;
+ group.sockets_handed_out_count--;
+
+ const bool can_reuse = socket->IsConnectedAndIdle();
+ if (can_reuse) {
+ IdleSocket idle_socket;
+ idle_socket.socket = socket;
+ idle_socket.start_time = base::TimeTicks::Now();
+
+ group.idle_sockets.push_back(idle_socket);
+ IncrementIdleCount();
+ } else {
+ delete socket;
+ }
+
+ // Process one pending request.
+ if (!group.pending_requests.empty()) {
+ ProcessPendingRequest(group_name, &group);
+ return;
+ }
+
+ // Delete group if no longer needed.
+ if (group.active_socket_count == 0 && group.idle_sockets.empty()) {
+ CHECK(group.pending_requests.empty());
+ CHECK(group.connecting_requests.empty());
+ group_map_.erase(i);
+ } else {
+ CheckSocketCounts(group);
+ }
+}
+
+ClientSocketPoolBase::Request* ClientSocketPoolBase::GetConnectingRequest(
+ const std::string& group_name, const ClientSocketHandle* handle) {
+ GroupMap::iterator group_it = group_map_.find(group_name);
+ if (group_it == group_map_.end())
+ return NULL;
+
+ Group& group = group_it->second;
+
+ RequestMap* request_map = &group.connecting_requests;
+ RequestMap::iterator it = request_map->find(handle);
+ if (it == request_map->end())
+ return NULL;
+
+ return &it->second;
+}
+
+CompletionCallback* ClientSocketPoolBase::OnConnectingRequestComplete(
+ const std::string& group_name,
+ const ClientSocketHandle* handle,
+ bool deactivate,
+ ClientSocket* socket) {
+ CHECK((deactivate && !socket) || (!deactivate && socket));
+ GroupMap::iterator group_it = group_map_.find(group_name);
+ CHECK(group_it != group_map_.end());
+ Group& group = group_it->second;
+
+ CheckSocketCounts(group);
+
+ RequestMap* request_map = &group.connecting_requests;
+
+ RequestMap::iterator it = request_map->find(handle);
+ CHECK(it != request_map->end());
+ Request request = it->second;
+ request_map->erase(it);
+ DCHECK_EQ(request.handle, handle);
+
+ if (deactivate) {
+ group.active_socket_count--;
+
+ // Delete group if no longer needed.
+ if (group.active_socket_count == 0 && group.idle_sockets.empty()) {
+ DCHECK(group.pending_requests.empty());
+ DCHECK(group.connecting_requests.empty());
+ group_map_.erase(group_name);
+ } else {
+ CheckSocketCounts(group);
+ }
+ } else {
+ request.handle->set_socket(socket);
+ request.handle->set_is_reused(false);
+ group.sockets_handed_out_count++;
+
+ CheckSocketCounts(group);
+ }
+
+ RemoveConnectingSocket(request.handle);
+
+ return request.callback;
+}
+
+// static
+void ClientSocketPoolBase::CheckSocketCounts(const Group& group) {
+ CHECK(group.active_socket_count ==
+ group.sockets_handed_out_count +
+ static_cast<int>(group.connecting_requests.size()))
+ << "[active_socket_count: " << group.active_socket_count
+ << " ] [sockets_handed_out_count: " << group.sockets_handed_out_count
+ << " ] [connecting_requests size: " << group.connecting_requests.size();
+}
+
+void ClientSocketPoolBase::RemoveConnectingSocket(
+ const ClientSocketHandle* handle) {
+ ConnectingSocketMap::iterator it = connecting_socket_map_.find(handle);
+ CHECK(it != connecting_socket_map_.end());
+ delete it->second;
+ connecting_socket_map_.erase(it);
+}
+
+void ClientSocketPoolBase::ProcessPendingRequest(const std::string& group_name,
+ Group* group) {
+ Request r = group->pending_requests.front();
+ group->pending_requests.pop_front();
+
+ int rv = RequestSocket(
+ group_name, r.resolve_info, r.priority, r.handle, r.callback);
+
+ // |group| may be invalid after RequestSocket.
+
+ if (rv != ERR_IO_PENDING)
+ r.callback->Run(rv);
+}
+
+ConnectingSocket*
+TCPClientSocketPool::TCPConnectingSocketFactory::NewConnectingSocket(
+ const std::string& group_name,
+ const ClientSocketPoolBase::Request& request,
+ ClientSocketPoolBase* pool) const {
+ return new TCPConnectingSocket(
+ group_name, request.resolve_info, request.handle,
+ client_socket_factory_, pool);
+}
+
+TCPClientSocketPool::TCPClientSocketPool(
+ int max_sockets_per_group,
+ HostResolver* host_resolver,
+ ClientSocketFactory* client_socket_factory)
+ : base_(new ClientSocketPoolBase(
+ max_sockets_per_group, host_resolver,
+ new TCPConnectingSocketFactory(client_socket_factory))) {}
+
+TCPClientSocketPool::~TCPClientSocketPool() {}
+
+int TCPClientSocketPool::RequestSocket(
+ const std::string& group_name,
+ const HostResolver::RequestInfo& resolve_info,
+ int priority,
+ ClientSocketHandle* handle,
+ CompletionCallback* callback) {
+ return base_->RequestSocket(
+ group_name, resolve_info, priority, handle, callback);
+}
+
+void TCPClientSocketPool::CancelRequest(
+ const std::string& group_name,
+ const ClientSocketHandle* handle) {
+ base_->CancelRequest(group_name, handle);
+}
+
+void TCPClientSocketPool::ReleaseSocket(
+ const std::string& group_name,
+ ClientSocket* socket) {
+ base_->ReleaseSocket(group_name, socket);
+}
+
+void TCPClientSocketPool::CloseIdleSockets() {
+ base_->CloseIdleSockets();
+}
+
+int TCPClientSocketPool::IdleSocketCountInGroup(
+ const std::string& group_name) const {
+ return base_->IdleSocketCountInGroup(group_name);
+}
+
+LoadState TCPClientSocketPool::GetLoadState(
+ const std::string& group_name, const ClientSocketHandle* handle) const {
+ return base_->GetLoadState(group_name, handle);
+}
+
+} // namespace net