bug 621558 - Disable IPv6 for the 300ms-delayed backup connection to improve
authorChristian Biesinger <cbiesinger@gmail.com>
Mon, 04 Jul 2011 11:47:09 +0200
changeset 72527 06815ff84678e97420eca4890f28fe517396a548
parent 72526 df4ec91d0b0aea3801cc08b088f264f4b30f2c84
child 72541 fb1291a31b54914cd97cc5a8f9e8413c4d5fa921
push id209
push userbzbarsky@mozilla.com
push dateTue, 05 Jul 2011 17:42:16 +0000
treeherdermozilla-aurora@cc6e30cce8af [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs621558
milestone7.0a1
bug 621558 - Disable IPv6 for the 300ms-delayed backup connection to improve the user experience of people with broken IPv6 connectivity (i.e. implement Chrome's "Happy Eyeballs" strategy) r=mcmanus
netwerk/base/public/nsISocketTransport.idl
netwerk/base/src/nsSocketTransport2.cpp
netwerk/dns/nsDNSService2.cpp
netwerk/dns/nsDNSService2.h
netwerk/dns/nsIDNSService.idl
netwerk/protocol/http/nsHttpConnectionMgr.cpp
netwerk/protocol/http/nsHttpConnectionMgr.h
--- a/netwerk/base/public/nsISocketTransport.idl
+++ b/netwerk/base/public/nsISocketTransport.idl
@@ -133,17 +133,17 @@ interface nsISocketTransport : nsITransp
     const unsigned long STATUS_CONNECTING_TO  = 0x804b0007;
     const unsigned long STATUS_CONNECTED_TO   = 0x804b0004;
     const unsigned long STATUS_SENDING_TO     = 0x804b0005;
     const unsigned long STATUS_WAITING_FOR    = 0x804b000a;
     const unsigned long STATUS_RECEIVING_FROM = 0x804b0006;
 
     /**
      * connectionFlags is a bitmask that can be used to modify underlying 
-     * behavior of the socket connection.
+     * behavior of the socket connection. See the flags below.
      */
     attribute unsigned long connectionFlags;
 
     /**
      * Values for the connectionFlags
      *
      * When making a new connection BYPASS_CACHE will force the Necko DNS 
      * cache entry to be refreshed with a new call to NSPR if it is set before
@@ -155,16 +155,22 @@ interface nsISocketTransport : nsITransp
      * When setting this flag, the socket will not apply any
      * credentials when establishing a connection. For example,
      * an SSL connection would not send any client-certificates
      * if this flag is set.
      */
     const unsigned long ANONYMOUS_CONNECT = (1 << 1);
 
     /**
+     * If set, we will skip all IPv6 addresses the host may have and only
+     * connect to IPv4 ones.
+     */
+    const unsigned long DISABLE_IPV6 = (1 << 2);
+
+    /**
      * Socket QoS/ToS markings. Valid values are IPTOS_DSCP_AFxx or
      * IPTOS_CLASS_CSx (or IPTOS_DSCP_EF, but currently no supported
      * services require expedited-forwarding).
      * Not setting this value will leave the socket with the default
      * ToS value, which on most systems if IPTOS_CLASS_CS0 (formerly
      * IPTOS_PREC_ROUTINE).
      */
     attribute octet QoSBits;
--- a/netwerk/base/src/nsSocketTransport2.cpp
+++ b/netwerk/base/src/nsSocketTransport2.cpp
@@ -937,16 +937,18 @@ nsSocketTransport::ResolveHost()
     nsCOMPtr<nsIDNSService> dns = do_GetService(kDNSServiceCID, &rv);
     if (NS_FAILED(rv)) return rv;
 
     mResolving = PR_TRUE;
 
     PRUint32 dnsFlags = 0;
     if (mConnectionFlags & nsSocketTransport::BYPASS_CACHE)
         dnsFlags = nsIDNSService::RESOLVE_BYPASS_CACHE;
+    if (mConnectionFlags & nsSocketTransport::DISABLE_IPV6)
+        dnsFlags |= nsIDNSService::RESOLVE_DISABLE_IPV6;
 
     SendStatus(STATUS_RESOLVING);
     rv = dns->AsyncResolve(SocketHost(), dnsFlags, this, nsnull,
                            getter_AddRefs(mDNSRequest));
     if (NS_SUCCEEDED(rv)) {
         SOCKET_LOG(("  advancing to STATE_RESOLVING\n"));
         mState = STATE_RESOLVING;
     }
--- a/netwerk/dns/nsDNSService2.cpp
+++ b/netwerk/dns/nsDNSService2.cpp
@@ -457,17 +457,17 @@ nsDNSService::AsyncResolve(const nsACStr
                                   NS_GET_IID(nsIDNSListener),
                                   listener,
                                   NS_PROXY_ASYNC | NS_PROXY_ALWAYS,
                                   getter_AddRefs(listenerProxy));
         if (NS_FAILED(rv)) return rv;
         listener = listenerProxy;
     }
 
-    PRUint16 af = GetAFForLookup(*hostPtr);
+    PRUint16 af = GetAFForLookup(*hostPtr, flags);
 
     nsDNSAsyncRequest *req =
             new nsDNSAsyncRequest(res, *hostPtr, listener, flags, af);
     if (!req)
         return NS_ERROR_OUT_OF_MEMORY;
     NS_ADDREF(*result = req);
 
     // addref for resolver; will be released when OnLookupComplete is called.
@@ -515,17 +515,17 @@ nsDNSService::Resolve(const nsACString &
     
     PRMonitor *mon = PR_NewMonitor();
     if (!mon)
         return NS_ERROR_OUT_OF_MEMORY;
 
     PR_EnterMonitor(mon);
     nsDNSSyncRequest syncReq(mon);
 
-    PRUint16 af = GetAFForLookup(*hostPtr);
+    PRUint16 af = GetAFForLookup(*hostPtr, flags);
 
     rv = res->ResolveHost(PromiseFlatCString(*hostPtr).get(), flags, af, &syncReq);
     if (NS_SUCCEEDED(rv)) {
         // wait for result
         while (!syncReq.mDone)
             PR_Wait(mon, PR_INTERVAL_NO_TIMEOUT);
 
         if (NS_FAILED(syncReq.mStatus))
@@ -575,19 +575,19 @@ nsDNSService::Observe(nsISupports *subje
     if (mResolver) {
         Shutdown();
     }
     Init();
     return NS_OK;
 }
 
 PRUint16
-nsDNSService::GetAFForLookup(const nsACString &host)
+nsDNSService::GetAFForLookup(const nsACString &host, PRUint32 flags)
 {
-    if (mDisableIPv6)
+    if (mDisableIPv6 || (flags & RESOLVE_DISABLE_IPV6))
         return PR_AF_INET;
 
     MutexAutoLock lock(mLock);
 
     PRUint16 af = PR_AF_UNSPEC;
 
     if (!mIPv4OnlyDomains.IsEmpty()) {
         const char *domain, *domainEnd, *end;
--- a/netwerk/dns/nsDNSService2.h
+++ b/netwerk/dns/nsDNSService2.h
@@ -50,17 +50,17 @@ public:
     NS_DECL_NSPIDNSSERVICE
     NS_DECL_NSIDNSSERVICE
     NS_DECL_NSIOBSERVER
 
     nsDNSService();
     ~nsDNSService();
 
 private:
-    PRUint16 GetAFForLookup(const nsACString &host);
+    PRUint16 GetAFForLookup(const nsACString &host, PRUint32 flags);
 
     nsRefPtr<nsHostResolver>  mResolver;
     nsCOMPtr<nsIIDNService>   mIDN;
 
     // mLock protects access to mResolver and mIPv4OnlyDomains
     mozilla::Mutex            mLock;
 
     // mIPv4OnlyDomains is a comma-separated list of domains for which only
--- a/netwerk/dns/nsIDNSService.idl
+++ b/netwerk/dns/nsIDNSService.idl
@@ -115,9 +115,14 @@ interface nsIDNSService : nsISupports
     const unsigned long RESOLVE_PRIORITY_MEDIUM = (1 << 2);
     const unsigned long RESOLVE_PRIORITY_LOW    = (1 << 3);
 
     /**
      * if set, indicates request is speculative. Speculative requests 
      * return errors if prefetching is disabled by configuration.
      */
     const unsigned long RESOLVE_SPECULATE = (1 << 4);
+
+    /**
+     * If set, only IPv4 addresses will be returned from resolve/asyncResolve.
+     */
+    const unsigned long RESOLVE_DISABLE_IPV6 = (1 << 5);
 };
--- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp
@@ -1286,17 +1286,18 @@ nsHttpConnectionMgr::nsHalfOpenSocket::~
         mEnt->mHalfOpens.RemoveElement(this);
     }
 }
 
 nsresult
 nsHttpConnectionMgr::
 nsHalfOpenSocket::SetupStreams(nsISocketTransport **transport,
                                nsIAsyncInputStream **instream,
-                               nsIAsyncOutputStream **outstream)
+                               nsIAsyncOutputStream **outstream,
+                               PRBool isBackup)
 {
     nsresult rv;
 
     const char* types[1];
     types[0] = (mEnt->mConnInfo->UsingSSL()) ?
         "ssl" : gHttpHandler->DefaultSocketType();
     PRUint32 typeCount = (types[0] != nsnull);
 
@@ -1315,16 +1316,24 @@ nsHalfOpenSocket::SetupStreams(nsISocket
     
     PRUint32 tmpFlags = 0;
     if (mTransaction->Caps() & NS_HTTP_REFRESH_DNS)
         tmpFlags = nsISocketTransport::BYPASS_CACHE;
 
     if (mTransaction->Caps() & NS_HTTP_LOAD_ANONYMOUS)
         tmpFlags |= nsISocketTransport::ANONYMOUS_CONNECT;
 
+    // For backup connections, we disable IPv6. That's because some users have
+    // broken IPv6 connectivity (leading to very long timeouts), and disabling
+    // IPv6 on the backup connection gives them a much better user experience
+    // with dual-stack hosts, though they still pay the 250ms delay for each new
+    // connection. This strategy is also known as "happy eyeballs".
+    if (isBackup)
+        tmpFlags |= nsISocketTransport::DISABLE_IPV6;
+
     socketTransport->SetConnectionFlags(tmpFlags);
 
     socketTransport->SetQoSBits(gHttpHandler->GetQoSBits());
 
     rv = socketTransport->SetEventSink(this, nsnull);
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = socketTransport->SetSecurityCallbacks(this);
@@ -1353,17 +1362,18 @@ nsHalfOpenSocket::SetupStreams(nsISocket
     return rv;
 }
 
 nsresult
 nsHttpConnectionMgr::nsHalfOpenSocket::SetupPrimaryStreams()
 {
     nsresult rv = SetupStreams(getter_AddRefs(mSocketTransport),
                                getter_AddRefs(mStreamIn),
-                               getter_AddRefs(mStreamOut));
+                               getter_AddRefs(mStreamOut),
+                               PR_FALSE);
     LOG(("nsHalfOpenSocket::SetupPrimaryStream [this=%p ent=%s rv=%x]",
          this, mEnt->mConnInfo->Host(), rv));
     if (NS_FAILED(rv)) {
         if (mStreamOut)
             mStreamOut->AsyncWait(nsnull, 0, 0, nsnull);
         mStreamOut = nsnull;
         mStreamIn = nsnull;
         mSocketTransport = nsnull;
@@ -1371,17 +1381,18 @@ nsHttpConnectionMgr::nsHalfOpenSocket::S
     return rv;
 }
 
 nsresult
 nsHttpConnectionMgr::nsHalfOpenSocket::SetupBackupStreams()
 {
     nsresult rv = SetupStreams(getter_AddRefs(mBackupTransport),
                                getter_AddRefs(mBackupStreamIn),
-                               getter_AddRefs(mBackupStreamOut));
+                               getter_AddRefs(mBackupStreamOut),
+                               PR_TRUE);
     LOG(("nsHalfOpenSocket::SetupBackupStream [this=%p ent=%s rv=%x]",
          this, mEnt->mConnInfo->Host(), rv));
     if (NS_FAILED(rv)) {
         if (mBackupStreamOut)
             mBackupStreamOut->AsyncWait(nsnull, 0, 0, nsnull);
         mBackupStreamOut = nsnull;
         mBackupStreamIn = nsnull;
         mBackupTransport = nsnull;
--- a/netwerk/protocol/http/nsHttpConnectionMgr.h
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.h
@@ -206,17 +206,18 @@ private:
         NS_DECL_NSITIMERCALLBACK
 
         nsHalfOpenSocket(nsConnectionEntry *ent,
                          nsHttpTransaction *trans);
         ~nsHalfOpenSocket();
         
         nsresult SetupStreams(nsISocketTransport **,
                               nsIAsyncInputStream **,
-                              nsIAsyncOutputStream **);
+                              nsIAsyncOutputStream **,
+                              PRBool isBackup);
         nsresult SetupPrimaryStreams();
         nsresult SetupBackupStreams();
         void     SetupBackupTimer();
         void     Abandon();
         
         nsHttpTransaction *Transaction() { return mTransaction; }
 
     private: