Index: src/net/socket/tcp_client_socket_pool.cc |
=================================================================== |
--- src/net/socket/tcp_client_socket_pool.cc (revision 85857) |
+++ src/net/socket/tcp_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,9 +23,45 @@ |
namespace net { |
-TCPSocketParams::TCPSocketParams(const HostPortPair& host_port_pair, |
- RequestPriority priority, const GURL& referrer, |
- bool disable_resolver_cache) |
+// 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 TCPConnectJob::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 |
+ |
+TCPSocketParams::TCPSocketParams( |
+ const HostPortPair& host_port_pair, |
+ RequestPriority priority, |
+ const GURL& referrer, |
+ bool disable_resolver_cache) |
: destination_(host_port_pair) { |
Initialize(priority, referrer, disable_resolver_cache); |
} |
@@ -76,7 +114,11 @@ |
ALLOW_THIS_IN_INITIALIZER_LIST( |
callback_(this, |
&TCPConnectJob::OnIOComplete)), |
- resolver_(host_resolver) {} |
+ resolver_(host_resolver), |
+ ALLOW_THIS_IN_INITIALIZER_LIST( |
+ fallback_callback_( |
+ this, |
+ &TCPConnectJob::DoIPv6FallbackTCPConnectComplete)) {} |
TCPConnectJob::~TCPConnectJob() { |
// We don't worry about cancelling the host resolution and TCP connect, since |
@@ -97,6 +139,38 @@ |
} |
} |
+// static |
+void TCPConnectJob::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 TCPConnectJob::OnIOComplete(int result) { |
int rv = DoLoop(result); |
if (rv != ERR_IO_PENDING) |
@@ -149,14 +223,22 @@ |
int TCPConnectJob::DoTCPConnect() { |
next_state_ = STATE_TCP_CONNECT_COMPLETE; |
- set_socket(client_socket_factory_->CreateTCPClientSocket( |
+ transport_socket_.reset(client_socket_factory_->CreateTCPClientSocket( |
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, &TCPConnectJob::DoIPv6FallbackTCPConnect); |
+ } |
+ return rv; |
} |
int TCPConnectJob::DoTCPConnectComplete(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(); |
@@ -174,14 +256,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 TCPConnectJob::DoIPv6FallbackTCPConnect() { |
+ // The timer should only fire while we're waiting for the main connect to |
+ // succeed. |
+ if (next_state_ != STATE_TCP_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_->CreateTCPClientSocket( |
+ *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) |
+ DoIPv6FallbackTCPConnectComplete(rv); |
+} |
+ |
+void TCPConnectJob::DoIPv6FallbackTCPConnectComplete(int result) { |
+ // This should only happen when we're waiting for the main connect to succeed. |
+ if (next_state_ != STATE_TCP_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 TCPConnectJob::ConnectInternal() { |
next_state_ = STATE_RESOLVE_HOST; |
start_time_ = base::TimeTicks::Now(); |