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