transport_client_socket_pool.cc revision 4e180b6a0b4720a9b8e9e959a882386f690f08ff
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 TransportConnectJob::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 84TransportConnectJob::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 less_than_20ms_since_connect_(true) { 100} 101 102TransportConnectJob::~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 107LoadState 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 123void 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 132void TransportConnectJob::OnIOComplete(int result) { 133 int rv = DoLoop(result); 134 if (rv != ERR_IO_PENDING) 135 NotifyDelegateOfCompletion(rv); // Deletes |this| 136} 137 138int 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 170int 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 182int 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 199int 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 (now - last_connect_time).InMilliseconds() < 20) { 209 less_than_20ms_since_connect_ = true; 210 } else { 211 less_than_20ms_since_connect_ = false; 212 } 213 214 next_state_ = STATE_TRANSPORT_CONNECT_COMPLETE; 215 transport_socket_ = client_socket_factory_->CreateTransportClientSocket( 216 addresses_, net_log().net_log(), net_log().source()); 217 int rv = transport_socket_->Connect( 218 base::Bind(&TransportConnectJob::OnIOComplete, base::Unretained(this))); 219 if (rv == ERR_IO_PENDING && 220 addresses_.front().GetFamily() == ADDRESS_FAMILY_IPV6 && 221 !AddressListOnlyContainsIPv6(addresses_)) { 222 fallback_timer_.Start(FROM_HERE, 223 base::TimeDelta::FromMilliseconds(kIPv6FallbackTimerInMs), 224 this, &TransportConnectJob::DoIPv6FallbackTransportConnect); 225 } 226 return rv; 227} 228 229int TransportConnectJob::DoTransportConnectComplete(int result) { 230 if (result == OK) { 231 bool is_ipv4 = addresses_.front().GetFamily() == ADDRESS_FAMILY_IPV4; 232 DCHECK(!connect_timing_.connect_start.is_null()); 233 DCHECK(!connect_timing_.dns_start.is_null()); 234 base::TimeTicks now = base::TimeTicks::Now(); 235 base::TimeDelta total_duration = now - connect_timing_.dns_start; 236 UMA_HISTOGRAM_CUSTOM_TIMES( 237 "Net.DNS_Resolution_And_TCP_Connection_Latency2", 238 total_duration, 239 base::TimeDelta::FromMilliseconds(1), 240 base::TimeDelta::FromMinutes(10), 241 100); 242 243 base::TimeDelta connect_duration = now - connect_timing_.connect_start; 244 UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency", 245 connect_duration, 246 base::TimeDelta::FromMilliseconds(1), 247 base::TimeDelta::FromMinutes(10), 248 100); 249 250 if (less_than_20ms_since_connect_) { 251 UMA_HISTOGRAM_CUSTOM_TIMES( 252 "Net.TCP_Connection_Latency_Interval_20ms_Minus", 253 connect_duration, 254 base::TimeDelta::FromMilliseconds(1), 255 base::TimeDelta::FromMinutes(10), 256 100); 257 } else { 258 UMA_HISTOGRAM_CUSTOM_TIMES( 259 "Net.TCP_Connection_Latency_Interval_20ms_Plus", 260 connect_duration, 261 base::TimeDelta::FromMilliseconds(1), 262 base::TimeDelta::FromMinutes(10), 263 100); 264 } 265 266 if (is_ipv4) { 267 UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency_IPv4_No_Race", 268 connect_duration, 269 base::TimeDelta::FromMilliseconds(1), 270 base::TimeDelta::FromMinutes(10), 271 100); 272 } else { 273 if (AddressListOnlyContainsIPv6(addresses_)) { 274 UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency_IPv6_Solo", 275 connect_duration, 276 base::TimeDelta::FromMilliseconds(1), 277 base::TimeDelta::FromMinutes(10), 278 100); 279 } else { 280 UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency_IPv6_Raceable", 281 connect_duration, 282 base::TimeDelta::FromMilliseconds(1), 283 base::TimeDelta::FromMinutes(10), 284 100); 285 } 286 } 287 SetSocket(transport_socket_.Pass()); 288 fallback_timer_.Stop(); 289 } else { 290 // Be a bit paranoid and kill off the fallback members to prevent reuse. 291 fallback_transport_socket_.reset(); 292 fallback_addresses_.reset(); 293 } 294 295 return result; 296} 297 298void TransportConnectJob::DoIPv6FallbackTransportConnect() { 299 // The timer should only fire while we're waiting for the main connect to 300 // succeed. 301 if (next_state_ != STATE_TRANSPORT_CONNECT_COMPLETE) { 302 NOTREACHED(); 303 return; 304 } 305 306 DCHECK(!fallback_transport_socket_.get()); 307 DCHECK(!fallback_addresses_.get()); 308 309 fallback_addresses_.reset(new AddressList(addresses_)); 310 MakeAddressListStartWithIPv4(fallback_addresses_.get()); 311 fallback_transport_socket_ = 312 client_socket_factory_->CreateTransportClientSocket( 313 *fallback_addresses_, net_log().net_log(), net_log().source()); 314 fallback_connect_start_time_ = base::TimeTicks::Now(); 315 int rv = fallback_transport_socket_->Connect( 316 base::Bind( 317 &TransportConnectJob::DoIPv6FallbackTransportConnectComplete, 318 base::Unretained(this))); 319 if (rv != ERR_IO_PENDING) 320 DoIPv6FallbackTransportConnectComplete(rv); 321} 322 323void TransportConnectJob::DoIPv6FallbackTransportConnectComplete(int result) { 324 // This should only happen when we're waiting for the main connect to succeed. 325 if (next_state_ != STATE_TRANSPORT_CONNECT_COMPLETE) { 326 NOTREACHED(); 327 return; 328 } 329 330 DCHECK_NE(ERR_IO_PENDING, result); 331 DCHECK(fallback_transport_socket_.get()); 332 DCHECK(fallback_addresses_.get()); 333 334 if (result == OK) { 335 DCHECK(!fallback_connect_start_time_.is_null()); 336 DCHECK(!connect_timing_.dns_start.is_null()); 337 base::TimeTicks now = base::TimeTicks::Now(); 338 base::TimeDelta total_duration = now - connect_timing_.dns_start; 339 UMA_HISTOGRAM_CUSTOM_TIMES( 340 "Net.DNS_Resolution_And_TCP_Connection_Latency2", 341 total_duration, 342 base::TimeDelta::FromMilliseconds(1), 343 base::TimeDelta::FromMinutes(10), 344 100); 345 346 base::TimeDelta connect_duration = now - fallback_connect_start_time_; 347 UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency", 348 connect_duration, 349 base::TimeDelta::FromMilliseconds(1), 350 base::TimeDelta::FromMinutes(10), 351 100); 352 353 UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency_IPv4_Wins_Race", 354 connect_duration, 355 base::TimeDelta::FromMilliseconds(1), 356 base::TimeDelta::FromMinutes(10), 357 100); 358 SetSocket(fallback_transport_socket_.Pass()); 359 next_state_ = STATE_NONE; 360 transport_socket_.reset(); 361 } else { 362 // Be a bit paranoid and kill off the fallback members to prevent reuse. 363 fallback_transport_socket_.reset(); 364 fallback_addresses_.reset(); 365 } 366 NotifyDelegateOfCompletion(result); // Deletes |this| 367} 368 369int TransportConnectJob::ConnectInternal() { 370 next_state_ = STATE_RESOLVE_HOST; 371 return DoLoop(OK); 372} 373 374scoped_ptr<ConnectJob> 375 TransportClientSocketPool::TransportConnectJobFactory::NewConnectJob( 376 const std::string& group_name, 377 const PoolBase::Request& request, 378 ConnectJob::Delegate* delegate) const { 379 return scoped_ptr<ConnectJob>( 380 new TransportConnectJob(group_name, 381 request.priority(), 382 request.params(), 383 ConnectionTimeout(), 384 client_socket_factory_, 385 host_resolver_, 386 delegate, 387 net_log_)); 388} 389 390base::TimeDelta 391 TransportClientSocketPool::TransportConnectJobFactory::ConnectionTimeout() 392 const { 393 return base::TimeDelta::FromSeconds(kTransportConnectJobTimeoutInSeconds); 394} 395 396TransportClientSocketPool::TransportClientSocketPool( 397 int max_sockets, 398 int max_sockets_per_group, 399 ClientSocketPoolHistograms* histograms, 400 HostResolver* host_resolver, 401 ClientSocketFactory* client_socket_factory, 402 NetLog* net_log) 403 : base_(NULL, max_sockets, max_sockets_per_group, histograms, 404 ClientSocketPool::unused_idle_socket_timeout(), 405 ClientSocketPool::used_idle_socket_timeout(), 406 new TransportConnectJobFactory(client_socket_factory, 407 host_resolver, net_log)) { 408 base_.EnableConnectBackupJobs(); 409} 410 411TransportClientSocketPool::~TransportClientSocketPool() {} 412 413int TransportClientSocketPool::RequestSocket( 414 const std::string& group_name, 415 const void* params, 416 RequestPriority priority, 417 ClientSocketHandle* handle, 418 const CompletionCallback& callback, 419 const BoundNetLog& net_log) { 420 const scoped_refptr<TransportSocketParams>* casted_params = 421 static_cast<const scoped_refptr<TransportSocketParams>*>(params); 422 423 if (net_log.IsLoggingAllEvents()) { 424 // TODO(eroman): Split out the host and port parameters. 425 net_log.AddEvent( 426 NetLog::TYPE_TCP_CLIENT_SOCKET_POOL_REQUESTED_SOCKET, 427 CreateNetLogHostPortPairCallback( 428 &casted_params->get()->destination().host_port_pair())); 429 } 430 431 return base_.RequestSocket(group_name, *casted_params, priority, handle, 432 callback, net_log); 433} 434 435void TransportClientSocketPool::RequestSockets( 436 const std::string& group_name, 437 const void* params, 438 int num_sockets, 439 const BoundNetLog& net_log) { 440 const scoped_refptr<TransportSocketParams>* casted_params = 441 static_cast<const scoped_refptr<TransportSocketParams>*>(params); 442 443 if (net_log.IsLoggingAllEvents()) { 444 // TODO(eroman): Split out the host and port parameters. 445 net_log.AddEvent( 446 NetLog::TYPE_TCP_CLIENT_SOCKET_POOL_REQUESTED_SOCKETS, 447 CreateNetLogHostPortPairCallback( 448 &casted_params->get()->destination().host_port_pair())); 449 } 450 451 base_.RequestSockets(group_name, *casted_params, num_sockets, net_log); 452} 453 454void TransportClientSocketPool::CancelRequest( 455 const std::string& group_name, 456 ClientSocketHandle* handle) { 457 base_.CancelRequest(group_name, handle); 458} 459 460void TransportClientSocketPool::ReleaseSocket( 461 const std::string& group_name, 462 scoped_ptr<StreamSocket> socket, 463 int id) { 464 base_.ReleaseSocket(group_name, socket.Pass(), id); 465} 466 467void TransportClientSocketPool::FlushWithError(int error) { 468 base_.FlushWithError(error); 469} 470 471void TransportClientSocketPool::CloseIdleSockets() { 472 base_.CloseIdleSockets(); 473} 474 475int TransportClientSocketPool::IdleSocketCount() const { 476 return base_.idle_socket_count(); 477} 478 479int TransportClientSocketPool::IdleSocketCountInGroup( 480 const std::string& group_name) const { 481 return base_.IdleSocketCountInGroup(group_name); 482} 483 484LoadState TransportClientSocketPool::GetLoadState( 485 const std::string& group_name, const ClientSocketHandle* handle) const { 486 return base_.GetLoadState(group_name, handle); 487} 488 489base::DictionaryValue* TransportClientSocketPool::GetInfoAsValue( 490 const std::string& name, 491 const std::string& type, 492 bool include_nested_pools) const { 493 return base_.GetInfoAsValue(name, type); 494} 495 496base::TimeDelta TransportClientSocketPool::ConnectionTimeout() const { 497 return base_.ConnectionTimeout(); 498} 499 500ClientSocketPoolHistograms* TransportClientSocketPool::histograms() const { 501 return base_.histograms(); 502} 503 504bool TransportClientSocketPool::IsStalled() const { 505 return base_.IsStalled(); 506} 507 508void TransportClientSocketPool::AddHigherLayeredPool( 509 HigherLayeredPool* higher_pool) { 510 base_.AddHigherLayeredPool(higher_pool); 511} 512 513void TransportClientSocketPool::RemoveHigherLayeredPool( 514 HigherLayeredPool* higher_pool) { 515 base_.RemoveHigherLayeredPool(higher_pool); 516} 517 518} // namespace net 519