transport_client_socket_pool.cc revision 3551c9c881056c480085172ff9840cab31610854
1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "net/socket/transport_client_socket_pool.h" 6 7#include <algorithm> 8 9#include "base/compiler_specific.h" 10#include "base/logging.h" 11#include "base/message_loop/message_loop.h" 12#include "base/metrics/histogram.h" 13#include "base/strings/string_util.h" 14#include "base/time/time.h" 15#include "base/values.h" 16#include "net/base/ip_endpoint.h" 17#include "net/base/net_errors.h" 18#include "net/base/net_log.h" 19#include "net/socket/client_socket_factory.h" 20#include "net/socket/client_socket_handle.h" 21#include "net/socket/client_socket_pool_base.h" 22#include "net/socket/socket_net_log_params.h" 23#include "net/socket/tcp_client_socket.h" 24 25using base::TimeDelta; 26 27namespace net { 28 29// TODO(willchan): Base this off RTT instead of statically setting it. Note we 30// choose a timeout that is different from the backup connect job timer so they 31// don't synchronize. 32const int TransportConnectJob::kIPv6FallbackTimerInMs = 300; 33 34namespace { 35 36// Returns true iff all addresses in |list| are in the IPv6 family. 37bool AddressListOnlyContainsIPv6(const AddressList& list) { 38 DCHECK(!list.empty()); 39 for (AddressList::const_iterator iter = list.begin(); iter != list.end(); 40 ++iter) { 41 if (iter->GetFamily() != ADDRESS_FAMILY_IPV6) 42 return false; 43 } 44 return true; 45} 46 47} // namespace 48 49TransportSocketParams::TransportSocketParams( 50 const HostPortPair& host_port_pair, 51 bool disable_resolver_cache, 52 bool ignore_limits, 53 const OnHostResolutionCallback& host_resolution_callback) 54 : destination_(host_port_pair), 55 ignore_limits_(ignore_limits), 56 host_resolution_callback_(host_resolution_callback) { 57 if (disable_resolver_cache) 58 destination_.set_allow_cached_response(false); 59} 60 61TransportSocketParams::~TransportSocketParams() {} 62 63// TransportConnectJobs will time out after this many seconds. Note this is 64// the total time, including both host resolution and TCP connect() times. 65// 66// TODO(eroman): The use of this constant needs to be re-evaluated. The time 67// needed for TCPClientSocketXXX::Connect() can be arbitrarily long, since 68// the address list may contain many alternatives, and most of those may 69// timeout. Even worse, the per-connect timeout threshold varies greatly 70// between systems (anywhere from 20 seconds to 190 seconds). 71// See comment #12 at http://crbug.com/23364 for specifics. 72static const int kTransportConnectJobTimeoutInSeconds = 240; // 4 minutes. 73 74TransportConnectJob::TransportConnectJob( 75 const std::string& group_name, 76 RequestPriority priority, 77 const scoped_refptr<TransportSocketParams>& params, 78 base::TimeDelta timeout_duration, 79 ClientSocketFactory* client_socket_factory, 80 HostResolver* host_resolver, 81 Delegate* delegate, 82 NetLog* net_log) 83 : ConnectJob(group_name, timeout_duration, priority, delegate, 84 BoundNetLog::Make(net_log, NetLog::SOURCE_CONNECT_JOB)), 85 params_(params), 86 client_socket_factory_(client_socket_factory), 87 resolver_(host_resolver), 88 next_state_(STATE_NONE) { 89} 90 91TransportConnectJob::~TransportConnectJob() { 92 // We don't worry about cancelling the host resolution and TCP connect, since 93 // ~SingleRequestHostResolver and ~StreamSocket will take care of it. 94} 95 96LoadState TransportConnectJob::GetLoadState() const { 97 switch (next_state_) { 98 case STATE_RESOLVE_HOST: 99 case STATE_RESOLVE_HOST_COMPLETE: 100 return LOAD_STATE_RESOLVING_HOST; 101 case STATE_TRANSPORT_CONNECT: 102 case STATE_TRANSPORT_CONNECT_COMPLETE: 103 return LOAD_STATE_CONNECTING; 104 default: 105 NOTREACHED(); 106 return LOAD_STATE_IDLE; 107 } 108} 109 110// static 111void TransportConnectJob::MakeAddressListStartWithIPv4(AddressList* list) { 112 for (AddressList::iterator i = list->begin(); i != list->end(); ++i) { 113 if (i->GetFamily() == ADDRESS_FAMILY_IPV4) { 114 std::rotate(list->begin(), i, list->end()); 115 break; 116 } 117 } 118} 119 120void TransportConnectJob::OnIOComplete(int result) { 121 int rv = DoLoop(result); 122 if (rv != ERR_IO_PENDING) 123 NotifyDelegateOfCompletion(rv); // Deletes |this| 124} 125 126int TransportConnectJob::DoLoop(int result) { 127 DCHECK_NE(next_state_, STATE_NONE); 128 129 int rv = result; 130 do { 131 State state = next_state_; 132 next_state_ = STATE_NONE; 133 switch (state) { 134 case STATE_RESOLVE_HOST: 135 DCHECK_EQ(OK, rv); 136 rv = DoResolveHost(); 137 break; 138 case STATE_RESOLVE_HOST_COMPLETE: 139 rv = DoResolveHostComplete(rv); 140 break; 141 case STATE_TRANSPORT_CONNECT: 142 DCHECK_EQ(OK, rv); 143 rv = DoTransportConnect(); 144 break; 145 case STATE_TRANSPORT_CONNECT_COMPLETE: 146 rv = DoTransportConnectComplete(rv); 147 break; 148 default: 149 NOTREACHED(); 150 rv = ERR_FAILED; 151 break; 152 } 153 } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE); 154 155 return rv; 156} 157 158int TransportConnectJob::DoResolveHost() { 159 next_state_ = STATE_RESOLVE_HOST_COMPLETE; 160 connect_timing_.dns_start = base::TimeTicks::Now(); 161 162 return resolver_.Resolve( 163 params_->destination(), 164 priority(), 165 &addresses_, 166 base::Bind(&TransportConnectJob::OnIOComplete, base::Unretained(this)), 167 net_log()); 168} 169 170int TransportConnectJob::DoResolveHostComplete(int result) { 171 connect_timing_.dns_end = base::TimeTicks::Now(); 172 // Overwrite connection start time, since for connections that do not go 173 // through proxies, |connect_start| should not include dns lookup time. 174 connect_timing_.connect_start = connect_timing_.dns_end; 175 176 if (result == OK) { 177 // Invoke callback, and abort if it fails. 178 if (!params_->host_resolution_callback().is_null()) 179 result = params_->host_resolution_callback().Run(addresses_, net_log()); 180 181 if (result == OK) 182 next_state_ = STATE_TRANSPORT_CONNECT; 183 } 184 return result; 185} 186 187int TransportConnectJob::DoTransportConnect() { 188 next_state_ = STATE_TRANSPORT_CONNECT_COMPLETE; 189 transport_socket_ = client_socket_factory_->CreateTransportClientSocket( 190 addresses_, net_log().net_log(), net_log().source()); 191 int rv = transport_socket_->Connect( 192 base::Bind(&TransportConnectJob::OnIOComplete, base::Unretained(this))); 193 if (rv == ERR_IO_PENDING && 194 addresses_.front().GetFamily() == ADDRESS_FAMILY_IPV6 && 195 !AddressListOnlyContainsIPv6(addresses_)) { 196 fallback_timer_.Start(FROM_HERE, 197 base::TimeDelta::FromMilliseconds(kIPv6FallbackTimerInMs), 198 this, &TransportConnectJob::DoIPv6FallbackTransportConnect); 199 } 200 return rv; 201} 202 203int TransportConnectJob::DoTransportConnectComplete(int result) { 204 if (result == OK) { 205 bool is_ipv4 = addresses_.front().GetFamily() == ADDRESS_FAMILY_IPV4; 206 DCHECK(!connect_timing_.connect_start.is_null()); 207 DCHECK(!connect_timing_.dns_start.is_null()); 208 base::TimeTicks now = base::TimeTicks::Now(); 209 base::TimeDelta total_duration = now - connect_timing_.dns_start; 210 UMA_HISTOGRAM_CUSTOM_TIMES( 211 "Net.DNS_Resolution_And_TCP_Connection_Latency2", 212 total_duration, 213 base::TimeDelta::FromMilliseconds(1), 214 base::TimeDelta::FromMinutes(10), 215 100); 216 217 base::TimeDelta connect_duration = now - connect_timing_.connect_start; 218 UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency", 219 connect_duration, 220 base::TimeDelta::FromMilliseconds(1), 221 base::TimeDelta::FromMinutes(10), 222 100); 223 224 if (is_ipv4) { 225 UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency_IPv4_No_Race", 226 connect_duration, 227 base::TimeDelta::FromMilliseconds(1), 228 base::TimeDelta::FromMinutes(10), 229 100); 230 } else { 231 if (AddressListOnlyContainsIPv6(addresses_)) { 232 UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency_IPv6_Solo", 233 connect_duration, 234 base::TimeDelta::FromMilliseconds(1), 235 base::TimeDelta::FromMinutes(10), 236 100); 237 } else { 238 UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency_IPv6_Raceable", 239 connect_duration, 240 base::TimeDelta::FromMilliseconds(1), 241 base::TimeDelta::FromMinutes(10), 242 100); 243 } 244 } 245 SetSocket(transport_socket_.Pass()); 246 fallback_timer_.Stop(); 247 } else { 248 // Be a bit paranoid and kill off the fallback members to prevent reuse. 249 fallback_transport_socket_.reset(); 250 fallback_addresses_.reset(); 251 } 252 253 return result; 254} 255 256void TransportConnectJob::DoIPv6FallbackTransportConnect() { 257 // The timer should only fire while we're waiting for the main connect to 258 // succeed. 259 if (next_state_ != STATE_TRANSPORT_CONNECT_COMPLETE) { 260 NOTREACHED(); 261 return; 262 } 263 264 DCHECK(!fallback_transport_socket_.get()); 265 DCHECK(!fallback_addresses_.get()); 266 267 fallback_addresses_.reset(new AddressList(addresses_)); 268 MakeAddressListStartWithIPv4(fallback_addresses_.get()); 269 fallback_transport_socket_ = 270 client_socket_factory_->CreateTransportClientSocket( 271 *fallback_addresses_, net_log().net_log(), net_log().source()); 272 fallback_connect_start_time_ = base::TimeTicks::Now(); 273 int rv = fallback_transport_socket_->Connect( 274 base::Bind( 275 &TransportConnectJob::DoIPv6FallbackTransportConnectComplete, 276 base::Unretained(this))); 277 if (rv != ERR_IO_PENDING) 278 DoIPv6FallbackTransportConnectComplete(rv); 279} 280 281void TransportConnectJob::DoIPv6FallbackTransportConnectComplete(int result) { 282 // This should only happen when we're waiting for the main connect to succeed. 283 if (next_state_ != STATE_TRANSPORT_CONNECT_COMPLETE) { 284 NOTREACHED(); 285 return; 286 } 287 288 DCHECK_NE(ERR_IO_PENDING, result); 289 DCHECK(fallback_transport_socket_.get()); 290 DCHECK(fallback_addresses_.get()); 291 292 if (result == OK) { 293 DCHECK(!fallback_connect_start_time_.is_null()); 294 DCHECK(!connect_timing_.dns_start.is_null()); 295 base::TimeTicks now = base::TimeTicks::Now(); 296 base::TimeDelta total_duration = now - connect_timing_.dns_start; 297 UMA_HISTOGRAM_CUSTOM_TIMES( 298 "Net.DNS_Resolution_And_TCP_Connection_Latency2", 299 total_duration, 300 base::TimeDelta::FromMilliseconds(1), 301 base::TimeDelta::FromMinutes(10), 302 100); 303 304 base::TimeDelta connect_duration = now - fallback_connect_start_time_; 305 UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency", 306 connect_duration, 307 base::TimeDelta::FromMilliseconds(1), 308 base::TimeDelta::FromMinutes(10), 309 100); 310 311 UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency_IPv4_Wins_Race", 312 connect_duration, 313 base::TimeDelta::FromMilliseconds(1), 314 base::TimeDelta::FromMinutes(10), 315 100); 316 SetSocket(fallback_transport_socket_.Pass()); 317 next_state_ = STATE_NONE; 318 transport_socket_.reset(); 319 } else { 320 // Be a bit paranoid and kill off the fallback members to prevent reuse. 321 fallback_transport_socket_.reset(); 322 fallback_addresses_.reset(); 323 } 324 NotifyDelegateOfCompletion(result); // Deletes |this| 325} 326 327int TransportConnectJob::ConnectInternal() { 328 next_state_ = STATE_RESOLVE_HOST; 329 return DoLoop(OK); 330} 331 332scoped_ptr<ConnectJob> 333 TransportClientSocketPool::TransportConnectJobFactory::NewConnectJob( 334 const std::string& group_name, 335 const PoolBase::Request& request, 336 ConnectJob::Delegate* delegate) const { 337 return scoped_ptr<ConnectJob>( 338 new TransportConnectJob(group_name, 339 request.priority(), 340 request.params(), 341 ConnectionTimeout(), 342 client_socket_factory_, 343 host_resolver_, 344 delegate, 345 net_log_)); 346} 347 348base::TimeDelta 349 TransportClientSocketPool::TransportConnectJobFactory::ConnectionTimeout() 350 const { 351 return base::TimeDelta::FromSeconds(kTransportConnectJobTimeoutInSeconds); 352} 353 354TransportClientSocketPool::TransportClientSocketPool( 355 int max_sockets, 356 int max_sockets_per_group, 357 ClientSocketPoolHistograms* histograms, 358 HostResolver* host_resolver, 359 ClientSocketFactory* client_socket_factory, 360 NetLog* net_log) 361 : base_(NULL, max_sockets, max_sockets_per_group, histograms, 362 ClientSocketPool::unused_idle_socket_timeout(), 363 ClientSocketPool::used_idle_socket_timeout(), 364 new TransportConnectJobFactory(client_socket_factory, 365 host_resolver, net_log)) { 366 base_.EnableConnectBackupJobs(); 367} 368 369TransportClientSocketPool::~TransportClientSocketPool() {} 370 371int TransportClientSocketPool::RequestSocket( 372 const std::string& group_name, 373 const void* params, 374 RequestPriority priority, 375 ClientSocketHandle* handle, 376 const CompletionCallback& callback, 377 const BoundNetLog& net_log) { 378 const scoped_refptr<TransportSocketParams>* casted_params = 379 static_cast<const scoped_refptr<TransportSocketParams>*>(params); 380 381 if (net_log.IsLoggingAllEvents()) { 382 // TODO(eroman): Split out the host and port parameters. 383 net_log.AddEvent( 384 NetLog::TYPE_TCP_CLIENT_SOCKET_POOL_REQUESTED_SOCKET, 385 CreateNetLogHostPortPairCallback( 386 &casted_params->get()->destination().host_port_pair())); 387 } 388 389 return base_.RequestSocket(group_name, *casted_params, priority, handle, 390 callback, net_log); 391} 392 393void TransportClientSocketPool::RequestSockets( 394 const std::string& group_name, 395 const void* params, 396 int num_sockets, 397 const BoundNetLog& net_log) { 398 const scoped_refptr<TransportSocketParams>* casted_params = 399 static_cast<const scoped_refptr<TransportSocketParams>*>(params); 400 401 if (net_log.IsLoggingAllEvents()) { 402 // TODO(eroman): Split out the host and port parameters. 403 net_log.AddEvent( 404 NetLog::TYPE_TCP_CLIENT_SOCKET_POOL_REQUESTED_SOCKETS, 405 CreateNetLogHostPortPairCallback( 406 &casted_params->get()->destination().host_port_pair())); 407 } 408 409 base_.RequestSockets(group_name, *casted_params, num_sockets, net_log); 410} 411 412void TransportClientSocketPool::CancelRequest( 413 const std::string& group_name, 414 ClientSocketHandle* handle) { 415 base_.CancelRequest(group_name, handle); 416} 417 418void TransportClientSocketPool::ReleaseSocket( 419 const std::string& group_name, 420 scoped_ptr<StreamSocket> socket, 421 int id) { 422 base_.ReleaseSocket(group_name, socket.Pass(), id); 423} 424 425void TransportClientSocketPool::FlushWithError(int error) { 426 base_.FlushWithError(error); 427} 428 429void TransportClientSocketPool::CloseIdleSockets() { 430 base_.CloseIdleSockets(); 431} 432 433int TransportClientSocketPool::IdleSocketCount() const { 434 return base_.idle_socket_count(); 435} 436 437int TransportClientSocketPool::IdleSocketCountInGroup( 438 const std::string& group_name) const { 439 return base_.IdleSocketCountInGroup(group_name); 440} 441 442LoadState TransportClientSocketPool::GetLoadState( 443 const std::string& group_name, const ClientSocketHandle* handle) const { 444 return base_.GetLoadState(group_name, handle); 445} 446 447base::DictionaryValue* TransportClientSocketPool::GetInfoAsValue( 448 const std::string& name, 449 const std::string& type, 450 bool include_nested_pools) const { 451 return base_.GetInfoAsValue(name, type); 452} 453 454base::TimeDelta TransportClientSocketPool::ConnectionTimeout() const { 455 return base_.ConnectionTimeout(); 456} 457 458ClientSocketPoolHistograms* TransportClientSocketPool::histograms() const { 459 return base_.histograms(); 460} 461 462bool TransportClientSocketPool::IsStalled() const { 463 return base_.IsStalled(); 464} 465 466void TransportClientSocketPool::AddHigherLayeredPool( 467 HigherLayeredPool* higher_pool) { 468 base_.AddHigherLayeredPool(higher_pool); 469} 470 471void TransportClientSocketPool::RemoveHigherLayeredPool( 472 HigherLayeredPool* higher_pool) { 473 base_.RemoveHigherLayeredPool(higher_pool); 474} 475 476} // namespace net 477