transport_client_socket_pool.cc revision a1401311d1ab56c4ed0a474bd38c108f75cb0cd9
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      interval_between_connects_(CONNECT_INTERVAL_GT_20MS) {
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    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
234int 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
317void 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
342void 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
388int TransportConnectJob::ConnectInternal() {
389  next_state_ = STATE_RESOLVE_HOST;
390  return DoLoop(OK);
391}
392
393scoped_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
409base::TimeDelta
410    TransportClientSocketPool::TransportConnectJobFactory::ConnectionTimeout()
411    const {
412  return base::TimeDelta::FromSeconds(kTransportConnectJobTimeoutInSeconds);
413}
414
415TransportClientSocketPool::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
430TransportClientSocketPool::~TransportClientSocketPool() {}
431
432int 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.IsLogging()) {
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
454void 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.IsLogging()) {
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
473void TransportClientSocketPool::CancelRequest(
474    const std::string& group_name,
475    ClientSocketHandle* handle) {
476  base_.CancelRequest(group_name, handle);
477}
478
479void 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
486void TransportClientSocketPool::FlushWithError(int error) {
487  base_.FlushWithError(error);
488}
489
490void TransportClientSocketPool::CloseIdleSockets() {
491  base_.CloseIdleSockets();
492}
493
494int TransportClientSocketPool::IdleSocketCount() const {
495  return base_.idle_socket_count();
496}
497
498int TransportClientSocketPool::IdleSocketCountInGroup(
499    const std::string& group_name) const {
500  return base_.IdleSocketCountInGroup(group_name);
501}
502
503LoadState TransportClientSocketPool::GetLoadState(
504    const std::string& group_name, const ClientSocketHandle* handle) const {
505  return base_.GetLoadState(group_name, handle);
506}
507
508base::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
515base::TimeDelta TransportClientSocketPool::ConnectionTimeout() const {
516  return base_.ConnectionTimeout();
517}
518
519ClientSocketPoolHistograms* TransportClientSocketPool::histograms() const {
520  return base_.histograms();
521}
522
523bool TransportClientSocketPool::IsStalled() const {
524  return base_.IsStalled();
525}
526
527void TransportClientSocketPool::AddHigherLayeredPool(
528    HigherLayeredPool* higher_pool) {
529  base_.AddHigherLayeredPool(higher_pool);
530}
531
532void TransportClientSocketPool::RemoveHigherLayeredPool(
533    HigherLayeredPool* higher_pool) {
534  base_.RemoveHigherLayeredPool(higher_pool);
535}
536
537}  // namespace net
538