Home | History | Annotate | Download | only in socket
      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