diff options
author | willchan@chromium.org <willchan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-06-10 04:52:53 +0000 |
---|---|---|
committer | willchan@chromium.org <willchan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-06-10 04:52:53 +0000 |
commit | 9a689bd50e409ac0d21375dd51a82206ca403cde (patch) | |
tree | fa750cc6fa02b023bee3d748d0b484eca20e2410 | |
parent | ea478a8069dec20dcbc3a8ae814386a0b444c1cb (diff) | |
download | chromium_src-9a689bd50e409ac0d21375dd51a82206ca403cde.zip chromium_src-9a689bd50e409ac0d21375dd51a82206ca403cde.tar.gz chromium_src-9a689bd50e409ac0d21375dd51a82206ca403cde.tar.bz2 |
Move much of the TCPClientSocketPool implementation out into ClientSocketPoolBase and TCPConnectingSocket for reuse.
When I write SSLClientSocketPool, it will need to reuse lots of this functionality. This isn't quite the interface we want yet, since we'll need a ConnectingSocket interface for both SSLConnectingSocket and TCPConnectingSocket to derive from, but this is a start.
BUG=http://crbug.com/13289
TEST=none
Review URL: http://codereview.chromium.org/118423
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@18034 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | net/base/client_socket_pool_base.cc | 342 | ||||
-rw-r--r-- | net/base/client_socket_pool_base.h | 177 | ||||
-rw-r--r-- | net/base/tcp_client_socket_pool.cc | 406 | ||||
-rw-r--r-- | net/base/tcp_client_socket_pool.h | 147 | ||||
-rw-r--r-- | net/base/tcp_connecting_socket.cc | 150 | ||||
-rw-r--r-- | net/base/tcp_connecting_socket.h | 80 | ||||
-rw-r--r-- | net/net.gyp | 4 |
7 files changed, 805 insertions, 501 deletions
diff --git a/net/base/client_socket_pool_base.cc b/net/base/client_socket_pool_base.cc new file mode 100644 index 0000000..92bb28d --- /dev/null +++ b/net/base/client_socket_pool_base.cc @@ -0,0 +1,342 @@ +// Copyright (c) 2009 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/base/client_socket_pool_base.h" + +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/time.h" +#include "base/stl_util-inl.h" +#include "net/base/client_socket_handle.h" +#include "net/base/net_errors.h" +#include "net/base/tcp_connecting_socket.h" + +using base::TimeDelta; + +namespace net { + +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. + +// 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. +void InsertRequestIntoQueue( + const ClientSocketPoolBase::Request& r, + ClientSocketPoolBase::RequestQueue* pending_requests) { + ClientSocketPoolBase::RequestQueue::iterator it = pending_requests->begin(); + while (it != pending_requests->end() && r.priority <= it->priority) + ++it; + pending_requests->insert(it, r); +} + +} // namespace + +ClientSocketPoolBase::ClientSocketPoolBase( + int max_sockets_per_group, + const ConnectingSocketFactory* connecting_socket_factory) + : idle_socket_count_(0), + max_sockets_per_group_(max_sockets_per_group), + 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. + CleanupIdleSockets(true /* force close all idle sockets */); + DCHECK(group_map_.empty()); +} + +int ClientSocketPoolBase::RequestSocket(const std::string& group_name, + const Request& request) { + DCHECK(!request.host.empty()); + DCHECK_GE(request.priority, 0); + DCHECK(request.callback); + DCHECK_EQ(LOAD_STATE_IDLE, request.load_state); + + Group& group = group_map_[group_name]; + + // Can we make another active socket now? + if (group.active_socket_count == max_sockets_per_group_) { + InsertRequestIntoQueue(request, &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! + request.handle->set_socket(idle_socket.socket); + request.handle->set_is_reused(true); + return OK; + } + delete idle_socket.socket; + } + + // We couldn't find a socket to reuse, so allocate and connect a new one. + + // First, we need to make sure we aren't already servicing a request for this + // handle (which could happen if we requested, canceled, and then requested + // with the same handle). + if (ContainsKey(connecting_socket_map_, request.handle)) + connecting_socket_map_[request.handle]->Cancel(); + + group.connecting_requests[request.handle] = request; + group.connecting_requests[request.handle].load_state = + LOAD_STATE_RESOLVING_HOST; + + TCPConnectingSocket* connecting_socket = + connecting_socket_factory_->CreateConnectingSocket(group_name, request); + connecting_socket_map_[request.handle] = connecting_socket; + + return connecting_socket->Connect(); +} + +void ClientSocketPoolBase::CancelRequest(const std::string& group_name, + const ClientSocketHandle* handle) { + DCHECK(ContainsKey(group_map_, group_name)); + + Group& group = group_map_[group_name]; + + // 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. + DCHECK(ContainsKey(group.connecting_requests, handle)); + + RequestMap::iterator map_it = group.connecting_requests.find(handle); + if (map_it != group.connecting_requests.end()) { + group.connecting_requests.erase(map_it); + 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); + } + } +} + +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)); +} + +int ClientSocketPoolBase::IdleSocketCountInGroup( + const std::string& group_name) const { + GroupMap::const_iterator i = group_map_.find(group_name); + DCHECK(i != group_map_.end()); + + return i->second.idle_sockets.size(); +} + +LoadState ClientSocketPoolBase::GetLoadState( + const std::string& group_name, + const ClientSocketHandle* handle) const { + DCHECK(ContainsKey(group_map_, group_name)) << group_name; + + // 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; + DCHECK(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) { + DCHECK_EQ(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()) { + DCHECK(group.pending_requests.empty()); + DCHECK(group.connecting_requests.empty()); + group_map_.erase(i++); + } else { + ++i; + } + } +} + +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) { + DCHECK((deactivate && !socket) || (!deactivate && socket)); + DCHECK(ContainsKey(group_map_, group_name)); + Group& group = group_map_[group_name]; + + RequestMap* request_map = &group.connecting_requests; + + // TODO(willchan): For decoupling connecting sockets from requests, don't use + // |handle|. Simply pop the first request from |connecting_requests|. Handle + // the case where |connecting_requests| is empty by calling DoReleaseSocket(). + DCHECK(ContainsKey(*request_map, handle)); + RequestMap::iterator it = request_map->find(handle); + 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 { + request.handle->set_socket(socket); + request.handle->set_is_reused(false); + } + + // TODO(willchan): Don't bother with this when decoupling connecting sockets + // from requests. + connecting_socket_map_.erase(handle); + + return request.callback; +} + +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); + DCHECK(i != group_map_.end()); + + Group& group = i->second; + + DCHECK_GT(group.active_socket_count, 0); + group.active_socket_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()) { + Request r = group.pending_requests.front(); + group.pending_requests.pop_front(); + + int rv = RequestSocket(group_name, r); + if (rv != ERR_IO_PENDING) + r.callback->Run(rv); + return; + } + + // 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(i); + } +} + +} // namespace net diff --git a/net/base/client_socket_pool_base.h b/net/base/client_socket_pool_base.h new file mode 100644 index 0000000..c18c5b7 --- /dev/null +++ b/net/base/client_socket_pool_base.h @@ -0,0 +1,177 @@ +// Copyright (c) 2009 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. +// +// ClientSocketPoolBase is an implementation class for building new +// ClientSocketPools. New ClientSocketPools should compose +// ClientSocketPoolBase. ClientSocketPoolBase provides functionality for +// managing socket reuse and restricting the number of open sockets within a +// "group". It always returns a connected socket. Clients of +// ClientSocketPoolBase need to provide a ConnectingSocketFactory to generate +// ConnectingSockets that actually perform the socket connection. + +#ifndef NET_BASE_CLIENT_SOCKET_POOL_BASE_H_ +#define NET_BASE_CLIENT_SOCKET_POOL_BASE_H_ + +#include <deque> +#include <map> +#include <string> + +#include "base/basictypes.h" +#include "base/ref_counted.h" +#include "base/scoped_ptr.h" +#include "base/task.h" +#include "base/timer.h" +#include "net/base/completion_callback.h" +#include "net/base/load_states.h" + +namespace net { + +class ClientSocket; +class ClientSocketHandle; +class TCPConnectingSocket; + +// A ClientSocketPoolBase is used to restrict the number of sockets open at +// a time. It also maintains a list of idle persistent sockets. +class ClientSocketPoolBase : public base::RefCounted<ClientSocketPoolBase> { + public: + // A Request is allocated per call to RequestSocket that results in + // ERR_IO_PENDING. + struct Request { + ClientSocketHandle* handle; + CompletionCallback* callback; + int priority; + std::string host; + int port; + LoadState load_state; + }; + + // Entry for a persistent socket which became idle at time |start_time|. + struct IdleSocket { + ClientSocket* socket; + base::TimeTicks start_time; + + // An idle socket should be removed if it can't be reused, or has been idle + // for too long. |now| is the current time value (TimeTicks::Now()). + // + // An idle socket can't be reused if it is disconnected or has received + // data unexpectedly (hence no longer idle). The unread data would be + // mistaken for the beginning of the next response if we were to reuse the + // socket for a new request. + bool ShouldCleanup(base::TimeTicks now) const; + }; + + typedef std::deque<Request> RequestQueue; + typedef std::map<const ClientSocketHandle*, Request> RequestMap; + + // A Group is allocated per group_name when there are idle sockets or pending + // requests. Otherwise, the Group object is removed from the map. + struct Group { + Group() : active_socket_count(0) {} + std::deque<IdleSocket> idle_sockets; + RequestQueue pending_requests; + RequestMap connecting_requests; + int active_socket_count; + }; + + typedef std::map<std::string, Group> GroupMap; + + // TODO(willchan): Change the return value of CreateConnectingSocket to be + // ConnectingSocket*, once I've extracted out an interface for it. + class ConnectingSocketFactory { + public: + ConnectingSocketFactory() {} + virtual ~ConnectingSocketFactory() {} + + // Creates a TCPConnectingSocket. Never returns NULL. + virtual TCPConnectingSocket* CreateConnectingSocket( + const std::string& group_name, const Request& request) const = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(ConnectingSocketFactory); + }; + + ClientSocketPoolBase( + int max_sockets_per_group, + const ConnectingSocketFactory* connecting_socket_factory); + ~ClientSocketPoolBase(); + + int RequestSocket(const std::string& group_name, const Request& request); + + void CancelRequest(const std::string& group_name, + const ClientSocketHandle* handle); + + void ReleaseSocket(const std::string& group_name, ClientSocket* socket); + + int idle_socket_count() const { return idle_socket_count_; } + + int IdleSocketCountInGroup(const std::string& group_name) const; + + LoadState GetLoadState(const std::string& group_name, + const ClientSocketHandle* handle) const; + + // Closes all idle sockets if |force| is true. Else, only closes idle + // sockets that timed out or can't be reused. + void CleanupIdleSockets(bool force); + + // Used by ConnectingSocket until we remove the coupling between a specific + // ConnectingSocket and a ClientSocketHandle: + + // Returns NULL if not found. Otherwise it returns the Request* + // corresponding to the ConnectingSocket (keyed by |group_name| and |handle|. + // Note that this pointer may be invalidated after any call that might mutate + // the RequestMap or GroupMap, so the user should not hold onto the pointer + // for long. + Request* GetConnectingRequest(const std::string& group_name, + const ClientSocketHandle* handle); + + // Handles the completed Request corresponding to the ConnectingSocket (keyed + // by |group_name| and |handle|. |deactivate| indicates whether or not to + // deactivate the socket, making the socket slot available for a new socket + // connection. If |deactivate| is false, then set |socket| into |handle|. + // Returns the callback to run. + // TODO(willchan): When we decouple Requests and ConnectingSockets, this will + // return the callback of the request we select. + CompletionCallback* OnConnectingRequestComplete( + const std::string& group_name, + const ClientSocketHandle* handle, + bool deactivate, + ClientSocket* socket); + + private: + // Called when the number of idle sockets changes. + void IncrementIdleCount(); + void DecrementIdleCount(); + + // Called via PostTask by ReleaseSocket. + void DoReleaseSocket(const std::string& group_name, ClientSocket* socket); + + // Called when timer_ fires. This method scans the idle sockets removing + // sockets that timed out or can't be reused. + void OnCleanupTimerFired() { + CleanupIdleSockets(false); + } + + GroupMap group_map_; + + std::map<const ClientSocketHandle*, TCPConnectingSocket*> + connecting_socket_map_; + + // Timer used to periodically prune idle sockets that timed out or can't be + // reused. + base::RepeatingTimer<ClientSocketPoolBase> timer_; + + // The total number of idle sockets in the system. + int idle_socket_count_; + + // The maximum number of sockets kept per group. + const int max_sockets_per_group_; + + const ConnectingSocketFactory* const connecting_socket_factory_; + + DISALLOW_COPY_AND_ASSIGN(ClientSocketPoolBase); +}; + +} // namespace net + +#endif // NET_BASE_CLIENT_SOCKET_POOL_BASE_H_ diff --git a/net/base/tcp_client_socket_pool.cc b/net/base/tcp_client_socket_pool.cc index 75875f2..3f895f7 100644 --- a/net/base/tcp_client_socket_pool.cc +++ b/net/base/tcp_client_socket_pool.cc @@ -14,176 +14,20 @@ #include "net/base/dns_resolution_observer.h" #include "net/base/net_errors.h" #include "net/base/tcp_client_socket.h" +#include "net/base/tcp_connecting_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 { -TCPClientSocketPool::ConnectingSocket::ConnectingSocket( - const std::string& group_name, - const ClientSocketHandle* handle, - ClientSocketFactory* client_socket_factory, - TCPClientSocketPool* pool) - : group_name_(group_name), - handle_(handle), - client_socket_factory_(client_socket_factory), - ALLOW_THIS_IN_INITIALIZER_LIST( - callback_(this, - &TCPClientSocketPool::ConnectingSocket::OnIOComplete)), - pool_(pool), - canceled_(false) { - DCHECK(!ContainsKey(pool_->connecting_socket_map_, handle)); - pool_->connecting_socket_map_[handle] = this; -} - -TCPClientSocketPool::ConnectingSocket::~ConnectingSocket() { - if (!canceled_) - pool_->connecting_socket_map_.erase(handle_); -} - -int TCPClientSocketPool::ConnectingSocket::Connect( - const std::string& host, - int port) { - DCHECK(!canceled_); - DidStartDnsResolution(host, this); - int rv = resolver_.Resolve(host, port, &addresses_, &callback_); - if (rv != ERR_IO_PENDING) - rv = OnIOCompleteInternal(rv, true /* synchronous */); - return rv; -} - -void TCPClientSocketPool::ConnectingSocket::OnIOComplete(int result) { - OnIOCompleteInternal(result, false /* asynchronous */); -} - -int TCPClientSocketPool::ConnectingSocket::OnIOCompleteInternal( - int result, bool synchronous) { - DCHECK_NE(result, ERR_IO_PENDING); - - if (canceled_) { - // We got canceled, so bail out. - delete this; - return result; - } - - GroupMap::iterator group_it = pool_->group_map_.find(group_name_); - if (group_it == pool_->group_map_.end()) { - // The request corresponding to this ConnectingSocket has been canceled. - // Stop bothering with it. - delete this; - return result; - } - - Group& group = group_it->second; - - RequestMap* request_map = &group.connecting_requests; - RequestMap::iterator it = request_map->find(handle_); - if (it == request_map->end()) { - // The request corresponding to this ConnectingSocket has been canceled. - // Stop bothering with it. - delete this; - return result; - } - - if (result == OK && it->second.load_state == LOAD_STATE_RESOLVING_HOST) { - it->second.load_state = LOAD_STATE_CONNECTING; - socket_.reset(client_socket_factory_->CreateTCPClientSocket(addresses_)); - connect_start_time_ = base::Time::Now(); - result = socket_->Connect(&callback_); - if (result == ERR_IO_PENDING) - return result; - } - - if (result == OK) { - DCHECK(connect_start_time_ != base::Time()); - base::TimeDelta connect_duration = - base::Time::Now() - connect_start_time_; - - UMA_HISTOGRAM_CLIPPED_TIMES( - FieldTrial::MakeName( - "Net.TCP_Connection_Latency", "DnsImpact").data(), - 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. - - Request request = it->second; - request_map->erase(it); - - if (result == OK) { - request.handle->set_socket(socket_.release()); - request.handle->set_is_reused(false); - } else { - 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()); - pool_->group_map_.erase(group_it); - } - } - - if (!synchronous) - request.callback->Run(result); - delete this; - return result; -} - -void TCPClientSocketPool::ConnectingSocket::Cancel() { - DCHECK(!canceled_); - DCHECK(ContainsKey(pool_->connecting_socket_map_, handle_)); - pool_->connecting_socket_map_.erase(handle_); - canceled_ = true; -} - TCPClientSocketPool::TCPClientSocketPool( int max_sockets_per_group, ClientSocketFactory* client_socket_factory) - : client_socket_factory_(client_socket_factory), - idle_socket_count_(0), - max_sockets_per_group_(max_sockets_per_group) { -} + : base_(new ClientSocketPoolBase(max_sockets_per_group, + &connecting_socket_factory_)), + connecting_socket_factory_(base_, client_socket_factory) {} -TCPClientSocketPool::~TCPClientSocketPool() { - // 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()); -} - -// 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 TCPClientSocketPool::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); -} +TCPClientSocketPool::~TCPClientSocketPool() {} int TCPClientSocketPool::RequestSocket(const std::string& group_name, const std::string& host, @@ -191,241 +35,61 @@ int TCPClientSocketPool::RequestSocket(const std::string& group_name, int priority, ClientSocketHandle* handle, CompletionCallback* callback) { - DCHECK(!host.empty()); - DCHECK_GE(priority, 0); - Group& group = group_map_[group_name]; - - // Can we make another active socket now? - if (group.active_socket_count == max_sockets_per_group_) { - Request r; - r.handle = handle; - DCHECK(callback); - r.callback = callback; - r.priority = priority; - r.host = host; - r.port = port; - r.load_state = 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); - return OK; - } - delete idle_socket.socket; - } - - // We couldn't find a socket to reuse, so allocate and connect a new one. - - // First, we need to make sure we aren't already servicing a request for this - // handle (which could happen if we requested, canceled, and then requested - // with the same handle). - if (ContainsKey(connecting_socket_map_, handle)) - connecting_socket_map_[handle]->Cancel(); - - Request r; - r.handle = handle; - DCHECK(callback); - r.callback = callback; - r.priority = priority; - r.host = host; - r.port = port; - r.load_state = LOAD_STATE_RESOLVING_HOST; - group_map_[group_name].connecting_requests[handle] = r; - - // connecting_socket will delete itself. - ConnectingSocket* connecting_socket = - new ConnectingSocket(group_name, handle, client_socket_factory_, this); - int rv = connecting_socket->Connect(host, port); - return rv; + ClientSocketPoolBase::Request request; + request.host = host; + request.port = port; + request.priority = priority; + request.handle = handle; + request.callback = callback; + request.load_state = LOAD_STATE_IDLE; + return base_->RequestSocket(group_name, request); } void TCPClientSocketPool::CancelRequest(const std::string& group_name, const ClientSocketHandle* handle) { - DCHECK(ContainsKey(group_map_, group_name)); - - Group& group = group_map_[group_name]; - - // 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. - DCHECK(ContainsKey(group.connecting_requests, handle)); - - RequestMap::iterator map_it = group.connecting_requests.find(handle); - if (map_it != group.connecting_requests.end()) { - group.connecting_requests.erase(map_it); - 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); - } - } + base_->CancelRequest(group_name, handle); } void TCPClientSocketPool::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, &TCPClientSocketPool::DoReleaseSocket, group_name, socket)); + base_->ReleaseSocket(group_name, socket); } void TCPClientSocketPool::CloseIdleSockets() { - CleanupIdleSockets(true); + base_->CleanupIdleSockets(true); } int TCPClientSocketPool::IdleSocketCountInGroup( const std::string& group_name) const { - GroupMap::const_iterator i = group_map_.find(group_name); - DCHECK(i != group_map_.end()); - - return i->second.idle_sockets.size(); + return base_->IdleSocketCountInGroup(group_name); } LoadState TCPClientSocketPool::GetLoadState( const std::string& group_name, const ClientSocketHandle* handle) const { - DCHECK(ContainsKey(group_map_, group_name)) << group_name; - - // 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; - DCHECK(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) { - DCHECK_EQ(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 TCPClientSocketPool::IdleSocket::ShouldCleanup(base::TimeTicks now) const { - bool timed_out = (now - start_time) >= - base::TimeDelta::FromSeconds(kIdleTimeout); - return timed_out || !socket->IsConnectedAndIdle(); + return base_->GetLoadState(group_name, handle); } -void TCPClientSocketPool::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()) { - DCHECK(group.pending_requests.empty()); - DCHECK(group.connecting_requests.empty()); - group_map_.erase(i++); - } else { - ++i; - } - } -} - -void TCPClientSocketPool::IncrementIdleCount() { - if (++idle_socket_count_ == 1) - timer_.Start(TimeDelta::FromSeconds(kCleanupInterval), this, - &TCPClientSocketPool::OnCleanupTimerFired); -} +TCPClientSocketPool::TCPConnectingSocketFactory::TCPConnectingSocketFactory( + const scoped_refptr<ClientSocketPoolBase>& base, + ClientSocketFactory* client_socket_factory) + : base_(base), + client_socket_factory_(client_socket_factory) {} -void TCPClientSocketPool::DecrementIdleCount() { - if (--idle_socket_count_ == 0) - timer_.Stop(); +TCPClientSocketPool::TCPConnectingSocketFactory::~TCPConnectingSocketFactory() { } -void TCPClientSocketPool::DoReleaseSocket(const std::string& group_name, - ClientSocket* socket) { - GroupMap::iterator i = group_map_.find(group_name); - DCHECK(i != group_map_.end()); - - Group& group = i->second; - - DCHECK_GT(group.active_socket_count, 0); - group.active_socket_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()) { - Request r = group.pending_requests.front(); - group.pending_requests.pop_front(); - - int rv = RequestSocket( - group_name, r.host, r.port, r.priority, r.handle, r.callback); - if (rv != ERR_IO_PENDING) - r.callback->Run(rv); - return; - } - - // 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(i); - } +TCPConnectingSocket* +TCPClientSocketPool::TCPConnectingSocketFactory::CreateConnectingSocket( + const std::string& group_name, + const ClientSocketPoolBase::Request& request) const { + return new TCPConnectingSocket(group_name, + request.host, + request.port, + request.handle, + request.callback, + client_socket_factory_, + base_); } } // namespace net diff --git a/net/base/tcp_client_socket_pool.h b/net/base/tcp_client_socket_pool.h index 8d4a3bd..c92a081 100644 --- a/net/base/tcp_client_socket_pool.h +++ b/net/base/tcp_client_socket_pool.h @@ -5,19 +5,15 @@ #ifndef NET_BASE_TCP_CLIENT_SOCKET_POOL_H_ #define NET_BASE_TCP_CLIENT_SOCKET_POOL_H_ -#include <deque> -#include <map> #include <string> -#include "base/scoped_ptr.h" -#include "base/timer.h" -#include "net/base/address_list.h" #include "net/base/client_socket_pool.h" -#include "net/base/host_resolver.h" +#include "net/base/client_socket_pool_base.h" namespace net { class ClientSocketFactory; +class TCPConnectingSocket; // A TCPClientSocketPool is used to restrict the number of TCP sockets open at // a time. It also maintains a list of idle persistent sockets. @@ -44,9 +40,7 @@ class TCPClientSocketPool : public ClientSocketPool { virtual void CloseIdleSockets(); - virtual int idle_socket_count() const { - return idle_socket_count_; - } + virtual int idle_socket_count() const { return base_->idle_socket_count(); } virtual int IdleSocketCountInGroup(const std::string& group_name) const; @@ -54,136 +48,29 @@ class TCPClientSocketPool : public ClientSocketPool { const ClientSocketHandle* handle) const; private: - // A Request is allocated per call to RequestSocket that results in - // ERR_IO_PENDING. - struct Request { - ClientSocketHandle* handle; - CompletionCallback* callback; - int priority; - std::string host; - int port; - LoadState load_state; - }; - - // Entry for a persistent socket which became idle at time |start_time|. - struct IdleSocket { - ClientSocket* socket; - base::TimeTicks start_time; - - // An idle socket should be removed if it can't be reused, or has been idle - // for too long. |now| is the current time value (TimeTicks::Now()). - // - // An idle socket can't be reused if it is disconnected or has received - // data unexpectedly (hence no longer idle). The unread data would be - // mistaken for the beginning of the next response if we were to reuse the - // socket for a new request. - bool ShouldCleanup(base::TimeTicks now) const; - }; - - typedef std::deque<Request> RequestQueue; - typedef std::map<const ClientSocketHandle*, Request> RequestMap; - - // A Group is allocated per group_name when there are idle sockets or pending - // requests. Otherwise, the Group object is removed from the map. - struct Group { - Group() : active_socket_count(0) {} - std::deque<IdleSocket> idle_sockets; - RequestQueue pending_requests; - RequestMap connecting_requests; - int active_socket_count; - }; - - typedef std::map<std::string, Group> GroupMap; - - // ConnectingSocket handles the host resolution necessary for socket creation - // and the Connect(). - class ConnectingSocket { + class TCPConnectingSocketFactory + : public ClientSocketPoolBase::ConnectingSocketFactory { public: - enum State { - STATE_RESOLVE_HOST, - STATE_CONNECT - }; - - ConnectingSocket(const std::string& group_name, - const ClientSocketHandle* handle, - ClientSocketFactory* client_socket_factory, - TCPClientSocketPool* pool); - ~ConnectingSocket(); - - // Begins the host resolution and the TCP connect. Returns OK on success - // and ERR_IO_PENDING if it cannot immediately service the request. - // Otherwise, it returns a net error code. - int Connect(const std::string& host, int port); - - // Called by the TCPClientSocketPool to cancel this ConnectingSocket. Only - // necessary if a ClientSocketHandle is reused. - void Cancel(); + TCPConnectingSocketFactory( + const scoped_refptr<ClientSocketPoolBase>& base, + ClientSocketFactory* factory); + virtual ~TCPConnectingSocketFactory(); + + virtual TCPConnectingSocket* CreateConnectingSocket( + const std::string& group_name, + const ClientSocketPoolBase::Request& request) const; private: - // Handles asynchronous completion of IO. |result| represents the result of - // the IO operation. - void OnIOComplete(int result); - - // Handles both asynchronous and synchronous completion of IO. |result| - // represents the result of the IO operation. |synchronous| indicates - // whether or not the previous IO operation completed synchronously or - // asynchronously. OnIOCompleteInternal returns the result of the next IO - // operation that executes, or just the value of |result|. - int OnIOCompleteInternal(int result, bool synchronous); - - const std::string group_name_; - const ClientSocketHandle* const handle_; + const scoped_refptr<ClientSocketPoolBase> base_; ClientSocketFactory* const client_socket_factory_; - CompletionCallbackImpl<ConnectingSocket> callback_; - scoped_ptr<ClientSocket> socket_; - scoped_refptr<TCPClientSocketPool> pool_; - HostResolver resolver_; - AddressList addresses_; - bool canceled_; - // The time the Connect() method was called (if it got called). - base::Time connect_start_time_; - - DISALLOW_COPY_AND_ASSIGN(ConnectingSocket); + DISALLOW_COPY_AND_ASSIGN(TCPConnectingSocketFactory); }; virtual ~TCPClientSocketPool(); - static void InsertRequestIntoQueue(const Request& r, - RequestQueue* pending_requests); - - // Closes all idle sockets if |force| is true. Else, only closes idle - // sockets that timed out or can't be reused. - void CleanupIdleSockets(bool force); - - // Called when the number of idle sockets changes. - void IncrementIdleCount(); - void DecrementIdleCount(); - - // Called via PostTask by ReleaseSocket. - void DoReleaseSocket(const std::string& group_name, ClientSocket* socket); - - // Called when timer_ fires. This method scans the idle sockets removing - // sockets that timed out or can't be reused. - void OnCleanupTimerFired() { - CleanupIdleSockets(false); - } - - ClientSocketFactory* const client_socket_factory_; - - GroupMap group_map_; - - std::map<const ClientSocketHandle*, ConnectingSocket*> connecting_socket_map_; - - // Timer used to periodically prune idle sockets that timed out or can't be - // reused. - base::RepeatingTimer<TCPClientSocketPool> timer_; - - // The total number of idle sockets in the system. - int idle_socket_count_; - - // The maximum number of sockets kept per group. - const int max_sockets_per_group_; + const scoped_refptr<ClientSocketPoolBase> base_; + const TCPConnectingSocketFactory connecting_socket_factory_; DISALLOW_COPY_AND_ASSIGN(TCPClientSocketPool); }; diff --git a/net/base/tcp_connecting_socket.cc b/net/base/tcp_connecting_socket.cc new file mode 100644 index 0000000..82e1952 --- /dev/null +++ b/net/base/tcp_connecting_socket.cc @@ -0,0 +1,150 @@ +// 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/base/tcp_connecting_socket.h" + +#include "base/compiler_specific.h" +#include "base/field_trial.h" +#include "base/stl_util-inl.h" +#include "base/time.h" +#include "net/base/client_socket_factory.h" +#include "net/base/client_socket_handle.h" +#include "net/base/client_socket_pool_base.h" +#include "net/base/dns_resolution_observer.h" +#include "net/base/net_errors.h" +#include "net/base/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 std::string& host, + int port, + const ClientSocketHandle* handle, + CompletionCallback* callback, + ClientSocketFactory* client_socket_factory, + const scoped_refptr<ClientSocketPoolBase>& pool) + : group_name_(group_name), + host_(host), + port_(port), + handle_(handle), + user_callback_(callback), + client_socket_factory_(client_socket_factory), + ALLOW_THIS_IN_INITIALIZER_LIST( + callback_(this, + &TCPConnectingSocket::OnIOComplete)), + pool_(pool), + canceled_(false) {} + +TCPConnectingSocket::~TCPConnectingSocket() {} + +int TCPConnectingSocket::Connect() { + DCHECK(!canceled_); + DidStartDnsResolution(host_, this); + int rv = resolver_.Resolve(host_, port_, &addresses_, &callback_); + if (rv == OK) { + rv = OnIOCompleteInternal(rv, true /* synchronous */); + } + return rv; +} + +void TCPConnectingSocket::OnIOComplete(int result) { + OnIOCompleteInternal(result, false /* asynchronous */); +} + +int TCPConnectingSocket::OnIOCompleteInternal(int result, bool synchronous) { + DCHECK_NE(result, ERR_IO_PENDING); + + if (canceled_) { + // We got canceled, so bail out. + delete this; + return result; + } + + ClientSocketPoolBase::Request* request = pool_->GetConnectingRequest( + group_name_, handle_); + if (!request) { + // The request corresponding to this TCPConnectingSocket has been canceled. + // Stop bothering with it. + delete this; + return result; + } + + 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::Time::Now(); + result = socket_->Connect(&callback_); + if (result == ERR_IO_PENDING) + return result; + } + + if (result == OK) { + DCHECK_EQ(request->load_state, LOAD_STATE_CONNECTING); + DCHECK(connect_start_time_ != base::Time()); + base::TimeDelta connect_duration = + base::Time::Now() - connect_start_time_; + + UMA_HISTOGRAM_CLIPPED_TIMES( + FieldTrial::MakeName( + "Net.TCP_Connection_Latency", "DnsImpact").data(), + 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 socket to give */); + } + + // TODO(willchan): Eventually this DCHECK will not be true, once we timeout + // slow connecting sockets and allocate extra connecting sockets to avoid the + // 3s timeout. + DCHECK(callback); + + if (!synchronous) + callback->Run(result); + delete this; + return result; +} + +void TCPConnectingSocket::Cancel() { + DCHECK(!canceled_); + canceled_ = true; +} + +} // namespace net diff --git a/net/base/tcp_connecting_socket.h b/net/base/tcp_connecting_socket.h new file mode 100644 index 0000000..7096692 --- /dev/null +++ b/net/base/tcp_connecting_socket.h @@ -0,0 +1,80 @@ +// 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. + +#ifndef NET_BASE_TCP_CONNECTING_SOCKET_H_ +#define NET_BASE_TCP_CONNECTING_SOCKET_H_ + +#include <deque> +#include <map> +#include <string> + +#include "base/scoped_ptr.h" +#include "base/timer.h" +#include "net/base/address_list.h" +#include "net/base/client_socket_pool.h" +#include "net/base/host_resolver.h" + +namespace net { + +class ClientSocketFactory; +class ClientSocketPoolBase; + +// TCPConnectingSocket handles host resolution and socket connection for a TCP +// socket. +class TCPConnectingSocket { + public: + TCPConnectingSocket(const std::string& group_name, + const std::string& host, + int port, + const ClientSocketHandle* handle, + CompletionCallback* callback, + ClientSocketFactory* client_socket_factory, + const scoped_refptr<ClientSocketPoolBase>& pool); + ~TCPConnectingSocket(); + + // Begins the host resolution and the TCP connect. Returns OK on success + // and ERR_IO_PENDING if it cannot immediately service the request. + // Otherwise, it returns a net error code. + int Connect(); + + // TODO(willchan): Delete this function once we decouple connecting sockets + // from requests, since we'll keep around the idle connected socket. + // Called by the ClientSocketPoolBase to cancel this TCPConnectingSocket. + // Only necessary if a ClientSocketHandle is reused. + void Cancel(); + + private: + // Handles asynchronous completion of IO. |result| represents the result of + // the IO operation. + void OnIOComplete(int result); + + // Handles both asynchronous and synchronous completion of IO. |result| + // represents the result of the IO operation. |synchronous| indicates + // whether or not the previous IO operation completed synchronously or + // asynchronously. OnIOCompleteInternal returns the result of the next IO + // operation that executes, or just the value of |result|. + int OnIOCompleteInternal(int result, bool synchronous); + + const std::string group_name_; + const std::string host_; + const int port_; + const ClientSocketHandle* const handle_; + CompletionCallback* const user_callback_; + ClientSocketFactory* const client_socket_factory_; + CompletionCallbackImpl<TCPConnectingSocket> callback_; + scoped_ptr<ClientSocket> socket_; + const scoped_refptr<ClientSocketPoolBase> pool_; + HostResolver resolver_; + AddressList addresses_; + bool canceled_; + + // The time the Connect() method was called (if it got called). + base::Time connect_start_time_; + + DISALLOW_COPY_AND_ASSIGN(TCPConnectingSocket); +}; + +} // namespace net + +#endif // NET_BASE_TCP_CONNECTING_SOCKET_H_ diff --git a/net/net.gyp b/net/net.gyp index 37bc3a7..66f888d 100644 --- a/net/net.gyp +++ b/net/net.gyp @@ -50,6 +50,8 @@ 'base/client_socket_handle.cc', 'base/client_socket_handle.h', 'base/client_socket_pool.h', + 'base/client_socket_pool_base.cc', + 'base/client_socket_pool_base.h', 'base/completion_callback.h', 'base/connection_type_histograms.cc', 'base/connection_type_histograms.h', @@ -130,6 +132,8 @@ 'base/tcp_client_socket_pool.h', 'base/tcp_client_socket_win.cc', 'base/tcp_client_socket_win.h', + 'base/tcp_connecting_socket.cc', + 'base/tcp_connecting_socket.h', 'base/telnet_server.cc', 'base/telnet_server.h', 'base/upload_data.cc', |