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