Index: net/socket/transport_client_socket_pool.cc |
=================================================================== |
--- net/socket/transport_client_socket_pool.cc (revision 85841) |
+++ net/socket/transport_client_socket_pool.cc (working copy) |
@@ -10,8 +10,10 @@ |
#include "base/metrics/histogram.h" |
#include "base/string_util.h" |
#include "base/time.h" |
+#include "net/base/ip_endpoint.h" |
#include "net/base/net_log.h" |
#include "net/base/net_errors.h" |
+#include "net/base/sys_addrinfo.h" |
#include "net/socket/client_socket_factory.h" |
#include "net/socket/client_socket_handle.h" |
#include "net/socket/client_socket_pool_base.h" |
@@ -21,10 +23,46 @@ |
namespace net { |
-TransportSocketParams::TransportSocketParams(const HostPortPair& host_port_pair, |
- RequestPriority priority, const GURL& referrer, |
- bool disable_resolver_cache, |
- bool ignore_limits) |
+// TODO(willchan): Base this off RTT instead of statically setting it. Note we |
+// choose a timeout that is different from the backup connect job timer so they |
+// don't synchronize. |
+const int TransportConnectJob::kIPv6FallbackTimerInMs = 300; |
+ |
+namespace { |
+ |
+bool AddressListStartsWithIPv6AndHasAnIPv4Addr(const AddressList& addrlist) { |
+ const struct addrinfo* ai = addrlist.head(); |
+ if (ai->ai_family != AF_INET6) |
+ return false; |
+ |
+ ai = ai->ai_next; |
+ while (ai) { |
+ if (ai->ai_family != AF_INET6) |
+ return true; |
+ ai = ai->ai_next; |
+ } |
+ |
+ return false; |
+} |
+ |
+bool AddressListOnlyContainsIPv6Addresses(const AddressList& addrlist) { |
+ DCHECK(addrlist.head()); |
+ for (const struct addrinfo* ai = addrlist.head(); ai; ai = ai->ai_next) { |
+ if (ai->ai_family != AF_INET6) |
+ return false; |
+ } |
+ |
+ return true; |
+} |
+ |
+} // namespace |
+ |
+TransportSocketParams::TransportSocketParams( |
+ const HostPortPair& host_port_pair, |
+ RequestPriority priority, |
+ const GURL& referrer, |
+ bool disable_resolver_cache, |
+ bool ignore_limits) |
: destination_(host_port_pair), ignore_limits_(ignore_limits) { |
Initialize(priority, referrer, disable_resolver_cache); |
} |
@@ -32,8 +70,8 @@ |
TransportSocketParams::~TransportSocketParams() {} |
void TransportSocketParams::Initialize(RequestPriority priority, |
- const GURL& referrer, |
- bool disable_resolver_cache) { |
+ const GURL& referrer, |
+ bool disable_resolver_cache) { |
// The referrer is used by the DNS prefetch system to correlate resolutions |
// with the page that triggered them. It doesn't impact the actual addresses |
// that we resolve to. |
@@ -69,7 +107,11 @@ |
ALLOW_THIS_IN_INITIALIZER_LIST( |
callback_(this, |
&TransportConnectJob::OnIOComplete)), |
- resolver_(host_resolver) {} |
+ resolver_(host_resolver), |
+ ALLOW_THIS_IN_INITIALIZER_LIST( |
+ fallback_callback_( |
+ this, |
+ &TransportConnectJob::DoIPv6FallbackTransportConnectComplete)) {} |
TransportConnectJob::~TransportConnectJob() { |
// We don't worry about cancelling the host resolution and TCP connect, since |
@@ -90,6 +132,38 @@ |
} |
} |
+// static |
+void TransportConnectJob::MakeAddrListStartWithIPv4(AddressList* addrlist) { |
+ if (addrlist->head()->ai_family != AF_INET6) |
+ return; |
+ bool has_ipv4 = false; |
+ for (const struct addrinfo* ai = addrlist->head(); ai; ai = ai->ai_next) { |
+ if (ai->ai_family != AF_INET6) { |
+ has_ipv4 = true; |
+ break; |
+ } |
+ } |
+ if (!has_ipv4) |
+ return; |
+ |
+ struct addrinfo* head = CreateCopyOfAddrinfo(addrlist->head(), true); |
+ struct addrinfo* tail = head; |
+ while (tail->ai_next) |
+ tail = tail->ai_next; |
+ char* canonname = head->ai_canonname; |
+ head->ai_canonname = NULL; |
+ while (head->ai_family == AF_INET6) { |
+ tail->ai_next = head; |
+ tail = head; |
+ head = head->ai_next; |
+ tail->ai_next = NULL; |
+ } |
+ head->ai_canonname = canonname; |
+ |
+ addrlist->Copy(head, true); |
+ FreeCopyOfAddrinfo(head); |
+} |
+ |
void TransportConnectJob::OnIOComplete(int result) { |
int rv = DoLoop(result); |
if (rv != ERR_IO_PENDING) |
@@ -142,14 +216,22 @@ |
int TransportConnectJob::DoTransportConnect() { |
next_state_ = STATE_TRANSPORT_CONNECT_COMPLETE; |
- set_socket(client_socket_factory_->CreateTransportClientSocket( |
+ transport_socket_.reset(client_socket_factory_->CreateTransportClientSocket( |
addresses_, net_log().net_log(), net_log().source())); |
connect_start_time_ = base::TimeTicks::Now(); |
- return socket()->Connect(&callback_); |
+ int rv = transport_socket_->Connect(&callback_); |
+ if (rv == ERR_IO_PENDING && |
+ AddressListStartsWithIPv6AndHasAnIPv4Addr(addresses_)) { |
+ fallback_timer_.Start( |
+ base::TimeDelta::FromMilliseconds(kIPv6FallbackTimerInMs), |
+ this, &TransportConnectJob::DoIPv6FallbackTransportConnect); |
+ } |
+ return rv; |
} |
int TransportConnectJob::DoTransportConnectComplete(int result) { |
if (result == OK) { |
+ bool is_ipv4 = addresses_.head()->ai_family != AF_INET6; |
DCHECK(connect_start_time_ != base::TimeTicks()); |
DCHECK(start_time_ != base::TimeTicks()); |
base::TimeTicks now = base::TimeTicks::Now(); |
@@ -167,14 +249,107 @@ |
base::TimeDelta::FromMilliseconds(1), |
base::TimeDelta::FromMinutes(10), |
100); |
+ |
+ if (is_ipv4) { |
+ UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency_IPv4_No_Race", |
+ connect_duration, |
+ base::TimeDelta::FromMilliseconds(1), |
+ base::TimeDelta::FromMinutes(10), |
+ 100); |
+ } else { |
+ if (AddressListOnlyContainsIPv6Addresses(addresses_)) { |
+ UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency_IPv6_Solo", |
+ connect_duration, |
+ base::TimeDelta::FromMilliseconds(1), |
+ base::TimeDelta::FromMinutes(10), |
+ 100); |
+ } else { |
+ UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency_IPv6_Raceable", |
+ connect_duration, |
+ base::TimeDelta::FromMilliseconds(1), |
+ base::TimeDelta::FromMinutes(10), |
+ 100); |
+ } |
+ } |
+ set_socket(transport_socket_.release()); |
+ fallback_timer_.Stop(); |
} else { |
- // Delete the socket on error. |
- set_socket(NULL); |
+ // Be a bit paranoid and kill off the fallback members to prevent reuse. |
+ fallback_transport_socket_.reset(); |
+ fallback_addresses_.reset(); |
} |
return result; |
} |
+void TransportConnectJob::DoIPv6FallbackTransportConnect() { |
+ // The timer should only fire while we're waiting for the main connect to |
+ // succeed. |
+ if (next_state_ != STATE_TRANSPORT_CONNECT_COMPLETE) { |
+ NOTREACHED(); |
+ return; |
+ } |
+ |
+ DCHECK(!fallback_transport_socket_.get()); |
+ DCHECK(!fallback_addresses_.get()); |
+ |
+ fallback_addresses_.reset(new AddressList(addresses_)); |
+ MakeAddrListStartWithIPv4(fallback_addresses_.get()); |
+ fallback_transport_socket_.reset( |
+ client_socket_factory_->CreateTransportClientSocket( |
+ *fallback_addresses_, net_log().net_log(), net_log().source())); |
+ fallback_connect_start_time_ = base::TimeTicks::Now(); |
+ int rv = fallback_transport_socket_->Connect(&fallback_callback_); |
+ if (rv != ERR_IO_PENDING) |
+ DoIPv6FallbackTransportConnectComplete(rv); |
+} |
+ |
+void TransportConnectJob::DoIPv6FallbackTransportConnectComplete(int result) { |
+ // This should only happen when we're waiting for the main connect to succeed. |
+ if (next_state_ != STATE_TRANSPORT_CONNECT_COMPLETE) { |
+ NOTREACHED(); |
+ return; |
+ } |
+ |
+ DCHECK_NE(ERR_IO_PENDING, result); |
+ DCHECK(fallback_transport_socket_.get()); |
+ DCHECK(fallback_addresses_.get()); |
+ |
+ if (result == OK) { |
+ DCHECK(fallback_connect_start_time_ != base::TimeTicks()); |
+ DCHECK(start_time_ != base::TimeTicks()); |
+ base::TimeTicks now = base::TimeTicks::Now(); |
+ base::TimeDelta total_duration = now - start_time_; |
+ UMA_HISTOGRAM_CUSTOM_TIMES( |
+ "Net.DNS_Resolution_And_TCP_Connection_Latency2", |
+ total_duration, |
+ base::TimeDelta::FromMilliseconds(1), |
+ base::TimeDelta::FromMinutes(10), |
+ 100); |
+ |
+ base::TimeDelta connect_duration = now - fallback_connect_start_time_; |
+ UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency", |
+ connect_duration, |
+ base::TimeDelta::FromMilliseconds(1), |
+ base::TimeDelta::FromMinutes(10), |
+ 100); |
+ |
+ UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency_IPv4_Wins_Race", |
+ connect_duration, |
+ base::TimeDelta::FromMilliseconds(1), |
+ base::TimeDelta::FromMinutes(10), |
+ 100); |
+ set_socket(fallback_transport_socket_.release()); |
+ next_state_ = STATE_NONE; |
+ transport_socket_.reset(); |
+ } else { |
+ // Be a bit paranoid and kill off the fallback members to prevent reuse. |
+ fallback_transport_socket_.reset(); |
+ fallback_addresses_.reset(); |
+ } |
+ NotifyDelegateOfCompletion(result); // Deletes |this| |
+} |
+ |
int TransportConnectJob::ConnectInternal() { |
next_state_ = STATE_RESOLVE_HOST; |
start_time_ = base::TimeTicks::Now(); |