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