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