|  | // Copyright 2014 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/websocket_transport_client_socket_pool.h" | 
|  |  | 
|  | #include <algorithm> | 
|  | #include <utility> | 
|  |  | 
|  | #include "base/compiler_specific.h" | 
|  | #include "base/location.h" | 
|  | #include "base/logging.h" | 
|  | #include "base/numerics/safe_conversions.h" | 
|  | #include "base/single_thread_task_runner.h" | 
|  | #include "base/strings/string_util.h" | 
|  | #include "base/threading/thread_task_runner_handle.h" | 
|  | #include "base/time/time.h" | 
|  | #include "base/trace_event/trace_event.h" | 
|  | #include "base/values.h" | 
|  | #include "net/base/net_errors.h" | 
|  | #include "net/log/net_log.h" | 
|  | #include "net/log/net_log_event_type.h" | 
|  | #include "net/log/net_log_source_type.h" | 
|  | #include "net/socket/client_socket_handle.h" | 
|  | #include "net/socket/client_socket_pool_base.h" | 
|  | #include "net/socket/websocket_endpoint_lock_manager.h" | 
|  | #include "net/socket/websocket_transport_connect_sub_job.h" | 
|  |  | 
|  | namespace net { | 
|  |  | 
|  | WebSocketTransportConnectJob::WebSocketTransportConnectJob( | 
|  | const std::string& group_name, | 
|  | RequestPriority priority, | 
|  | ClientSocketPool::RespectLimits respect_limits, | 
|  | const scoped_refptr<TransportSocketParams>& params, | 
|  | base::TimeDelta timeout_duration, | 
|  | const CompletionCallback& callback, | 
|  | ClientSocketFactory* client_socket_factory, | 
|  | HostResolver* host_resolver, | 
|  | ClientSocketHandle* handle, | 
|  | Delegate* delegate, | 
|  | NetLog* pool_net_log, | 
|  | const BoundNetLog& request_net_log) | 
|  | : ConnectJob( | 
|  | group_name, | 
|  | timeout_duration, | 
|  | priority, | 
|  | respect_limits, | 
|  | delegate, | 
|  | BoundNetLog::Make(pool_net_log, NetLogSourceType::CONNECT_JOB)), | 
|  | params_(params), | 
|  | resolver_(host_resolver), | 
|  | client_socket_factory_(client_socket_factory), | 
|  | next_state_(STATE_NONE), | 
|  | race_result_(TransportConnectJob::RACE_UNKNOWN), | 
|  | handle_(handle), | 
|  | callback_(callback), | 
|  | request_net_log_(request_net_log), | 
|  | had_ipv4_(false), | 
|  | had_ipv6_(false) {} | 
|  |  | 
|  | WebSocketTransportConnectJob::~WebSocketTransportConnectJob() {} | 
|  |  | 
|  | LoadState WebSocketTransportConnectJob::GetLoadState() const { | 
|  | LoadState load_state = LOAD_STATE_RESOLVING_HOST; | 
|  | if (ipv6_job_) | 
|  | load_state = ipv6_job_->GetLoadState(); | 
|  | // This method should return LOAD_STATE_CONNECTING in preference to | 
|  | // LOAD_STATE_WAITING_FOR_AVAILABLE_SOCKET when possible because "waiting for | 
|  | // available socket" implies that nothing is happening. | 
|  | if (ipv4_job_ && load_state != LOAD_STATE_CONNECTING) | 
|  | load_state = ipv4_job_->GetLoadState(); | 
|  | return load_state; | 
|  | } | 
|  |  | 
|  | void WebSocketTransportConnectJob::OnIOComplete(int result) { | 
|  | result = DoLoop(result); | 
|  | if (result != ERR_IO_PENDING) | 
|  | NotifyDelegateOfCompletion(result);  // Deletes |this| | 
|  | } | 
|  |  | 
|  | int WebSocketTransportConnectJob::DoLoop(int result) { | 
|  | DCHECK_NE(next_state_, STATE_NONE); | 
|  |  | 
|  | int rv = result; | 
|  | do { | 
|  | State state = next_state_; | 
|  | next_state_ = STATE_NONE; | 
|  | switch (state) { | 
|  | case STATE_RESOLVE_HOST: | 
|  | DCHECK_EQ(OK, rv); | 
|  | rv = DoResolveHost(); | 
|  | break; | 
|  | case STATE_RESOLVE_HOST_COMPLETE: | 
|  | rv = DoResolveHostComplete(rv); | 
|  | break; | 
|  | case STATE_TRANSPORT_CONNECT: | 
|  | DCHECK_EQ(OK, rv); | 
|  | rv = DoTransportConnect(); | 
|  | break; | 
|  | case STATE_TRANSPORT_CONNECT_COMPLETE: | 
|  | rv = DoTransportConnectComplete(rv); | 
|  | break; | 
|  | default: | 
|  | NOTREACHED(); | 
|  | rv = ERR_FAILED; | 
|  | break; | 
|  | } | 
|  | } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE); | 
|  |  | 
|  | return rv; | 
|  | } | 
|  |  | 
|  | int WebSocketTransportConnectJob::DoResolveHost() { | 
|  | next_state_ = STATE_RESOLVE_HOST_COMPLETE; | 
|  | connect_timing_.dns_start = base::TimeTicks::Now(); | 
|  |  | 
|  | return resolver_->Resolve( | 
|  | params_->destination(), priority(), &addresses_, | 
|  | base::Bind(&WebSocketTransportConnectJob::OnIOComplete, | 
|  | base::Unretained(this)), | 
|  | &request_, net_log()); | 
|  | } | 
|  |  | 
|  | int WebSocketTransportConnectJob::DoResolveHostComplete(int result) { | 
|  | TRACE_EVENT0("net", "WebSocketTransportConnectJob::DoResolveHostComplete"); | 
|  | connect_timing_.dns_end = base::TimeTicks::Now(); | 
|  | // Overwrite connection start time, since for connections that do not go | 
|  | // through proxies, |connect_start| should not include dns lookup time. | 
|  | connect_timing_.connect_start = connect_timing_.dns_end; | 
|  |  | 
|  | if (result != OK) | 
|  | return result; | 
|  |  | 
|  | // Invoke callback, and abort if it fails. | 
|  | if (!params_->host_resolution_callback().is_null()) { | 
|  | result = params_->host_resolution_callback().Run(addresses_, net_log()); | 
|  | if (result != OK) | 
|  | return result; | 
|  | } | 
|  |  | 
|  | next_state_ = STATE_TRANSPORT_CONNECT; | 
|  | return result; | 
|  | } | 
|  |  | 
|  | int WebSocketTransportConnectJob::DoTransportConnect() { | 
|  | AddressList ipv4_addresses; | 
|  | AddressList ipv6_addresses; | 
|  | int result = ERR_UNEXPECTED; | 
|  | next_state_ = STATE_TRANSPORT_CONNECT_COMPLETE; | 
|  |  | 
|  | for (AddressList::const_iterator it = addresses_.begin(); | 
|  | it != addresses_.end(); ++it) { | 
|  | switch (it->GetFamily()) { | 
|  | case ADDRESS_FAMILY_IPV4: | 
|  | ipv4_addresses.push_back(*it); | 
|  | break; | 
|  |  | 
|  | case ADDRESS_FAMILY_IPV6: | 
|  | ipv6_addresses.push_back(*it); | 
|  | break; | 
|  |  | 
|  | default: | 
|  | DVLOG(1) << "Unexpected ADDRESS_FAMILY: " << it->GetFamily(); | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (!ipv4_addresses.empty()) { | 
|  | had_ipv4_ = true; | 
|  | ipv4_job_.reset(new WebSocketTransportConnectSubJob( | 
|  | ipv4_addresses, this, SUB_JOB_IPV4)); | 
|  | } | 
|  |  | 
|  | if (!ipv6_addresses.empty()) { | 
|  | had_ipv6_ = true; | 
|  | ipv6_job_.reset(new WebSocketTransportConnectSubJob( | 
|  | ipv6_addresses, this, SUB_JOB_IPV6)); | 
|  | result = ipv6_job_->Start(); | 
|  | switch (result) { | 
|  | case OK: | 
|  | SetSocket(ipv6_job_->PassSocket()); | 
|  | race_result_ = had_ipv4_ ? TransportConnectJob::RACE_IPV6_WINS | 
|  | : TransportConnectJob::RACE_IPV6_SOLO; | 
|  | return result; | 
|  |  | 
|  | case ERR_IO_PENDING: | 
|  | if (ipv4_job_) { | 
|  | // This use of base::Unretained is safe because |fallback_timer_| is | 
|  | // owned by this object. | 
|  | fallback_timer_.Start( | 
|  | FROM_HERE, base::TimeDelta::FromMilliseconds( | 
|  | TransportConnectJob::kIPv6FallbackTimerInMs), | 
|  | base::Bind(&WebSocketTransportConnectJob::StartIPv4JobAsync, | 
|  | base::Unretained(this))); | 
|  | } | 
|  | return result; | 
|  |  | 
|  | default: | 
|  | ipv6_job_.reset(); | 
|  | } | 
|  | } | 
|  |  | 
|  | DCHECK(!ipv6_job_); | 
|  | if (ipv4_job_) { | 
|  | result = ipv4_job_->Start(); | 
|  | if (result == OK) { | 
|  | SetSocket(ipv4_job_->PassSocket()); | 
|  | race_result_ = had_ipv6_ ? TransportConnectJob::RACE_IPV4_WINS | 
|  | : TransportConnectJob::RACE_IPV4_SOLO; | 
|  | } | 
|  | } | 
|  |  | 
|  | return result; | 
|  | } | 
|  |  | 
|  | int WebSocketTransportConnectJob::DoTransportConnectComplete(int result) { | 
|  | if (result == OK) | 
|  | TransportConnectJob::HistogramDuration(connect_timing_, race_result_); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | void WebSocketTransportConnectJob::OnSubJobComplete( | 
|  | int result, | 
|  | WebSocketTransportConnectSubJob* job) { | 
|  | if (result == OK) { | 
|  | switch (job->type()) { | 
|  | case SUB_JOB_IPV4: | 
|  | race_result_ = had_ipv6_ ? TransportConnectJob::RACE_IPV4_WINS | 
|  | : TransportConnectJob::RACE_IPV4_SOLO; | 
|  | break; | 
|  |  | 
|  | case SUB_JOB_IPV6: | 
|  | race_result_ = had_ipv4_ ? TransportConnectJob::RACE_IPV6_WINS | 
|  | : TransportConnectJob::RACE_IPV6_SOLO; | 
|  | break; | 
|  | } | 
|  | SetSocket(job->PassSocket()); | 
|  |  | 
|  | // Make sure all connections are cancelled even if this object fails to be | 
|  | // deleted. | 
|  | ipv4_job_.reset(); | 
|  | ipv6_job_.reset(); | 
|  | } else { | 
|  | switch (job->type()) { | 
|  | case SUB_JOB_IPV4: | 
|  | ipv4_job_.reset(); | 
|  | break; | 
|  |  | 
|  | case SUB_JOB_IPV6: | 
|  | ipv6_job_.reset(); | 
|  | if (ipv4_job_ && !ipv4_job_->started()) { | 
|  | fallback_timer_.Stop(); | 
|  | result = ipv4_job_->Start(); | 
|  | if (result != ERR_IO_PENDING) { | 
|  | OnSubJobComplete(result, ipv4_job_.get()); | 
|  | return; | 
|  | } | 
|  | } | 
|  | break; | 
|  | } | 
|  | if (ipv4_job_ || ipv6_job_) | 
|  | return; | 
|  | } | 
|  | OnIOComplete(result); | 
|  | } | 
|  |  | 
|  | void WebSocketTransportConnectJob::StartIPv4JobAsync() { | 
|  | DCHECK(ipv4_job_); | 
|  | int result = ipv4_job_->Start(); | 
|  | if (result != ERR_IO_PENDING) | 
|  | OnSubJobComplete(result, ipv4_job_.get()); | 
|  | } | 
|  |  | 
|  | int WebSocketTransportConnectJob::ConnectInternal() { | 
|  | next_state_ = STATE_RESOLVE_HOST; | 
|  | return DoLoop(OK); | 
|  | } | 
|  |  | 
|  | WebSocketTransportClientSocketPool::WebSocketTransportClientSocketPool( | 
|  | int max_sockets, | 
|  | int max_sockets_per_group, | 
|  | HostResolver* host_resolver, | 
|  | ClientSocketFactory* client_socket_factory, | 
|  | NetLog* net_log) | 
|  | : TransportClientSocketPool(max_sockets, | 
|  | max_sockets_per_group, | 
|  | host_resolver, | 
|  | client_socket_factory, | 
|  | NULL, | 
|  | net_log), | 
|  | connect_job_delegate_(this), | 
|  | pool_net_log_(net_log), | 
|  | client_socket_factory_(client_socket_factory), | 
|  | host_resolver_(host_resolver), | 
|  | max_sockets_(max_sockets), | 
|  | handed_out_socket_count_(0), | 
|  | flushing_(false), | 
|  | weak_factory_(this) {} | 
|  |  | 
|  | WebSocketTransportClientSocketPool::~WebSocketTransportClientSocketPool() { | 
|  | // Clean up any pending connect jobs. | 
|  | FlushWithError(ERR_ABORTED); | 
|  | DCHECK(pending_connects_.empty()); | 
|  | DCHECK_EQ(0, handed_out_socket_count_); | 
|  | DCHECK(stalled_request_queue_.empty()); | 
|  | DCHECK(stalled_request_map_.empty()); | 
|  | } | 
|  |  | 
|  | // static | 
|  | void WebSocketTransportClientSocketPool::UnlockEndpoint( | 
|  | ClientSocketHandle* handle) { | 
|  | DCHECK(handle->is_initialized()); | 
|  | DCHECK(handle->socket()); | 
|  | IPEndPoint address; | 
|  | if (handle->socket()->GetPeerAddress(&address) == OK) | 
|  | WebSocketEndpointLockManager::GetInstance()->UnlockEndpoint(address); | 
|  | } | 
|  |  | 
|  | int WebSocketTransportClientSocketPool::RequestSocket( | 
|  | const std::string& group_name, | 
|  | const void* params, | 
|  | RequestPriority priority, | 
|  | RespectLimits respect_limits, | 
|  | ClientSocketHandle* handle, | 
|  | const CompletionCallback& callback, | 
|  | const BoundNetLog& request_net_log) { | 
|  | DCHECK(params); | 
|  | const scoped_refptr<TransportSocketParams>& casted_params = | 
|  | *static_cast<const scoped_refptr<TransportSocketParams>*>(params); | 
|  |  | 
|  | NetLogTcpClientSocketPoolRequestedSocket(request_net_log, &casted_params); | 
|  |  | 
|  | CHECK(!callback.is_null()); | 
|  | CHECK(handle); | 
|  |  | 
|  | request_net_log.BeginEvent(NetLogEventType::SOCKET_POOL); | 
|  |  | 
|  | if (ReachedMaxSocketsLimit() && | 
|  | respect_limits == ClientSocketPool::RespectLimits::ENABLED) { | 
|  | request_net_log.AddEvent(NetLogEventType::SOCKET_POOL_STALLED_MAX_SOCKETS); | 
|  | // TODO(ricea): Use emplace_back when C++11 becomes allowed. | 
|  | StalledRequest request( | 
|  | casted_params, priority, handle, callback, request_net_log); | 
|  | stalled_request_queue_.push_back(request); | 
|  | StalledRequestQueue::iterator iterator = stalled_request_queue_.end(); | 
|  | --iterator; | 
|  | DCHECK_EQ(handle, iterator->handle); | 
|  | // Because StalledRequestQueue is a std::list, its iterators are guaranteed | 
|  | // to remain valid as long as the elements are not removed. As long as | 
|  | // stalled_request_queue_ and stalled_request_map_ are updated in sync, it | 
|  | // is safe to dereference an iterator in stalled_request_map_ to find the | 
|  | // corresponding list element. | 
|  | stalled_request_map_.insert( | 
|  | StalledRequestMap::value_type(handle, iterator)); | 
|  | return ERR_IO_PENDING; | 
|  | } | 
|  |  | 
|  | std::unique_ptr<WebSocketTransportConnectJob> connect_job( | 
|  | new WebSocketTransportConnectJob( | 
|  | group_name, priority, respect_limits, casted_params, | 
|  | ConnectionTimeout(), callback, client_socket_factory_, host_resolver_, | 
|  | handle, &connect_job_delegate_, pool_net_log_, request_net_log)); | 
|  |  | 
|  | int rv = connect_job->Connect(); | 
|  | // Regardless of the outcome of |connect_job|, it will always be bound to | 
|  | // |handle|, since this pool uses early-binding. So the binding is logged | 
|  | // here, without waiting for the result. | 
|  | request_net_log.AddEvent( | 
|  | NetLogEventType::SOCKET_POOL_BOUND_TO_CONNECT_JOB, | 
|  | connect_job->net_log().source().ToEventParametersCallback()); | 
|  | if (rv == OK) { | 
|  | HandOutSocket(connect_job->PassSocket(), | 
|  | connect_job->connect_timing(), | 
|  | handle, | 
|  | request_net_log); | 
|  | request_net_log.EndEvent(NetLogEventType::SOCKET_POOL); | 
|  | } else if (rv == ERR_IO_PENDING) { | 
|  | // TODO(ricea): Implement backup job timer? | 
|  | AddJob(handle, std::move(connect_job)); | 
|  | } else { | 
|  | std::unique_ptr<StreamSocket> error_socket; | 
|  | connect_job->GetAdditionalErrorState(handle); | 
|  | error_socket = connect_job->PassSocket(); | 
|  | if (error_socket) { | 
|  | HandOutSocket(std::move(error_socket), connect_job->connect_timing(), | 
|  | handle, request_net_log); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (rv != ERR_IO_PENDING) { | 
|  | request_net_log.EndEventWithNetErrorCode(NetLogEventType::SOCKET_POOL, rv); | 
|  | } | 
|  |  | 
|  | return rv; | 
|  | } | 
|  |  | 
|  | void WebSocketTransportClientSocketPool::RequestSockets( | 
|  | const std::string& group_name, | 
|  | const void* params, | 
|  | int num_sockets, | 
|  | const BoundNetLog& net_log) { | 
|  | NOTIMPLEMENTED(); | 
|  | } | 
|  |  | 
|  | void WebSocketTransportClientSocketPool::CancelRequest( | 
|  | const std::string& group_name, | 
|  | ClientSocketHandle* handle) { | 
|  | DCHECK(!handle->is_initialized()); | 
|  | if (DeleteStalledRequest(handle)) | 
|  | return; | 
|  | std::unique_ptr<StreamSocket> socket = handle->PassSocket(); | 
|  | if (socket) | 
|  | ReleaseSocket(handle->group_name(), std::move(socket), handle->id()); | 
|  | if (!DeleteJob(handle)) | 
|  | pending_callbacks_.erase(handle); | 
|  | if (!ReachedMaxSocketsLimit() && !stalled_request_queue_.empty()) | 
|  | ActivateStalledRequest(); | 
|  | } | 
|  |  | 
|  | void WebSocketTransportClientSocketPool::ReleaseSocket( | 
|  | const std::string& group_name, | 
|  | std::unique_ptr<StreamSocket> socket, | 
|  | int id) { | 
|  | WebSocketEndpointLockManager::GetInstance()->UnlockSocket(socket.get()); | 
|  | CHECK_GT(handed_out_socket_count_, 0); | 
|  | --handed_out_socket_count_; | 
|  | if (!ReachedMaxSocketsLimit() && !stalled_request_queue_.empty()) | 
|  | ActivateStalledRequest(); | 
|  | } | 
|  |  | 
|  | void WebSocketTransportClientSocketPool::FlushWithError(int error) { | 
|  | // Sockets which are in LOAD_STATE_CONNECTING are in danger of unlocking | 
|  | // sockets waiting for the endpoint lock. If they connected synchronously, | 
|  | // then OnConnectJobComplete(). The |flushing_| flag tells this object to | 
|  | // ignore spurious calls to OnConnectJobComplete(). It is safe to ignore those | 
|  | // calls because this method will delete the jobs and call their callbacks | 
|  | // anyway. | 
|  | flushing_ = true; | 
|  | for (PendingConnectsMap::iterator it = pending_connects_.begin(); | 
|  | it != pending_connects_.end(); | 
|  | ++it) { | 
|  | InvokeUserCallbackLater( | 
|  | it->second->handle(), it->second->callback(), error); | 
|  | delete it->second, it->second = NULL; | 
|  | } | 
|  | pending_connects_.clear(); | 
|  | for (StalledRequestQueue::iterator it = stalled_request_queue_.begin(); | 
|  | it != stalled_request_queue_.end(); | 
|  | ++it) { | 
|  | InvokeUserCallbackLater(it->handle, it->callback, error); | 
|  | } | 
|  | stalled_request_map_.clear(); | 
|  | stalled_request_queue_.clear(); | 
|  | flushing_ = false; | 
|  | } | 
|  |  | 
|  | void WebSocketTransportClientSocketPool::CloseIdleSockets() { | 
|  | // We have no idle sockets. | 
|  | } | 
|  |  | 
|  | int WebSocketTransportClientSocketPool::IdleSocketCount() const { | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | int WebSocketTransportClientSocketPool::IdleSocketCountInGroup( | 
|  | const std::string& group_name) const { | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | LoadState WebSocketTransportClientSocketPool::GetLoadState( | 
|  | const std::string& group_name, | 
|  | const ClientSocketHandle* handle) const { | 
|  | if (stalled_request_map_.find(handle) != stalled_request_map_.end()) | 
|  | return LOAD_STATE_WAITING_FOR_AVAILABLE_SOCKET; | 
|  | if (pending_callbacks_.count(handle)) | 
|  | return LOAD_STATE_CONNECTING; | 
|  | return LookupConnectJob(handle)->GetLoadState(); | 
|  | } | 
|  |  | 
|  | std::unique_ptr<base::DictionaryValue> | 
|  | WebSocketTransportClientSocketPool::GetInfoAsValue( | 
|  | const std::string& name, | 
|  | const std::string& type, | 
|  | bool include_nested_pools) const { | 
|  | std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue()); | 
|  | dict->SetString("name", name); | 
|  | dict->SetString("type", type); | 
|  | dict->SetInteger("handed_out_socket_count", handed_out_socket_count_); | 
|  | dict->SetInteger("connecting_socket_count", pending_connects_.size()); | 
|  | dict->SetInteger("idle_socket_count", 0); | 
|  | dict->SetInteger("max_socket_count", max_sockets_); | 
|  | dict->SetInteger("max_sockets_per_group", max_sockets_); | 
|  | dict->SetInteger("pool_generation_number", 0); | 
|  | return dict; | 
|  | } | 
|  |  | 
|  | base::TimeDelta WebSocketTransportClientSocketPool::ConnectionTimeout() const { | 
|  | // TODO(ricea): For now, we implement a global timeout for compatibility with | 
|  | // TransportConnectJob. Since WebSocketTransportConnectJob controls the | 
|  | // address selection process more tightly, it could do something smarter here. | 
|  | return base::TimeDelta::FromSeconds(TransportConnectJob::kTimeoutInSeconds); | 
|  | } | 
|  |  | 
|  | bool WebSocketTransportClientSocketPool::IsStalled() const { | 
|  | return !stalled_request_queue_.empty(); | 
|  | } | 
|  |  | 
|  | void WebSocketTransportClientSocketPool::OnConnectJobComplete( | 
|  | int result, | 
|  | WebSocketTransportConnectJob* job) { | 
|  | DCHECK_NE(ERR_IO_PENDING, result); | 
|  |  | 
|  | std::unique_ptr<StreamSocket> socket = job->PassSocket(); | 
|  |  | 
|  | // See comment in FlushWithError. | 
|  | if (flushing_) { | 
|  | WebSocketEndpointLockManager::GetInstance()->UnlockSocket(socket.get()); | 
|  | return; | 
|  | } | 
|  |  | 
|  | BoundNetLog request_net_log = job->request_net_log(); | 
|  | CompletionCallback callback = job->callback(); | 
|  | LoadTimingInfo::ConnectTiming connect_timing = job->connect_timing(); | 
|  |  | 
|  | ClientSocketHandle* const handle = job->handle(); | 
|  | bool handed_out_socket = false; | 
|  |  | 
|  | if (result == OK) { | 
|  | DCHECK(socket.get()); | 
|  | handed_out_socket = true; | 
|  | HandOutSocket(std::move(socket), connect_timing, handle, request_net_log); | 
|  | request_net_log.EndEvent(NetLogEventType::SOCKET_POOL); | 
|  | } else { | 
|  | // If we got a socket, it must contain error information so pass that | 
|  | // up so that the caller can retrieve it. | 
|  | job->GetAdditionalErrorState(handle); | 
|  | if (socket.get()) { | 
|  | handed_out_socket = true; | 
|  | HandOutSocket(std::move(socket), connect_timing, handle, request_net_log); | 
|  | } | 
|  | request_net_log.EndEventWithNetErrorCode(NetLogEventType::SOCKET_POOL, | 
|  | result); | 
|  | } | 
|  | bool delete_succeeded = DeleteJob(handle); | 
|  | DCHECK(delete_succeeded); | 
|  | if (!handed_out_socket && !stalled_request_queue_.empty() && | 
|  | !ReachedMaxSocketsLimit()) | 
|  | ActivateStalledRequest(); | 
|  | InvokeUserCallbackLater(handle, callback, result); | 
|  | } | 
|  |  | 
|  | void WebSocketTransportClientSocketPool::InvokeUserCallbackLater( | 
|  | ClientSocketHandle* handle, | 
|  | const CompletionCallback& callback, | 
|  | int rv) { | 
|  | DCHECK(!pending_callbacks_.count(handle)); | 
|  | pending_callbacks_.insert(handle); | 
|  | base::ThreadTaskRunnerHandle::Get()->PostTask( | 
|  | FROM_HERE, | 
|  | base::Bind(&WebSocketTransportClientSocketPool::InvokeUserCallback, | 
|  | weak_factory_.GetWeakPtr(), handle, callback, rv)); | 
|  | } | 
|  |  | 
|  | void WebSocketTransportClientSocketPool::InvokeUserCallback( | 
|  | ClientSocketHandle* handle, | 
|  | const CompletionCallback& callback, | 
|  | int rv) { | 
|  | if (pending_callbacks_.erase(handle)) | 
|  | callback.Run(rv); | 
|  | } | 
|  |  | 
|  | bool WebSocketTransportClientSocketPool::ReachedMaxSocketsLimit() const { | 
|  | return handed_out_socket_count_ >= max_sockets_ || | 
|  | base::checked_cast<int>(pending_connects_.size()) >= | 
|  | max_sockets_ - handed_out_socket_count_; | 
|  | } | 
|  |  | 
|  | void WebSocketTransportClientSocketPool::HandOutSocket( | 
|  | std::unique_ptr<StreamSocket> socket, | 
|  | const LoadTimingInfo::ConnectTiming& connect_timing, | 
|  | ClientSocketHandle* handle, | 
|  | const BoundNetLog& net_log) { | 
|  | DCHECK(socket); | 
|  | handle->SetSocket(std::move(socket)); | 
|  | DCHECK_EQ(ClientSocketHandle::UNUSED, handle->reuse_type()); | 
|  | DCHECK_EQ(0, handle->idle_time().InMicroseconds()); | 
|  | handle->set_pool_id(0); | 
|  | handle->set_connect_timing(connect_timing); | 
|  |  | 
|  | net_log.AddEvent( | 
|  | NetLogEventType::SOCKET_POOL_BOUND_TO_SOCKET, | 
|  | handle->socket()->NetLog().source().ToEventParametersCallback()); | 
|  |  | 
|  | ++handed_out_socket_count_; | 
|  | } | 
|  |  | 
|  | void WebSocketTransportClientSocketPool::AddJob( | 
|  | ClientSocketHandle* handle, | 
|  | std::unique_ptr<WebSocketTransportConnectJob> connect_job) { | 
|  | bool inserted = | 
|  | pending_connects_.insert(PendingConnectsMap::value_type( | 
|  | handle, connect_job.release())).second; | 
|  | DCHECK(inserted); | 
|  | } | 
|  |  | 
|  | bool WebSocketTransportClientSocketPool::DeleteJob(ClientSocketHandle* handle) { | 
|  | PendingConnectsMap::iterator it = pending_connects_.find(handle); | 
|  | if (it == pending_connects_.end()) | 
|  | return false; | 
|  | // Deleting a ConnectJob which holds an endpoint lock can lead to a different | 
|  | // ConnectJob proceeding to connect. If the connect proceeds synchronously | 
|  | // (usually because of a failure) then it can trigger that job to be | 
|  | // deleted. |it| remains valid because std::map guarantees that erase() does | 
|  | // not invalid iterators to other entries. | 
|  | delete it->second, it->second = NULL; | 
|  | DCHECK(pending_connects_.find(handle) == it); | 
|  | pending_connects_.erase(it); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | const WebSocketTransportConnectJob* | 
|  | WebSocketTransportClientSocketPool::LookupConnectJob( | 
|  | const ClientSocketHandle* handle) const { | 
|  | PendingConnectsMap::const_iterator it = pending_connects_.find(handle); | 
|  | CHECK(it != pending_connects_.end()); | 
|  | return it->second; | 
|  | } | 
|  |  | 
|  | void WebSocketTransportClientSocketPool::ActivateStalledRequest() { | 
|  | DCHECK(!stalled_request_queue_.empty()); | 
|  | DCHECK(!ReachedMaxSocketsLimit()); | 
|  | // Usually we will only be able to activate one stalled request at a time, | 
|  | // however if all the connects fail synchronously for some reason, we may be | 
|  | // able to clear the whole queue at once. | 
|  | while (!stalled_request_queue_.empty() && !ReachedMaxSocketsLimit()) { | 
|  | StalledRequest request(stalled_request_queue_.front()); | 
|  | stalled_request_queue_.pop_front(); | 
|  | stalled_request_map_.erase(request.handle); | 
|  | int rv = RequestSocket("ignored", &request.params, request.priority, | 
|  | // Stalled requests can't have |respect_limits| | 
|  | // DISABLED. | 
|  | RespectLimits::ENABLED, request.handle, | 
|  | request.callback, request.net_log); | 
|  | // ActivateStalledRequest() never returns synchronously, so it is never | 
|  | // called re-entrantly. | 
|  | if (rv != ERR_IO_PENDING) | 
|  | InvokeUserCallbackLater(request.handle, request.callback, rv); | 
|  | } | 
|  | } | 
|  |  | 
|  | bool WebSocketTransportClientSocketPool::DeleteStalledRequest( | 
|  | ClientSocketHandle* handle) { | 
|  | StalledRequestMap::iterator it = stalled_request_map_.find(handle); | 
|  | if (it == stalled_request_map_.end()) | 
|  | return false; | 
|  | stalled_request_queue_.erase(it->second); | 
|  | stalled_request_map_.erase(it); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | WebSocketTransportClientSocketPool::ConnectJobDelegate::ConnectJobDelegate( | 
|  | WebSocketTransportClientSocketPool* owner) | 
|  | : owner_(owner) {} | 
|  |  | 
|  | WebSocketTransportClientSocketPool::ConnectJobDelegate::~ConnectJobDelegate() {} | 
|  |  | 
|  | void | 
|  | WebSocketTransportClientSocketPool::ConnectJobDelegate::OnConnectJobComplete( | 
|  | int result, | 
|  | ConnectJob* job) { | 
|  | owner_->OnConnectJobComplete(result, | 
|  | static_cast<WebSocketTransportConnectJob*>(job)); | 
|  | } | 
|  |  | 
|  | WebSocketTransportClientSocketPool::StalledRequest::StalledRequest( | 
|  | const scoped_refptr<TransportSocketParams>& params, | 
|  | RequestPriority priority, | 
|  | ClientSocketHandle* handle, | 
|  | const CompletionCallback& callback, | 
|  | const BoundNetLog& net_log) | 
|  | : params(params), | 
|  | priority(priority), | 
|  | handle(handle), | 
|  | callback(callback), | 
|  | net_log(net_log) {} | 
|  |  | 
|  | WebSocketTransportClientSocketPool::StalledRequest::StalledRequest( | 
|  | const StalledRequest& other) = default; | 
|  |  | 
|  | WebSocketTransportClientSocketPool::StalledRequest::~StalledRequest() {} | 
|  |  | 
|  | }  // namespace net |