transport_client_socket_pool.cc revision 5821806d5e7f356e8fa4b058a389a808ea183019
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/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() != AF_INET6)
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() == AF_INET) {
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  return resolver_.Resolve(
167      params_->destination(), &addresses_,
168      base::Bind(&TransportConnectJob::OnIOComplete, base::Unretained(this)),
169      net_log());
170}
171
172int TransportConnectJob::DoResolveHostComplete(int result) {
173  if (result == OK) {
174    // Invoke callback, and abort if it fails.
175    if (!params_->host_resolution_callback().is_null())
176      result = params_->host_resolution_callback().Run(addresses_, net_log());
177
178    if (result == OK)
179      next_state_ = STATE_TRANSPORT_CONNECT;
180  }
181  return result;
182}
183
184int TransportConnectJob::DoTransportConnect() {
185  next_state_ = STATE_TRANSPORT_CONNECT_COMPLETE;
186  transport_socket_.reset(client_socket_factory_->CreateTransportClientSocket(
187        addresses_, net_log().net_log(), net_log().source()));
188  connect_start_time_ = base::TimeTicks::Now();
189  int rv = transport_socket_->Connect(
190      base::Bind(&TransportConnectJob::OnIOComplete, base::Unretained(this)));
191  if (rv == ERR_IO_PENDING &&
192      addresses_.front().GetFamily() == AF_INET6 &&
193      !AddressListOnlyContainsIPv6(addresses_)) {
194    fallback_timer_.Start(FROM_HERE,
195        base::TimeDelta::FromMilliseconds(kIPv6FallbackTimerInMs),
196        this, &TransportConnectJob::DoIPv6FallbackTransportConnect);
197  }
198  return rv;
199}
200
201int TransportConnectJob::DoTransportConnectComplete(int result) {
202  if (result == OK) {
203    bool is_ipv4 = addresses_.front().GetFamily() != AF_INET6;
204    DCHECK(connect_start_time_ != base::TimeTicks());
205    DCHECK(start_time_ != base::TimeTicks());
206    base::TimeTicks now = base::TimeTicks::Now();
207    base::TimeDelta total_duration = now - start_time_;
208    UMA_HISTOGRAM_CUSTOM_TIMES(
209        "Net.DNS_Resolution_And_TCP_Connection_Latency2",
210        total_duration,
211        base::TimeDelta::FromMilliseconds(1),
212        base::TimeDelta::FromMinutes(10),
213        100);
214
215    base::TimeDelta connect_duration = now - connect_start_time_;
216    UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency",
217        connect_duration,
218        base::TimeDelta::FromMilliseconds(1),
219        base::TimeDelta::FromMinutes(10),
220        100);
221
222    if (is_ipv4) {
223      UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency_IPv4_No_Race",
224                                 connect_duration,
225                                 base::TimeDelta::FromMilliseconds(1),
226                                 base::TimeDelta::FromMinutes(10),
227                                 100);
228    } else {
229      if (AddressListOnlyContainsIPv6(addresses_)) {
230        UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency_IPv6_Solo",
231                                   connect_duration,
232                                   base::TimeDelta::FromMilliseconds(1),
233                                   base::TimeDelta::FromMinutes(10),
234                                   100);
235      } else {
236        UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency_IPv6_Raceable",
237                                   connect_duration,
238                                   base::TimeDelta::FromMilliseconds(1),
239                                   base::TimeDelta::FromMinutes(10),
240                                   100);
241      }
242    }
243    set_socket(transport_socket_.release());
244    fallback_timer_.Stop();
245  } else {
246    // Be a bit paranoid and kill off the fallback members to prevent reuse.
247    fallback_transport_socket_.reset();
248    fallback_addresses_.reset();
249  }
250
251  return result;
252}
253
254void TransportConnectJob::DoIPv6FallbackTransportConnect() {
255  // The timer should only fire while we're waiting for the main connect to
256  // succeed.
257  if (next_state_ != STATE_TRANSPORT_CONNECT_COMPLETE) {
258    NOTREACHED();
259    return;
260  }
261
262  DCHECK(!fallback_transport_socket_.get());
263  DCHECK(!fallback_addresses_.get());
264
265  fallback_addresses_.reset(new AddressList(addresses_));
266  MakeAddressListStartWithIPv4(fallback_addresses_.get());
267  fallback_transport_socket_.reset(
268      client_socket_factory_->CreateTransportClientSocket(
269          *fallback_addresses_, net_log().net_log(), net_log().source()));
270  fallback_connect_start_time_ = base::TimeTicks::Now();
271  int rv = fallback_transport_socket_->Connect(
272      base::Bind(
273          &TransportConnectJob::DoIPv6FallbackTransportConnectComplete,
274          base::Unretained(this)));
275  if (rv != ERR_IO_PENDING)
276    DoIPv6FallbackTransportConnectComplete(rv);
277}
278
279void TransportConnectJob::DoIPv6FallbackTransportConnectComplete(int result) {
280  // This should only happen when we're waiting for the main connect to succeed.
281  if (next_state_ != STATE_TRANSPORT_CONNECT_COMPLETE) {
282    NOTREACHED();
283    return;
284  }
285
286  DCHECK_NE(ERR_IO_PENDING, result);
287  DCHECK(fallback_transport_socket_.get());
288  DCHECK(fallback_addresses_.get());
289
290  if (result == OK) {
291    DCHECK(fallback_connect_start_time_ != base::TimeTicks());
292    DCHECK(start_time_ != base::TimeTicks());
293    base::TimeTicks now = base::TimeTicks::Now();
294    base::TimeDelta total_duration = now - start_time_;
295    UMA_HISTOGRAM_CUSTOM_TIMES(
296        "Net.DNS_Resolution_And_TCP_Connection_Latency2",
297        total_duration,
298        base::TimeDelta::FromMilliseconds(1),
299        base::TimeDelta::FromMinutes(10),
300        100);
301
302    base::TimeDelta connect_duration = now - fallback_connect_start_time_;
303    UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency",
304        connect_duration,
305        base::TimeDelta::FromMilliseconds(1),
306        base::TimeDelta::FromMinutes(10),
307        100);
308
309    UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency_IPv4_Wins_Race",
310        connect_duration,
311        base::TimeDelta::FromMilliseconds(1),
312        base::TimeDelta::FromMinutes(10),
313        100);
314    set_socket(fallback_transport_socket_.release());
315    next_state_ = STATE_NONE;
316    transport_socket_.reset();
317  } else {
318    // Be a bit paranoid and kill off the fallback members to prevent reuse.
319    fallback_transport_socket_.reset();
320    fallback_addresses_.reset();
321  }
322  NotifyDelegateOfCompletion(result);  // Deletes |this|
323}
324
325int TransportConnectJob::ConnectInternal() {
326  next_state_ = STATE_RESOLVE_HOST;
327  start_time_ = base::TimeTicks::Now();
328  return DoLoop(OK);
329}
330
331ConnectJob*
332    TransportClientSocketPool::TransportConnectJobFactory::NewConnectJob(
333    const std::string& group_name,
334    const PoolBase::Request& request,
335    ConnectJob::Delegate* delegate) const {
336  return new TransportConnectJob(group_name,
337                                 request.params(),
338                                 ConnectionTimeout(),
339                                 client_socket_factory_,
340                                 host_resolver_,
341                                 delegate,
342                                 net_log_);
343}
344
345base::TimeDelta
346    TransportClientSocketPool::TransportConnectJobFactory::ConnectionTimeout()
347    const {
348  return base::TimeDelta::FromSeconds(kTransportConnectJobTimeoutInSeconds);
349}
350
351TransportClientSocketPool::TransportClientSocketPool(
352    int max_sockets,
353    int max_sockets_per_group,
354    ClientSocketPoolHistograms* histograms,
355    HostResolver* host_resolver,
356    ClientSocketFactory* client_socket_factory,
357    NetLog* net_log)
358    : base_(max_sockets, max_sockets_per_group, histograms,
359            ClientSocketPool::unused_idle_socket_timeout(),
360            ClientSocketPool::used_idle_socket_timeout(),
361            new TransportConnectJobFactory(client_socket_factory,
362                                     host_resolver, net_log)) {
363  base_.EnableConnectBackupJobs();
364}
365
366TransportClientSocketPool::~TransportClientSocketPool() {}
367
368int TransportClientSocketPool::RequestSocket(
369    const std::string& group_name,
370    const void* params,
371    RequestPriority priority,
372    ClientSocketHandle* handle,
373    const CompletionCallback& callback,
374    const BoundNetLog& net_log) {
375  const scoped_refptr<TransportSocketParams>* casted_params =
376      static_cast<const scoped_refptr<TransportSocketParams>*>(params);
377
378  if (net_log.IsLoggingAllEvents()) {
379    // TODO(eroman): Split out the host and port parameters.
380    net_log.AddEvent(
381        NetLog::TYPE_TCP_CLIENT_SOCKET_POOL_REQUESTED_SOCKET,
382        CreateNetLogHostPortPairCallback(
383            &casted_params->get()->destination().host_port_pair()));
384  }
385
386  return base_.RequestSocket(group_name, *casted_params, priority, handle,
387                             callback, net_log);
388}
389
390void TransportClientSocketPool::RequestSockets(
391    const std::string& group_name,
392    const void* params,
393    int num_sockets,
394    const BoundNetLog& net_log) {
395  const scoped_refptr<TransportSocketParams>* casted_params =
396      static_cast<const scoped_refptr<TransportSocketParams>*>(params);
397
398  if (net_log.IsLoggingAllEvents()) {
399    // TODO(eroman): Split out the host and port parameters.
400    net_log.AddEvent(
401        NetLog::TYPE_TCP_CLIENT_SOCKET_POOL_REQUESTED_SOCKETS,
402        CreateNetLogHostPortPairCallback(
403            &casted_params->get()->destination().host_port_pair()));
404  }
405
406  base_.RequestSockets(group_name, *casted_params, num_sockets, net_log);
407}
408
409void TransportClientSocketPool::CancelRequest(
410    const std::string& group_name,
411    ClientSocketHandle* handle) {
412  base_.CancelRequest(group_name, handle);
413}
414
415void TransportClientSocketPool::ReleaseSocket(
416    const std::string& group_name,
417    StreamSocket* socket,
418    int id) {
419  base_.ReleaseSocket(group_name, socket, id);
420}
421
422void TransportClientSocketPool::Flush() {
423  base_.Flush();
424}
425
426bool TransportClientSocketPool::IsStalled() const {
427  return base_.IsStalled();
428}
429
430void TransportClientSocketPool::CloseIdleSockets() {
431  base_.CloseIdleSockets();
432}
433
434int TransportClientSocketPool::IdleSocketCount() const {
435  return base_.idle_socket_count();
436}
437
438int TransportClientSocketPool::IdleSocketCountInGroup(
439    const std::string& group_name) const {
440  return base_.IdleSocketCountInGroup(group_name);
441}
442
443LoadState TransportClientSocketPool::GetLoadState(
444    const std::string& group_name, const ClientSocketHandle* handle) const {
445  return base_.GetLoadState(group_name, handle);
446}
447
448void TransportClientSocketPool::AddLayeredPool(LayeredPool* layered_pool) {
449  base_.AddLayeredPool(layered_pool);
450}
451
452void TransportClientSocketPool::RemoveLayeredPool(LayeredPool* layered_pool) {
453  base_.RemoveLayeredPool(layered_pool);
454}
455
456DictionaryValue* TransportClientSocketPool::GetInfoAsValue(
457    const std::string& name,
458    const std::string& type,
459    bool include_nested_pools) const {
460  return base_.GetInfoAsValue(name, type);
461}
462
463base::TimeDelta TransportClientSocketPool::ConnectionTimeout() const {
464  return base_.ConnectionTimeout();
465}
466
467ClientSocketPoolHistograms* TransportClientSocketPool::histograms() const {
468  return base_.histograms();
469}
470
471}  // namespace net
472