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