transport_client_socket_pool.cc revision 7d4cd473f85ac64c3747c96c277f9e506a0d2246
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/logging.h"
11#include "base/message_loop.h"
12#include "base/metrics/histogram.h"
13#include "base/strings/string_util.h"
14#include "base/time.h"
15#include "base/values.h"
16#include "net/base/ip_endpoint.h"
17#include "net/base/net_log.h"
18#include "net/base/net_errors.h"
19#include "net/socket/client_socket_factory.h"
20#include "net/socket/client_socket_handle.h"
21#include "net/socket/client_socket_pool_base.h"
22#include "net/socket/socket_net_log_params.h"
23#include "net/socket/tcp_client_socket.h"
24
25using base::TimeDelta;
26
27namespace net {
28
29// TODO(willchan): Base this off RTT instead of statically setting it. Note we
30// choose a timeout that is different from the backup connect job timer so they
31// don't synchronize.
32const int TransportConnectJob::kIPv6FallbackTimerInMs = 300;
33
34namespace {
35
36// Returns true iff all addresses in |list| are in the IPv6 family.
37bool AddressListOnlyContainsIPv6(const AddressList& list) {
38  DCHECK(!list.empty());
39  for (AddressList::const_iterator iter = list.begin(); iter != list.end();
40       ++iter) {
41    if (iter->GetFamily() != ADDRESS_FAMILY_IPV6)
42      return false;
43  }
44  return true;
45}
46
47}  // namespace
48
49TransportSocketParams::TransportSocketParams(
50    const HostPortPair& host_port_pair,
51    RequestPriority priority,
52    bool disable_resolver_cache,
53    bool ignore_limits,
54    const OnHostResolutionCallback& host_resolution_callback)
55    : destination_(host_port_pair),
56      ignore_limits_(ignore_limits),
57      host_resolution_callback_(host_resolution_callback) {
58  Initialize(priority, disable_resolver_cache);
59}
60
61TransportSocketParams::~TransportSocketParams() {}
62
63void TransportSocketParams::Initialize(RequestPriority priority,
64                                       bool disable_resolver_cache) {
65  destination_.set_priority(priority);
66  if (disable_resolver_cache)
67    destination_.set_allow_cached_response(false);
68}
69
70// TransportConnectJobs will time out after this many seconds.  Note this is
71// the total time, including both host resolution and TCP connect() times.
72//
73// TODO(eroman): The use of this constant needs to be re-evaluated. The time
74// needed for TCPClientSocketXXX::Connect() can be arbitrarily long, since
75// the address list may contain many alternatives, and most of those may
76// timeout. Even worse, the per-connect timeout threshold varies greatly
77// between systems (anywhere from 20 seconds to 190 seconds).
78// See comment #12 at http://crbug.com/23364 for specifics.
79static const int kTransportConnectJobTimeoutInSeconds = 240;  // 4 minutes.
80
81TransportConnectJob::TransportConnectJob(
82    const std::string& group_name,
83    const scoped_refptr<TransportSocketParams>& params,
84    base::TimeDelta timeout_duration,
85    ClientSocketFactory* client_socket_factory,
86    HostResolver* host_resolver,
87    Delegate* delegate,
88    NetLog* net_log)
89    : ConnectJob(group_name, timeout_duration, delegate,
90                 BoundNetLog::Make(net_log, NetLog::SOURCE_CONNECT_JOB)),
91      params_(params),
92      client_socket_factory_(client_socket_factory),
93      resolver_(host_resolver),
94      next_state_(STATE_NONE) {
95}
96
97TransportConnectJob::~TransportConnectJob() {
98  // We don't worry about cancelling the host resolution and TCP connect, since
99  // ~SingleRequestHostResolver and ~StreamSocket will take care of it.
100}
101
102LoadState TransportConnectJob::GetLoadState() const {
103  switch (next_state_) {
104    case STATE_RESOLVE_HOST:
105    case STATE_RESOLVE_HOST_COMPLETE:
106      return LOAD_STATE_RESOLVING_HOST;
107    case STATE_TRANSPORT_CONNECT:
108    case STATE_TRANSPORT_CONNECT_COMPLETE:
109      return LOAD_STATE_CONNECTING;
110    default:
111      NOTREACHED();
112      return LOAD_STATE_IDLE;
113  }
114}
115
116// static
117void TransportConnectJob::MakeAddressListStartWithIPv4(AddressList* list) {
118  for (AddressList::iterator i = list->begin(); i != list->end(); ++i) {
119    if (i->GetFamily() == ADDRESS_FAMILY_IPV4) {
120      std::rotate(list->begin(), i, list->end());
121      break;
122    }
123  }
124}
125
126void TransportConnectJob::OnIOComplete(int result) {
127  int rv = DoLoop(result);
128  if (rv != ERR_IO_PENDING)
129    NotifyDelegateOfCompletion(rv);  // Deletes |this|
130}
131
132int TransportConnectJob::DoLoop(int result) {
133  DCHECK_NE(next_state_, STATE_NONE);
134
135  int rv = result;
136  do {
137    State state = next_state_;
138    next_state_ = STATE_NONE;
139    switch (state) {
140      case STATE_RESOLVE_HOST:
141        DCHECK_EQ(OK, rv);
142        rv = DoResolveHost();
143        break;
144      case STATE_RESOLVE_HOST_COMPLETE:
145        rv = DoResolveHostComplete(rv);
146        break;
147      case STATE_TRANSPORT_CONNECT:
148        DCHECK_EQ(OK, rv);
149        rv = DoTransportConnect();
150        break;
151      case STATE_TRANSPORT_CONNECT_COMPLETE:
152        rv = DoTransportConnectComplete(rv);
153        break;
154      default:
155        NOTREACHED();
156        rv = ERR_FAILED;
157        break;
158    }
159  } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
160
161  return rv;
162}
163
164int TransportConnectJob::DoResolveHost() {
165  next_state_ = STATE_RESOLVE_HOST_COMPLETE;
166  connect_timing_.dns_start = base::TimeTicks::Now();
167
168  return resolver_.Resolve(
169      params_->destination(), &addresses_,
170      base::Bind(&TransportConnectJob::OnIOComplete, base::Unretained(this)),
171      net_log());
172}
173
174int TransportConnectJob::DoResolveHostComplete(int result) {
175  connect_timing_.dns_end = base::TimeTicks::Now();
176  // Overwrite connection start time, since for connections that do not go
177  // through proxies, |connect_start| should not include dns lookup time.
178  connect_timing_.connect_start = connect_timing_.dns_end;
179
180  if (result == OK) {
181    // Invoke callback, and abort if it fails.
182    if (!params_->host_resolution_callback().is_null())
183      result = params_->host_resolution_callback().Run(addresses_, net_log());
184
185    if (result == OK)
186      next_state_ = STATE_TRANSPORT_CONNECT;
187  }
188  return result;
189}
190
191int TransportConnectJob::DoTransportConnect() {
192  next_state_ = STATE_TRANSPORT_CONNECT_COMPLETE;
193  transport_socket_.reset(client_socket_factory_->CreateTransportClientSocket(
194        addresses_, net_log().net_log(), net_log().source()));
195  int rv = transport_socket_->Connect(
196      base::Bind(&TransportConnectJob::OnIOComplete, base::Unretained(this)));
197  if (rv == ERR_IO_PENDING &&
198      addresses_.front().GetFamily() == ADDRESS_FAMILY_IPV6 &&
199      !AddressListOnlyContainsIPv6(addresses_)) {
200    fallback_timer_.Start(FROM_HERE,
201        base::TimeDelta::FromMilliseconds(kIPv6FallbackTimerInMs),
202        this, &TransportConnectJob::DoIPv6FallbackTransportConnect);
203  }
204  return rv;
205}
206
207int TransportConnectJob::DoTransportConnectComplete(int result) {
208  if (result == OK) {
209    bool is_ipv4 = addresses_.front().GetFamily() == ADDRESS_FAMILY_IPV4;
210    DCHECK(!connect_timing_.connect_start.is_null());
211    DCHECK(!connect_timing_.dns_start.is_null());
212    base::TimeTicks now = base::TimeTicks::Now();
213    base::TimeDelta total_duration = now - connect_timing_.dns_start;
214    UMA_HISTOGRAM_CUSTOM_TIMES(
215        "Net.DNS_Resolution_And_TCP_Connection_Latency2",
216        total_duration,
217        base::TimeDelta::FromMilliseconds(1),
218        base::TimeDelta::FromMinutes(10),
219        100);
220
221    base::TimeDelta connect_duration = now - connect_timing_.connect_start;
222    UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency",
223        connect_duration,
224        base::TimeDelta::FromMilliseconds(1),
225        base::TimeDelta::FromMinutes(10),
226        100);
227
228    if (is_ipv4) {
229      UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency_IPv4_No_Race",
230                                 connect_duration,
231                                 base::TimeDelta::FromMilliseconds(1),
232                                 base::TimeDelta::FromMinutes(10),
233                                 100);
234    } else {
235      if (AddressListOnlyContainsIPv6(addresses_)) {
236        UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency_IPv6_Solo",
237                                   connect_duration,
238                                   base::TimeDelta::FromMilliseconds(1),
239                                   base::TimeDelta::FromMinutes(10),
240                                   100);
241      } else {
242        UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency_IPv6_Raceable",
243                                   connect_duration,
244                                   base::TimeDelta::FromMilliseconds(1),
245                                   base::TimeDelta::FromMinutes(10),
246                                   100);
247      }
248    }
249    set_socket(transport_socket_.release());
250    fallback_timer_.Stop();
251  } else {
252    // Be a bit paranoid and kill off the fallback members to prevent reuse.
253    fallback_transport_socket_.reset();
254    fallback_addresses_.reset();
255  }
256
257  return result;
258}
259
260void TransportConnectJob::DoIPv6FallbackTransportConnect() {
261  // The timer should only fire while we're waiting for the main connect to
262  // succeed.
263  if (next_state_ != STATE_TRANSPORT_CONNECT_COMPLETE) {
264    NOTREACHED();
265    return;
266  }
267
268  DCHECK(!fallback_transport_socket_.get());
269  DCHECK(!fallback_addresses_.get());
270
271  fallback_addresses_.reset(new AddressList(addresses_));
272  MakeAddressListStartWithIPv4(fallback_addresses_.get());
273  fallback_transport_socket_.reset(
274      client_socket_factory_->CreateTransportClientSocket(
275          *fallback_addresses_, net_log().net_log(), net_log().source()));
276  fallback_connect_start_time_ = base::TimeTicks::Now();
277  int rv = fallback_transport_socket_->Connect(
278      base::Bind(
279          &TransportConnectJob::DoIPv6FallbackTransportConnectComplete,
280          base::Unretained(this)));
281  if (rv != ERR_IO_PENDING)
282    DoIPv6FallbackTransportConnectComplete(rv);
283}
284
285void TransportConnectJob::DoIPv6FallbackTransportConnectComplete(int result) {
286  // This should only happen when we're waiting for the main connect to succeed.
287  if (next_state_ != STATE_TRANSPORT_CONNECT_COMPLETE) {
288    NOTREACHED();
289    return;
290  }
291
292  DCHECK_NE(ERR_IO_PENDING, result);
293  DCHECK(fallback_transport_socket_.get());
294  DCHECK(fallback_addresses_.get());
295
296  if (result == OK) {
297    DCHECK(!fallback_connect_start_time_.is_null());
298    DCHECK(!connect_timing_.dns_start.is_null());
299    base::TimeTicks now = base::TimeTicks::Now();
300    base::TimeDelta total_duration = now - connect_timing_.dns_start;
301    UMA_HISTOGRAM_CUSTOM_TIMES(
302        "Net.DNS_Resolution_And_TCP_Connection_Latency2",
303        total_duration,
304        base::TimeDelta::FromMilliseconds(1),
305        base::TimeDelta::FromMinutes(10),
306        100);
307
308    base::TimeDelta connect_duration = now - fallback_connect_start_time_;
309    UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency",
310        connect_duration,
311        base::TimeDelta::FromMilliseconds(1),
312        base::TimeDelta::FromMinutes(10),
313        100);
314
315    UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency_IPv4_Wins_Race",
316        connect_duration,
317        base::TimeDelta::FromMilliseconds(1),
318        base::TimeDelta::FromMinutes(10),
319        100);
320    set_socket(fallback_transport_socket_.release());
321    next_state_ = STATE_NONE;
322    transport_socket_.reset();
323  } else {
324    // Be a bit paranoid and kill off the fallback members to prevent reuse.
325    fallback_transport_socket_.reset();
326    fallback_addresses_.reset();
327  }
328  NotifyDelegateOfCompletion(result);  // Deletes |this|
329}
330
331int TransportConnectJob::ConnectInternal() {
332  next_state_ = STATE_RESOLVE_HOST;
333  return DoLoop(OK);
334}
335
336ConnectJob*
337    TransportClientSocketPool::TransportConnectJobFactory::NewConnectJob(
338    const std::string& group_name,
339    const PoolBase::Request& request,
340    ConnectJob::Delegate* delegate) const {
341  return new TransportConnectJob(group_name,
342                                 request.params(),
343                                 ConnectionTimeout(),
344                                 client_socket_factory_,
345                                 host_resolver_,
346                                 delegate,
347                                 net_log_);
348}
349
350base::TimeDelta
351    TransportClientSocketPool::TransportConnectJobFactory::ConnectionTimeout()
352    const {
353  return base::TimeDelta::FromSeconds(kTransportConnectJobTimeoutInSeconds);
354}
355
356TransportClientSocketPool::TransportClientSocketPool(
357    int max_sockets,
358    int max_sockets_per_group,
359    ClientSocketPoolHistograms* histograms,
360    HostResolver* host_resolver,
361    ClientSocketFactory* client_socket_factory,
362    NetLog* net_log)
363    : base_(max_sockets, max_sockets_per_group, histograms,
364            ClientSocketPool::unused_idle_socket_timeout(),
365            ClientSocketPool::used_idle_socket_timeout(),
366            new TransportConnectJobFactory(client_socket_factory,
367                                     host_resolver, net_log)) {
368  base_.EnableConnectBackupJobs();
369}
370
371TransportClientSocketPool::~TransportClientSocketPool() {}
372
373int TransportClientSocketPool::RequestSocket(
374    const std::string& group_name,
375    const void* params,
376    RequestPriority priority,
377    ClientSocketHandle* handle,
378    const CompletionCallback& callback,
379    const BoundNetLog& net_log) {
380  const scoped_refptr<TransportSocketParams>* casted_params =
381      static_cast<const scoped_refptr<TransportSocketParams>*>(params);
382
383  if (net_log.IsLoggingAllEvents()) {
384    // TODO(eroman): Split out the host and port parameters.
385    net_log.AddEvent(
386        NetLog::TYPE_TCP_CLIENT_SOCKET_POOL_REQUESTED_SOCKET,
387        CreateNetLogHostPortPairCallback(
388            &casted_params->get()->destination().host_port_pair()));
389  }
390
391  return base_.RequestSocket(group_name, *casted_params, priority, handle,
392                             callback, net_log);
393}
394
395void TransportClientSocketPool::RequestSockets(
396    const std::string& group_name,
397    const void* params,
398    int num_sockets,
399    const BoundNetLog& net_log) {
400  const scoped_refptr<TransportSocketParams>* casted_params =
401      static_cast<const scoped_refptr<TransportSocketParams>*>(params);
402
403  if (net_log.IsLoggingAllEvents()) {
404    // TODO(eroman): Split out the host and port parameters.
405    net_log.AddEvent(
406        NetLog::TYPE_TCP_CLIENT_SOCKET_POOL_REQUESTED_SOCKETS,
407        CreateNetLogHostPortPairCallback(
408            &casted_params->get()->destination().host_port_pair()));
409  }
410
411  base_.RequestSockets(group_name, *casted_params, num_sockets, net_log);
412}
413
414void TransportClientSocketPool::CancelRequest(
415    const std::string& group_name,
416    ClientSocketHandle* handle) {
417  base_.CancelRequest(group_name, handle);
418}
419
420void TransportClientSocketPool::ReleaseSocket(
421    const std::string& group_name,
422    StreamSocket* socket,
423    int id) {
424  base_.ReleaseSocket(group_name, socket, id);
425}
426
427void TransportClientSocketPool::FlushWithError(int error) {
428  base_.FlushWithError(error);
429}
430
431bool TransportClientSocketPool::IsStalled() const {
432  return base_.IsStalled();
433}
434
435void TransportClientSocketPool::CloseIdleSockets() {
436  base_.CloseIdleSockets();
437}
438
439int TransportClientSocketPool::IdleSocketCount() const {
440  return base_.idle_socket_count();
441}
442
443int TransportClientSocketPool::IdleSocketCountInGroup(
444    const std::string& group_name) const {
445  return base_.IdleSocketCountInGroup(group_name);
446}
447
448LoadState TransportClientSocketPool::GetLoadState(
449    const std::string& group_name, const ClientSocketHandle* handle) const {
450  return base_.GetLoadState(group_name, handle);
451}
452
453void TransportClientSocketPool::AddLayeredPool(LayeredPool* layered_pool) {
454  base_.AddLayeredPool(layered_pool);
455}
456
457void TransportClientSocketPool::RemoveLayeredPool(LayeredPool* layered_pool) {
458  base_.RemoveLayeredPool(layered_pool);
459}
460
461base::DictionaryValue* TransportClientSocketPool::GetInfoAsValue(
462    const std::string& name,
463    const std::string& type,
464    bool include_nested_pools) const {
465  return base_.GetInfoAsValue(name, type);
466}
467
468base::TimeDelta TransportClientSocketPool::ConnectionTimeout() const {
469  return base_.ConnectionTimeout();
470}
471
472ClientSocketPoolHistograms* TransportClientSocketPool::histograms() const {
473  return base_.histograms();
474}
475
476}  // namespace net
477