diff options
Diffstat (limited to 'net/base')
-rw-r--r-- | net/base/client_socket_handle.cc | 51 | ||||
-rw-r--r-- | net/base/client_socket_handle.h | 54 | ||||
-rw-r--r-- | net/base/client_socket_pool.cc | 273 | ||||
-rw-r--r-- | net/base/client_socket_pool.h | 126 | ||||
-rw-r--r-- | net/base/client_socket_pool_unittest.cc | 274 | ||||
-rw-r--r-- | net/base/test_completion_callback.h | 2 |
6 files changed, 599 insertions, 181 deletions
diff --git a/net/base/client_socket_handle.cc b/net/base/client_socket_handle.cc index f5ab056..396fe32 100644 --- a/net/base/client_socket_handle.cc +++ b/net/base/client_socket_handle.cc @@ -4,37 +4,70 @@ #include "net/base/client_socket_handle.h" +#include "base/compiler_specific.h" #include "net/base/client_socket.h" #include "net/base/client_socket_pool.h" +#include "net/base/net_errors.h" namespace net { ClientSocketHandle::ClientSocketHandle(ClientSocketPool* pool) - : pool_(pool), socket_(NULL) { -} + : pool_(pool), + socket_(NULL), + is_reused_(false), + ALLOW_THIS_IN_INITIALIZER_LIST( + callback_(this, &ClientSocketHandle::OnIOComplete)) {} ClientSocketHandle::~ClientSocketHandle() { Reset(); } int ClientSocketHandle::Init(const std::string& group_name, + const std::string& host, + int port, int priority, CompletionCallback* callback) { - Reset(); + ResetInternal(true); group_name_ = group_name; - return pool_->RequestSocket(this, priority, callback); + user_callback_ = callback; + return pool_->RequestSocket( + group_name, host, port, priority, this, &callback_); } void ClientSocketHandle::Reset() { + ResetInternal(true); +} + +void ClientSocketHandle::ResetInternal(bool cancel) { if (group_name_.empty()) // Was Init called? return; - if (socket_) { - pool_->ReleaseSocket(this); - socket_ = NULL; - } else { - pool_->CancelRequest(this); + if (socket_.get()) { + // If we've still got a socket, release it back to the ClientSocketPool so + // it can be deleted or reused. + pool_->ReleaseSocket(group_name_, release_socket()); + } else if (cancel) { + // If we did not get initialized yet, so we've got a socket request pending. + // Cancel it. + pool_->CancelRequest(group_name_, this); } group_name_.clear(); + is_reused_ = false; + user_callback_ = NULL; +} + +LoadState ClientSocketHandle::GetLoadState() const { + DCHECK(!is_initialized()); + DCHECK(!group_name_.empty()); + return pool_->GetLoadState(group_name_, this); +} + +void ClientSocketHandle::OnIOComplete(int result) { + DCHECK_NE(ERR_IO_PENDING, result); + CompletionCallback* callback = user_callback_; + user_callback_ = NULL; + if (result != OK) + ResetInternal(false); // The request failed, so there's nothing to cancel. + callback->Run(result); } } // namespace net diff --git a/net/base/client_socket_handle.h b/net/base/client_socket_handle.h index 1328369..5bac2c8 100644 --- a/net/base/client_socket_handle.h +++ b/net/base/client_socket_handle.h @@ -11,40 +11,43 @@ #include "base/scoped_ptr.h" #include "net/base/client_socket.h" #include "net/base/completion_callback.h" +#include "net/base/load_states.h" namespace net { class ClientSocketPool; -// A container for a connected ClientSocket. +// A container for a ClientSocket. // // The handle's |group_name| uniquely identifies the origin and type of the // connection. It is used by the ClientSocketPool to group similar connected // client socket objects. // -// A handle is initialized with a null socket. It is the consumer's job to -// initialize a ClientSocket object and set it on the handle. -// class ClientSocketHandle { public: explicit ClientSocketHandle(ClientSocketPool* pool); ~ClientSocketHandle(); // Initializes a ClientSocketHandle object, which involves talking to the - // ClientSocketPool to locate a socket to possibly reuse. This method - // returns either OK or ERR_IO_PENDING. On ERR_IO_PENDING, |priority| is - // used to determine the placement in ClientSocketPool's wait list. + // ClientSocketPool to obtain a connected socket, possibly reusing one. This + // method returns either OK or ERR_IO_PENDING. On ERR_IO_PENDING, |priority| + // is used to determine the placement in ClientSocketPool's wait list. // // If this method succeeds, then the socket member will be set to an existing - // socket if an existing socket was available to reuse. Otherwise, the - // consumer should set the socket member of this handle. + // connected socket if an existing connected socket was available to reuse, + // otherwise it will be set to a new connected socket. Consumers can then + // call is_reused() to see if the socket was reused. If not reusing an + // existing socket, ClientSocketPool may need to establish a new + // connection to the |host| |port| pair. // // This method returns ERR_IO_PENDING if it cannot complete synchronously, in - // which case the consumer should wait for the completion callback to run. + // which case the consumer will be notified of completion via |callback|. // // Init may be called multiple times. // int Init(const std::string& group_name, + const std::string& host, + int port, int priority, CompletionCallback* callback); @@ -54,25 +57,40 @@ class ClientSocketHandle { // the socket may be kept alive for use by a subsequent ClientSocketHandle. // // NOTE: To prevent the socket from being kept alive, be sure to call its - // Disconnect method. - // + // Disconnect method. This will result in the ClientSocketPool deleting the + // ClientSocket. void Reset(); - // Returns true when Init has completed successfully. + // Used after Init() is called, but before the ClientSocketPool has + // initialized the ClientSocketHandle. + LoadState GetLoadState() const; + + // Returns true when Init() has completed successfully. bool is_initialized() const { return socket_ != NULL; } + // Used by ClientSocketPool to initialize the ClientSocketHandle. + void set_is_reused(bool is_reused) { is_reused_ = is_reused; } + void set_socket(ClientSocket* s) { socket_.reset(s); } + // These may only be used if is_initialized() is true. const std::string& group_name() const { return group_name_; } - ClientSocket* socket() { return socket_->get(); } - ClientSocket* release_socket() { return socket_->release(); } - void set_socket(ClientSocket* s) { socket_->reset(s); } + ClientSocket* socket() { return socket_.get(); } + ClientSocket* release_socket() { return socket_.release(); } + bool is_reused() const { return is_reused_; } private: - friend class ClientSocketPool; + void OnIOComplete(int result); + + // Resets the state of the ClientSocketHandle. |cancel| indicates whether or + // not to try to cancel the request with the ClientSocketPool. + void ResetInternal(bool cancel); scoped_refptr<ClientSocketPool> pool_; - scoped_ptr<ClientSocket>* socket_; + scoped_ptr<ClientSocket> socket_; std::string group_name_; + bool is_reused_; + CompletionCallbackImpl<ClientSocketHandle> callback_; + CompletionCallback* user_callback_; DISALLOW_COPY_AND_ASSIGN(ClientSocketHandle); }; diff --git a/net/base/client_socket_pool.cc b/net/base/client_socket_pool.cc index 4171b0c..9f14fe7 100644 --- a/net/base/client_socket_pool.cc +++ b/net/base/client_socket_pool.cc @@ -4,10 +4,16 @@ #include "net/base/client_socket_pool.h" +#include "base/compiler_specific.h" +#include "base/field_trial.h" #include "base/message_loop.h" -#include "net/base/client_socket.h" +#include "base/time.h" +#include "base/stl_util-inl.h" +#include "net/base/client_socket_factory.h" #include "net/base/client_socket_handle.h" +#include "net/base/dns_resolution_observer.h" #include "net/base/net_errors.h" +#include "net/base/tcp_client_socket.h" using base::TimeDelta; @@ -28,8 +34,123 @@ const int kIdleTimeout = 300; // 5 minutes. namespace net { -ClientSocketPool::ClientSocketPool(int max_sockets_per_group) - : idle_socket_count_(0), +ClientSocketPool::ConnectingSocket::ConnectingSocket( + const std::string& group_name, + const ClientSocketHandle* handle, + ClientSocketFactory* client_socket_factory, + ClientSocketPool* pool) + : group_name_(group_name), + handle_(handle), + client_socket_factory_(client_socket_factory), + ALLOW_THIS_IN_INITIALIZER_LIST( + callback_(this, + &ClientSocketPool::ConnectingSocket::OnIOComplete)), + pool_(pool) {} + +ClientSocketPool::ConnectingSocket::~ConnectingSocket() {} + +int ClientSocketPool::ConnectingSocket::Connect( + const std::string& host, + int port, + CompletionCallback* callback) { + DidStartDnsResolution(host, this); + int rv = resolver_.Resolve(host, port, &addresses_, &callback_); + if (rv == OK) { + // TODO(willchan): This code is broken. It should be fixed, but the code + // path is impossible in the current implementation since the host resolver + // always dumps the request to a worker pool, so it cannot complete + // synchronously. + connect_start_time_ = base::Time::Now(); + rv = socket_->Connect(&callback_); + } + return rv; +} + +ClientSocket* ClientSocketPool::ConnectingSocket::ReleaseSocket() { + return socket_.release(); +} + +void ClientSocketPool::ConnectingSocket::OnIOComplete(int result) { + DCHECK_NE(result, ERR_IO_PENDING); + + 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; + } + + 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. + 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); + } + delete this; + return; + } + + if (result == OK) { + if (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; + } else { + 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); + } + } + + request.callback->Run(result); + delete this; +} + +ClientSocketPool::ClientSocketPool(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) { } @@ -54,10 +175,15 @@ void ClientSocketPool::InsertRequestIntoQueue(const Request& r, pending_requests->insert(it, r); } -int ClientSocketPool::RequestSocket(ClientSocketHandle* handle, +int ClientSocketPool::RequestSocket(const std::string& group_name, + const std::string& host, + int port, int priority, + ClientSocketHandle* handle, CompletionCallback* callback) { - Group& group = group_map_[handle->group_name_]; + 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_) { @@ -66,6 +192,9 @@ int ClientSocketPool::RequestSocket(ClientSocketHandle* 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; } @@ -73,49 +202,91 @@ int ClientSocketPool::RequestSocket(ClientSocketHandle* handle, // OK, we are going to activate one. group.active_socket_count++; - // Use idle sockets in LIFO order because they're more likely to be - // still reusable. while (!group.idle_sockets.empty()) { IdleSocket idle_socket = group.idle_sockets.back(); group.idle_sockets.pop_back(); DecrementIdleCount(); - if ((*idle_socket.ptr)->IsConnectedAndIdle()) { + if (idle_socket.socket->IsConnectedAndIdle()) { // We found one we can reuse! - handle->socket_ = idle_socket.ptr; + handle->set_socket(idle_socket.socket); + handle->set_is_reused(true); return OK; } - delete idle_socket.ptr; + delete idle_socket.socket; } - handle->socket_ = new ClientSocketPtr(); - return OK; + // We couldn't find a socket to reuse, so allocate and connect a new one. + scoped_ptr<ConnectingSocket> connecting_socket( + new ConnectingSocket(group_name, handle, client_socket_factory_, this)); + int rv = connecting_socket->Connect(host, port, callback); + if (rv == OK) { + handle->set_socket(connecting_socket->ReleaseSocket()); + handle->set_is_reused(false); + } else if (rv == ERR_IO_PENDING) { + // The ConnectingSocket will delete itself. + connecting_socket.release(); + 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; + } 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()); + group_map_.erase(group_name); + } + } + + return rv; } -void ClientSocketPool::CancelRequest(ClientSocketHandle* handle) { - Group& group = group_map_[handle->group_name_]; +void ClientSocketPool::CancelRequest(const std::string& group_name, + const ClientSocketHandle* handle) { + DCHECK(ContainsKey(group_map_, group_name)); - // In order for us to be canceling a pending request, we must have active - // sockets equaling the limit. NOTE: The correctness of the code doesn't - // require this assertion. - DCHECK(group.active_socket_count == max_sockets_per_group_); + Group& group = group_map_[group_name]; // Search pending_requests for matching handle. - std::deque<Request>::iterator it = group.pending_requests.begin(); + RequestQueue::iterator it = group.pending_requests.begin(); for (; it != group.pending_requests.end(); ++it) { if (it->handle == handle) { group.pending_requests.erase(it); - break; + 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 ClientSocketPool::ReleaseSocket(ClientSocketHandle* handle) { +void ClientSocketPool::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, &ClientSocketPool::DoReleaseSocket, handle->group_name_, - handle->socket_)); + this, &ClientSocketPool::DoReleaseSocket, group_name, socket)); } void ClientSocketPool::CloseIdleSockets() { @@ -130,10 +301,42 @@ int ClientSocketPool::IdleSocketCountInGroup( return i->second.idle_sockets.size(); } +LoadState ClientSocketPool::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 ClientSocketPool::IdleSocket::ShouldCleanup(base::TimeTicks now) const { bool timed_out = (now - start_time) >= base::TimeDelta::FromSeconds(kIdleTimeout); - return timed_out || !(*ptr)->IsConnectedAndIdle(); + return timed_out || !socket->IsConnectedAndIdle(); } void ClientSocketPool::CleanupIdleSockets(bool force) { @@ -151,7 +354,7 @@ void ClientSocketPool::CleanupIdleSockets(bool force) { std::deque<IdleSocket>::iterator j = group.idle_sockets.begin(); while (j != group.idle_sockets.end()) { if (force || j->ShouldCleanup(now)) { - delete j->ptr; + delete j->socket; j = group.idle_sockets.erase(j); DecrementIdleCount(); } else { @@ -162,6 +365,7 @@ void ClientSocketPool::CleanupIdleSockets(bool force) { // 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; @@ -181,40 +385,43 @@ void ClientSocketPool::DecrementIdleCount() { } void ClientSocketPool::DoReleaseSocket(const std::string& group_name, - ClientSocketPtr* ptr) { + ClientSocket* socket) { GroupMap::iterator i = group_map_.find(group_name); DCHECK(i != group_map_.end()); Group& group = i->second; - DCHECK(group.active_socket_count > 0); + DCHECK_GT(group.active_socket_count, 0); group.active_socket_count--; - bool can_reuse = ptr->get() && (*ptr)->IsConnectedAndIdle(); + const bool can_reuse = socket->IsConnectedAndIdle(); if (can_reuse) { IdleSocket idle_socket; - idle_socket.ptr = ptr; + idle_socket.socket = socket; idle_socket.start_time = base::TimeTicks::Now(); group.idle_sockets.push_back(idle_socket); IncrementIdleCount(); } else { - delete ptr; + 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(r.handle, r.priority, NULL); - DCHECK(rv == OK); - r.callback->Run(rv); + + 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); } } diff --git a/net/base/client_socket_pool.h b/net/base/client_socket_pool.h index 6c92f37..2e93a00 100644 --- a/net/base/client_socket_pool.h +++ b/net/base/client_socket_pool.h @@ -12,58 +12,66 @@ #include "base/ref_counted.h" #include "base/scoped_ptr.h" #include "base/timer.h" +#include "net/base/address_list.h" #include "net/base/completion_callback.h" +#include "net/base/host_resolver.h" +#include "net/base/load_states.h" namespace net { class ClientSocket; +class ClientSocketFactory; class ClientSocketHandle; // A ClientSocketPool is used to restrict the number of sockets open at a time. // It also maintains a list of idle persistent sockets. // -// The ClientSocketPool allocates scoped_ptr<ClientSocket> objects, but it is -// not responsible for allocating the associated ClientSocket objects. The -// consumer must do so if it gets a scoped_ptr<ClientSocket> with a null value. -// class ClientSocketPool : public base::RefCounted<ClientSocketPool> { public: - explicit ClientSocketPool(int max_sockets_per_group); - - // Called to request a socket for the given handle. There are three possible - // results: 1) the handle will be initialized with a socket to reuse, 2) the - // handle will be initialized without a socket such that the consumer needs - // to supply a socket, or 3) the handle will be added to a wait list until a - // socket is available to reuse or the opportunity to create a new socket - // arises. The completion callback is notified in the 3rd case. |priority| - // will determine the placement into the wait list. + ClientSocketPool(int max_sockets_per_group, + ClientSocketFactory* client_socket_factory); + + // Requests a connected socket for a group_name. + // + // There are four possible results from calling this function: + // 1) RequestSocket returns OK and initializes |handle| with a reused socket. + // 2) RequestSocket returns OK with a newly connected socket. + // 3) RequestSocket returns ERR_IO_PENDING. The handle will be added to a + // wait list until a socket is available to reuse or a new socket finishes + // connecting. |priority| will determine the placement into the wait list. + // 4) An error occurred early on, so RequestSocket returns an error code. // // If this function returns OK, then |handle| is initialized upon return. // The |handle|'s is_initialized method will return true in this case. If a - // ClientSocket was reused, then |handle|'s socket member will be non-NULL. - // Otherwise, the consumer will need to supply |handle| with a socket by - // allocating a new ClientSocket object and calling the |handle|'s set_socket - // method. + // ClientSocket was reused, then ClientSocketPool will call + // |handle|->set_reused(true). In either case, the socket will have been + // allocated and will be connected. A client might want to know whether or + // not the socket is reused in order to know whether or not he needs to + // perform SSL connection or tunnel setup or to request a new socket if he + // encounters an error with the reused socket. // - // If ERR_IO_PENDING is returned, then the completion callback will be called - // when |handle| has been initialized. + // If ERR_IO_PENDING is returned, then the callback will be used to notify the + // client of completion. // - int RequestSocket(ClientSocketHandle* handle, + int RequestSocket(const std::string& group_name, + const std::string& host, + int port, int priority, + ClientSocketHandle* handle, CompletionCallback* callback); // Called to cancel a RequestSocket call that returned ERR_IO_PENDING. The // same handle parameter must be passed to this method as was passed to the // RequestSocket call being cancelled. The associated CompletionCallback is // not run. - void CancelRequest(ClientSocketHandle* handle); + void CancelRequest(const std::string& group_name, + const ClientSocketHandle* handle); - // Called to release the socket member of an initialized ClientSocketHandle - // once the socket is no longer needed. If the socket member is non-null and - // still has an established connection, then it will be added to the idle set - // of sockets to be used to satisfy future RequestSocket calls. Otherwise, - // the ClientSocket is destroyed. - void ReleaseSocket(ClientSocketHandle* handle); + // Called to release a socket once the socket is no longer needed. If the + // socket still has an established connection, then it will be added to the + // set of idle sockets to be used to satisfy future RequestSocket calls. + // Otherwise, the ClientSocket is destroyed. + void ReleaseSocket(const std::string& group_name, ClientSocket* socket); // Called to close any idle connections held by the connection manager. void CloseIdleSockets(); @@ -76,22 +84,27 @@ class ClientSocketPool : public base::RefCounted<ClientSocketPool> { // The total number of idle sockets in a connection group. int IdleSocketCountInGroup(const std::string& group_name) const; + // Determine the LoadState of a connecting ClientSocketHandle. + LoadState GetLoadState(const std::string& group_name, + const ClientSocketHandle* handle) const; + private: friend class base::RefCounted<ClientSocketPool>; - typedef scoped_ptr<ClientSocket> ClientSocketPtr; - // 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 { - ClientSocketPtr* ptr; + ClientSocket* socket; base::TimeTicks start_time; // An idle socket should be removed if it can't be reused, or has been idle @@ -105,6 +118,7 @@ class ClientSocketPool : public base::RefCounted<ClientSocketPool> { }; 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. @@ -112,11 +126,57 @@ class ClientSocketPool : public base::RefCounted<ClientSocketPool> { 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 { + public: + enum State { + STATE_RESOLVE_HOST, + STATE_CONNECT + }; + + ConnectingSocket(const std::string& group_name, + const ClientSocketHandle* handle, + ClientSocketFactory* client_socket_factory, + ClientSocketPool* pool); + ~ConnectingSocket(); + + // Begins the host resolution and the TCP connect. Returns OK on success, + // in which case |callback| is not called. On pending IO, Connect returns + // ERR_IO_PENDING and runs |callback| on completion. + int Connect(const std::string& host, + int port, + CompletionCallback* callback); + + // If Connect() returns OK, ClientSocketPool may invoke this method to get + // the ConnectingSocket to release |socket_| to be set into the + // ClientSocketHandle immediately. + ClientSocket* ReleaseSocket(); + + private: + void OnIOComplete(int result); + + const std::string group_name_; + const ClientSocketHandle* const handle_; + ClientSocketFactory* const client_socket_factory_; + CompletionCallbackImpl<ConnectingSocket> callback_; + scoped_ptr<ClientSocket> socket_; + scoped_refptr<ClientSocketPool> pool_; + HostResolver resolver_; + AddressList addresses_; + + // The time the Connect() method was called (if it got called). + base::Time connect_start_time_; + + DISALLOW_COPY_AND_ASSIGN(ConnectingSocket); + }; + ~ClientSocketPool(); static void InsertRequestIntoQueue(const Request& r, @@ -131,7 +191,7 @@ class ClientSocketPool : public base::RefCounted<ClientSocketPool> { void DecrementIdleCount(); // Called via PostTask by ReleaseSocket. - void DoReleaseSocket(const std::string& group_name, ClientSocketPtr* ptr); + 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. @@ -139,6 +199,8 @@ class ClientSocketPool : public base::RefCounted<ClientSocketPool> { CleanupIdleSockets(false); } + ClientSocketFactory* const client_socket_factory_; + GroupMap group_map_; // Timer used to periodically prune idle sockets that timed out or can't be @@ -149,7 +211,7 @@ class ClientSocketPool : public base::RefCounted<ClientSocketPool> { int idle_socket_count_; // The maximum number of sockets kept per group. - int max_sockets_per_group_; + const int max_sockets_per_group_; DISALLOW_COPY_AND_ASSIGN(ClientSocketPool); }; diff --git a/net/base/client_socket_pool_unittest.cc b/net/base/client_socket_pool_unittest.cc index a889d9a..73fc0e3 100644 --- a/net/base/client_socket_pool_unittest.cc +++ b/net/base/client_socket_pool_unittest.cc @@ -4,11 +4,16 @@ #include "base/message_loop.h" #include "net/base/client_socket.h" +#include "net/base/client_socket_factory.h" #include "net/base/client_socket_handle.h" #include "net/base/client_socket_pool.h" +#include "net/base/host_resolver_unittest.h" #include "net/base/net_errors.h" +#include "net/base/test_completion_callback.h" #include "testing/gtest/include/gtest/gtest.h" +namespace net { + namespace { const int kMaxSocketsPerGroup = 6; @@ -21,16 +26,16 @@ const int kPriorities[10] = { 1, 7, 9, 5, 6, 2, 8, 3, 4, 1 }; // available sockets in the socket group. const int kNumPendingRequests = arraysize(kPriorities); -class MockClientSocket : public net::ClientSocket { +const int kNumRequests = kMaxSocketsPerGroup + kNumPendingRequests; + +class MockClientSocket : public ClientSocket { public: - MockClientSocket() : connected_(false) { - allocation_count++; - } + MockClientSocket() : connected_(false) {} // ClientSocket methods: - virtual int Connect(net::CompletionCallback* callback) { + virtual int Connect(CompletionCallback* callback) { connected_ = true; - return net::OK; + return OK; } virtual void Disconnect() { connected_ = false; @@ -43,51 +48,115 @@ class MockClientSocket : public net::ClientSocket { } // Socket methods: - virtual int Read(net::IOBuffer* buf, int buf_len, - net::CompletionCallback* callback) { - return net::ERR_FAILED; + virtual int Read(IOBuffer* buf, int buf_len, + CompletionCallback* callback) { + return ERR_FAILED; } - virtual int Write(net::IOBuffer* buf, int buf_len, - net::CompletionCallback* callback) { - return net::ERR_FAILED; + virtual int Write(IOBuffer* buf, int buf_len, + CompletionCallback* callback) { + return ERR_FAILED; } - static int allocation_count; - private: bool connected_; }; -int MockClientSocket::allocation_count = 0; +class MockFailingClientSocket : public ClientSocket { + public: + MockFailingClientSocket() {} + + // ClientSocket methods: + virtual int Connect(CompletionCallback* callback) { + return ERR_CONNECTION_FAILED; + } + + virtual void Disconnect() {} + + virtual bool IsConnected() const { + return false; + } + virtual bool IsConnectedAndIdle() const { + return false; + } + + // Socket methods: + virtual int Read(IOBuffer* buf, int buf_len, + CompletionCallback* callback) { + return ERR_FAILED; + } + + virtual int Write(IOBuffer* buf, int buf_len, + CompletionCallback* callback) { + return ERR_FAILED; + } +}; + +class MockClientSocketFactory : public ClientSocketFactory { + public: + enum ClientSocketType { + MOCK_CLIENT_SOCKET, + MOCK_FAILING_CLIENT_SOCKET, + }; + + MockClientSocketFactory() + : allocation_count_(0), client_socket_type_(MOCK_CLIENT_SOCKET) {} + + virtual ClientSocket* CreateTCPClientSocket(const AddressList& addresses) { + allocation_count_++; + switch (client_socket_type_) { + case MOCK_CLIENT_SOCKET: + return new MockClientSocket(); + case MOCK_FAILING_CLIENT_SOCKET: + return new MockFailingClientSocket(); + default: + NOTREACHED(); + return new MockClientSocket(); + } + } + + virtual SSLClientSocket* CreateSSLClientSocket( + ClientSocket* transport_socket, + const std::string& hostname, + const SSLConfig& ssl_config) { + NOTIMPLEMENTED(); + return NULL; + } + + int allocation_count() const { return allocation_count_; } + + void set_client_socket_type(ClientSocketType type) { + client_socket_type_ = type; + } + + private: + int allocation_count_; + ClientSocketType client_socket_type_; +}; class TestSocketRequest : public CallbackRunner< Tuple1<int> > { public: TestSocketRequest( - net::ClientSocketPool* pool, + ClientSocketPool* pool, std::vector<TestSocketRequest*>* request_order) : handle(pool), request_order_(request_order) {} - net::ClientSocketHandle handle; + ClientSocketHandle handle; - void EnsureSocket() { - DCHECK(handle.is_initialized()); - request_order_->push_back(this); - if (!handle.socket()) { - handle.set_socket(new MockClientSocket()); - handle.socket()->Connect(NULL); - } + int WaitForResult() { + return callback_.WaitForResult(); } virtual void RunWithParams(const Tuple1<int>& params) { - DCHECK(params.a == net::OK); + callback_.RunWithParams(params); completion_count++; - EnsureSocket(); + request_order_->push_back(this); } static int completion_count; private: std::vector<TestSocketRequest*>* request_order_; + TestCompletionCallback callback_; }; int TestSocketRequest::completion_count = 0; @@ -95,68 +164,76 @@ int TestSocketRequest::completion_count = 0; class ClientSocketPoolTest : public testing::Test { protected: ClientSocketPoolTest() - : pool_(new net::ClientSocketPool(kMaxSocketsPerGroup)) {} + : pool_(new ClientSocketPool(kMaxSocketsPerGroup, + &client_socket_factory_)) {} virtual void SetUp() { - MockClientSocket::allocation_count = 0; TestSocketRequest::completion_count = 0; } - scoped_refptr<net::ClientSocketPool> pool_; + MockClientSocketFactory client_socket_factory_; + scoped_refptr<ClientSocketPool> pool_; std::vector<TestSocketRequest*> request_order_; }; TEST_F(ClientSocketPoolTest, Basic) { - TestSocketRequest r(pool_.get(), &request_order_); - int rv; + TestCompletionCallback callback; + ClientSocketHandle handle(pool_.get()); + int rv = handle.Init("a", "www.google.com", 80, 0, &callback); + EXPECT_EQ(ERR_IO_PENDING, rv); + EXPECT_FALSE(handle.is_initialized()); + EXPECT_FALSE(handle.socket()); - rv = r.handle.Init("a", 0, &r); - EXPECT_EQ(net::OK, rv); - EXPECT_TRUE(r.handle.is_initialized()); + EXPECT_EQ(OK, callback.WaitForResult()); + EXPECT_TRUE(handle.is_initialized()); + EXPECT_TRUE(handle.socket()); - r.handle.Reset(); + handle.Reset(); // The handle's Reset method may have posted a task. MessageLoop::current()->RunAllPending(); } -TEST_F(ClientSocketPoolTest, WithIdleConnection) { - TestSocketRequest r(pool_.get(), &request_order_); - int rv; - - rv = r.handle.Init("a", 0, &r); - EXPECT_EQ(net::OK, rv); - EXPECT_TRUE(r.handle.is_initialized()); - - // Create a socket. - r.EnsureSocket(); - - // Release the socket. It should find its way into the idle list. We're - // testing that this does not trigger a crash. - r.handle.Reset(); +TEST_F(ClientSocketPoolTest, InitHostResolutionFailure) { + RuleBasedHostMapper* host_mapper = new RuleBasedHostMapper; + host_mapper->AddSimulatedFailure("unresolvable.host.name"); + ScopedHostMapper scoped_host_mapper(host_mapper); + TestSocketRequest req(pool_.get(), &request_order_); + EXPECT_EQ(ERR_IO_PENDING, + req.handle.Init("a", "unresolvable.host.name", 80, 5, &req)); + EXPECT_EQ(ERR_NAME_NOT_RESOLVED, req.WaitForResult()); +} - // The handle's Reset method may have posted a task. - MessageLoop::current()->RunAllPending(); +TEST_F(ClientSocketPoolTest, InitConnectionFailure) { + client_socket_factory_.set_client_socket_type( + MockClientSocketFactory::MOCK_FAILING_CLIENT_SOCKET); + TestSocketRequest req(pool_.get(), &request_order_); + EXPECT_EQ(ERR_IO_PENDING, + req.handle.Init("a", "unresolvable.host.name", 80, 5, &req)); + EXPECT_EQ(ERR_CONNECTION_FAILED, req.WaitForResult()); } TEST_F(ClientSocketPoolTest, PendingRequests) { int rv; - scoped_ptr<TestSocketRequest> reqs[kMaxSocketsPerGroup + kNumPendingRequests]; + scoped_ptr<TestSocketRequest> reqs[kNumRequests]; for (size_t i = 0; i < arraysize(reqs); ++i) reqs[i].reset(new TestSocketRequest(pool_.get(), &request_order_)); // Create connections or queue up requests. for (int i = 0; i < kMaxSocketsPerGroup; ++i) { - rv = reqs[i]->handle.Init("a", 5, reqs[i].get()); - EXPECT_EQ(net::OK, rv); - reqs[i]->EnsureSocket(); + EXPECT_EQ( + ERR_IO_PENDING, + reqs[i]->handle.Init("a", "www.google.com", 80, 5, reqs[i].get())); + EXPECT_EQ(OK, reqs[i]->WaitForResult()); } + for (int i = 0; i < kNumPendingRequests; ++i) { rv = reqs[kMaxSocketsPerGroup + i]->handle.Init( - "a", kPriorities[i], reqs[kMaxSocketsPerGroup + i].get()); - EXPECT_EQ(net::ERR_IO_PENDING, rv); + "a", "www.google.com", 80, kPriorities[i], + reqs[kMaxSocketsPerGroup + i].get()); + EXPECT_EQ(ERR_IO_PENDING, rv); } // Release any connections until we have no connections. @@ -172,8 +249,8 @@ TEST_F(ClientSocketPoolTest, PendingRequests) { } } while (released_one); - EXPECT_EQ(kMaxSocketsPerGroup, MockClientSocket::allocation_count); - EXPECT_EQ(kNumPendingRequests, TestSocketRequest::completion_count); + EXPECT_EQ(kMaxSocketsPerGroup, client_socket_factory_.allocation_count()); + EXPECT_EQ(kNumRequests, TestSocketRequest::completion_count); for (int i = 0; i < kMaxSocketsPerGroup; ++i) { EXPECT_EQ(request_order_[i], reqs[i].get()) << @@ -194,58 +271,77 @@ TEST_F(ClientSocketPoolTest, PendingRequests) { } TEST_F(ClientSocketPoolTest, PendingRequests_NoKeepAlive) { - int rv; - - scoped_ptr<TestSocketRequest> reqs[kMaxSocketsPerGroup + kNumPendingRequests]; + scoped_ptr<TestSocketRequest> reqs[kNumRequests]; for (size_t i = 0; i < arraysize(reqs); ++i) reqs[i].reset(new TestSocketRequest(pool_.get(), &request_order_)); // Create connections or queue up requests. - for (size_t i = 0; i < arraysize(reqs); ++i) { - rv = reqs[i]->handle.Init("a", 0, reqs[i].get()); - if (rv != net::ERR_IO_PENDING) { - EXPECT_EQ(net::OK, rv); - reqs[i]->EnsureSocket(); - } + for (int i = 0; i < kMaxSocketsPerGroup; ++i) { + EXPECT_EQ( + ERR_IO_PENDING, + reqs[i]->handle.Init("a", "www.google.com", 80, 0, reqs[i].get())); + EXPECT_EQ(OK, reqs[i]->WaitForResult()); + } + + for (int i = 0; i < kNumPendingRequests; ++i) { + EXPECT_EQ(ERR_IO_PENDING, reqs[kMaxSocketsPerGroup + i]->handle.Init( + "a", "www.google.com", 80, 0, reqs[kMaxSocketsPerGroup + i].get())); } // Release any connections until we have no connections. - bool released_one; - do { - released_one = false; + + while (TestSocketRequest::completion_count < kNumRequests) { + int num_released = 0; for (size_t i = 0; i < arraysize(reqs); ++i) { if (reqs[i]->handle.is_initialized()) { reqs[i]->handle.socket()->Disconnect(); reqs[i]->handle.Reset(); - MessageLoop::current()->RunAllPending(); - released_one = true; + num_released++; } } - } while (released_one); + int curr_num_completed = TestSocketRequest::completion_count; + for (int i = 0; + (i < num_released) && (i + curr_num_completed < kNumRequests); ++i) { + EXPECT_EQ(OK, reqs[i + curr_num_completed]->WaitForResult()); + } + } - EXPECT_EQ(kMaxSocketsPerGroup + kNumPendingRequests, - MockClientSocket::allocation_count); - EXPECT_EQ(kNumPendingRequests, TestSocketRequest::completion_count); + EXPECT_EQ(kNumRequests, client_socket_factory_.allocation_count()); + EXPECT_EQ(kNumRequests, TestSocketRequest::completion_count); } -TEST_F(ClientSocketPoolTest, CancelRequest) { - int rv; +// This test will start up a RequestSocket() and then immediately Cancel() it. +// The pending host resolution will eventually complete, and destroy the +// ClientSocketPool which will crash if the group was not cleared properly. +TEST_F(ClientSocketPoolTest, CancelRequestClearGroup) { + TestSocketRequest req(pool_.get(), &request_order_); + EXPECT_EQ(ERR_IO_PENDING, + req.handle.Init("a", "www.google.com", 80, 5, &req)); + req.handle.Reset(); + // There is a race condition here. If the worker pool doesn't post the task + // before we get here, then this might not run ConnectingSocket::IOComplete + // and therefore leak the canceled ConnectingSocket. + MessageLoop::current()->RunAllPending(); +} - scoped_ptr<TestSocketRequest> reqs[kMaxSocketsPerGroup + kNumPendingRequests]; +TEST_F(ClientSocketPoolTest, CancelRequest) { + scoped_ptr<TestSocketRequest> reqs[kNumRequests]; for (size_t i = 0; i < arraysize(reqs); ++i) reqs[i].reset(new TestSocketRequest(pool_.get(), &request_order_)); // Create connections or queue up requests. for (int i = 0; i < kMaxSocketsPerGroup; ++i) { - rv = reqs[i]->handle.Init("a", 5, reqs[i].get()); - EXPECT_EQ(net::OK, rv); - reqs[i]->EnsureSocket(); + EXPECT_EQ( + ERR_IO_PENDING, + reqs[i]->handle.Init("a", "www.google.com", 80, 5, reqs[i].get())); + EXPECT_EQ(OK, reqs[i]->WaitForResult()); } + for (int i = 0; i < kNumPendingRequests; ++i) { - rv = reqs[kMaxSocketsPerGroup + i]->handle.Init( - "a", kPriorities[i], reqs[kMaxSocketsPerGroup + i].get()); - EXPECT_EQ(net::ERR_IO_PENDING, rv); + EXPECT_EQ(ERR_IO_PENDING, reqs[kMaxSocketsPerGroup + i]->handle.Init( + "a", "www.google.com", 80, kPriorities[i], + reqs[kMaxSocketsPerGroup + i].get())); } // Cancel a request. @@ -266,8 +362,8 @@ TEST_F(ClientSocketPoolTest, CancelRequest) { } } while (released_one); - EXPECT_EQ(kMaxSocketsPerGroup, MockClientSocket::allocation_count); - EXPECT_EQ(kNumPendingRequests - 1, TestSocketRequest::completion_count); + EXPECT_EQ(kMaxSocketsPerGroup, client_socket_factory_.allocation_count()); + EXPECT_EQ(kNumRequests - 1, TestSocketRequest::completion_count); for (int i = 0; i < kMaxSocketsPerGroup; ++i) { EXPECT_EQ(request_order_[i], reqs[i].get()) << "Request " << i << " was not in order."; @@ -290,3 +386,5 @@ TEST_F(ClientSocketPoolTest, CancelRequest) { } } // namespace + +} // namespace net diff --git a/net/base/test_completion_callback.h b/net/base/test_completion_callback.h index 3494646..a5d1145 100644 --- a/net/base/test_completion_callback.h +++ b/net/base/test_completion_callback.h @@ -38,7 +38,6 @@ class TestCompletionCallback : public CallbackRunner< Tuple1<int> > { return result_; } - private: virtual void RunWithParams(const Tuple1<int>& params) { result_ = params.a; have_result_ = true; @@ -46,6 +45,7 @@ class TestCompletionCallback : public CallbackRunner< Tuple1<int> > { MessageLoop::current()->Quit(); } + private: int result_; bool have_result_; bool waiting_for_result_; |