bug 792438 - part 2 block network image loads on head css,js r=honzab sr=bz
authorPatrick McManus <mcmanus@ducksong.com>
Tue, 04 Dec 2012 18:06:29 -0500
changeset 120542 3ecb7e3e91726eedc561ac326a3a1ef4eb556c34
parent 120541 85f941efa32e08ca4f14a8aad200653c6cccd9f4
child 120543 7e806928652670d3f359a725251b85b2f03073ac
push idunknown
push userunknown
push dateunknown
reviewershonzab, bz
bugs792438
milestone20.0a1
bug 792438 - part 2 block network image loads on head css,js r=honzab sr=bz
modules/libpref/src/init/all.js
netwerk/base/public/nsILoadGroup.idl
netwerk/base/src/nsLoadGroup.cpp
netwerk/base/src/nsLoadGroup.h
netwerk/protocol/http/nsHttp.h
netwerk/protocol/http/nsHttpChannel.cpp
netwerk/protocol/http/nsHttpChannel.h
netwerk/protocol/http/nsHttpConnectionMgr.cpp
netwerk/protocol/http/nsHttpConnectionMgr.h
netwerk/protocol/http/nsHttpHandler.cpp
netwerk/protocol/http/nsHttpHandler.h
netwerk/protocol/http/nsHttpTransaction.cpp
netwerk/protocol/http/nsHttpTransaction.h
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -905,16 +905,20 @@ pref("network.http.qos", 0);
 // to wait before trying a different connection. 0 means do not use a second
 // connection.
 pref("network.http.connection-retry-timeout", 250);
 
 // The number of seconds after sending initial SYN for an HTTP connection
 // to give up if the OS does not give up first
 pref("network.http.connection-timeout", 90);
 
+// The maximum number of current global half open sockets allowable
+// when starting a new speculative connection.
+pref("network.http.speculative-parallel-limit", 6);
+
 // Disable IPv6 for backup connections to workaround problems about broken
 // IPv6 connectivity.
 pref("network.http.fast-fallback-to-IPv4", true);
 
 // Try and use SPDY when using SSL
 pref("network.http.spdy.enabled", true);
 pref("network.http.spdy.enabled.v2", true);
 pref("network.http.spdy.enabled.v3", true);
--- a/netwerk/base/public/nsILoadGroup.idl
+++ b/netwerk/base/public/nsILoadGroup.idl
@@ -3,21 +3,22 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsIRequest.idl"
 
 interface nsISimpleEnumerator;
 interface nsIRequestObserver;
 interface nsIInterfaceRequestor;
+interface nsILoadGroupConnectionInfo;
 
 /**
  * A load group maintains a collection of nsIRequest objects. 
  */
-[scriptable, uuid(3de0a31c-feaf-400f-9f1e-4ef71f8b20cc)]
+[scriptable, uuid(19501006-46e3-4634-b97d-26eff894b4d3)]
 interface nsILoadGroup : nsIRequest
 {
     /**
      * The group observer is notified when requests are added to and removed
      * from this load group.  The groupObserver is weak referenced.
      */
     attribute nsIRequestObserver groupObserver;
 
@@ -66,9 +67,41 @@ interface nsILoadGroup : nsIRequest
      * LOAD_BACKGROUND bit set).
      */
     readonly attribute unsigned long activeCount;
 
     /**
      * Notification callbacks for the load group.
      */
     attribute nsIInterfaceRequestor notificationCallbacks;
+
+    /**
+     * Connection information for managing things like js/css
+     * connection blocking, and per-tab connection grouping
+     */
+    readonly attribute nsILoadGroupConnectionInfo connectionInfo;
 };
+
+/**
+ * Used to maintain state about the connections of a load group and
+ * how they interact with blocking items like HEAD css/js loads.
+ */
+[uuid(d1f9f18e-3d85-473a-ad58-a2367d7cdb2a)]
+interface nsILoadGroupConnectionInfo : nsISupports
+{
+    /**
+     * Number of active blocking transactions associated with this load group
+     */
+    readonly attribute unsigned long blockingTransactionCount;
+
+    /**
+     * Increase the number of active blocking transactions associated
+     * with this load group by one.
+     */
+    void addBlockingTransaction();
+
+    /**
+     * Decrease the number of active blocking transactions associated
+     * with this load group by one. The return value is the number of remaining
+     * blockers.
+     */
+    unsigned long removeBlockingTransaction();
+};
--- a/netwerk/base/src/nsLoadGroup.cpp
+++ b/netwerk/base/src/nsLoadGroup.cpp
@@ -141,40 +141,16 @@ nsLoadGroup::~nsLoadGroup()
     }
 
     mDefaultLoadRequest = 0;
 
     LOG(("LOADGROUP [%x]: Destroyed.\n", this));
 }
 
 
-nsresult nsLoadGroup::Init()
-{
-    static PLDHashTableOps hash_table_ops =
-    {
-        PL_DHashAllocTable,
-        PL_DHashFreeTable,
-        PL_DHashVoidPtrKeyStub,
-        RequestHashMatchEntry,
-        PL_DHashMoveEntryStub,
-        RequestHashClearEntry,
-        PL_DHashFinalizeStub,
-        RequestHashInitEntry
-    };
-
-    if (!PL_DHashTableInit(&mRequests, &hash_table_ops, nullptr,
-                           sizeof(RequestMapEntry), 16)) {
-        mRequests.ops = nullptr;
-
-        return NS_ERROR_OUT_OF_MEMORY;
-    }
-
-    return NS_OK;
-}
-
 ////////////////////////////////////////////////////////////////////////////////
 // nsISupports methods:
 
 NS_IMPL_AGGREGATED(nsLoadGroup)
 NS_INTERFACE_MAP_BEGIN_AGGREGATED(nsLoadGroup)
     NS_INTERFACE_MAP_ENTRY(nsILoadGroup)
     NS_INTERFACE_MAP_ENTRY(nsIRequest)
     NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
@@ -786,16 +762,25 @@ nsLoadGroup::GetNotificationCallbacks(ns
 
 NS_IMETHODIMP
 nsLoadGroup::SetNotificationCallbacks(nsIInterfaceRequestor *aCallbacks)
 {
     mCallbacks = aCallbacks;
     return NS_OK;
 }
 
+NS_IMETHODIMP
+nsLoadGroup::GetConnectionInfo(nsILoadGroupConnectionInfo **aCI)
+{
+    NS_ENSURE_ARG_POINTER(aCI);
+    *aCI = mConnectionInfo;
+    NS_IF_ADDREF(*aCI);
+    return NS_OK;
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // nsISupportsPriority methods:
 
 NS_IMETHODIMP
 nsLoadGroup::GetPriority(int32_t *aValue)
 {
     *aValue = mPriority;
     return NS_OK;
@@ -999,8 +984,78 @@ nsresult nsLoadGroup::MergeLoadFlags(nsI
                             VALIDATE_NEVER));
 
     if (flags != oldFlags)
         rv = aRequest->SetLoadFlags(flags);
 
     outFlags = flags;
     return rv;
 }
+
+// nsLoadGroupConnectionInfo
+
+class nsLoadGroupConnectionInfo MOZ_FINAL : public nsILoadGroupConnectionInfo
+{
+public:
+    NS_DECL_ISUPPORTS
+    NS_DECL_NSILOADGROUPCONNECTIONINFO
+
+    nsLoadGroupConnectionInfo();
+private:
+    int32_t       mBlockingTransactionCount; // signed for PR_ATOMIC_*
+};
+
+NS_IMPL_THREADSAFE_ISUPPORTS1(nsLoadGroupConnectionInfo, nsILoadGroupConnectionInfo)
+
+nsLoadGroupConnectionInfo::nsLoadGroupConnectionInfo()
+    : mBlockingTransactionCount(0)
+{
+}
+
+NS_IMETHODIMP
+nsLoadGroupConnectionInfo::GetBlockingTransactionCount(uint32_t *aBlockingTransactionCount)
+{
+    NS_ENSURE_ARG_POINTER(aBlockingTransactionCount);
+    *aBlockingTransactionCount = static_cast<uint32_t>(mBlockingTransactionCount);
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroupConnectionInfo::AddBlockingTransaction()
+{
+    PR_ATOMIC_INCREMENT(&mBlockingTransactionCount);
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLoadGroupConnectionInfo::RemoveBlockingTransaction(uint32_t *_retval)
+{
+    NS_ENSURE_ARG_POINTER(_retval);
+    *_retval =
+        static_cast<uint32_t>(PR_ATOMIC_DECREMENT(&mBlockingTransactionCount));
+    return NS_OK;
+}
+
+nsresult nsLoadGroup::Init()
+{
+    static PLDHashTableOps hash_table_ops =
+    {
+        PL_DHashAllocTable,
+        PL_DHashFreeTable,
+        PL_DHashVoidPtrKeyStub,
+        RequestHashMatchEntry,
+        PL_DHashMoveEntryStub,
+        RequestHashClearEntry,
+        PL_DHashFinalizeStub,
+        RequestHashInitEntry
+    };
+
+    if (!PL_DHashTableInit(&mRequests, &hash_table_ops, nullptr,
+                           sizeof(RequestMapEntry), 16)) {
+        mRequests.ops = nullptr;
+
+        return NS_ERROR_OUT_OF_MEMORY;
+    }
+
+    mConnectionInfo = new nsLoadGroupConnectionInfo();
+
+    return NS_OK;
+}
--- a/netwerk/base/src/nsLoadGroup.h
+++ b/netwerk/base/src/nsLoadGroup.h
@@ -16,16 +16,17 @@
 #include "nsIInterfaceRequestor.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsISupportsPriority.h"
 #include "nsITimedChannel.h"
 #include "pldhash.h"
 #include "mozilla/TimeStamp.h"
 
 class  nsISupportsArray;
+class  nsILoadGroupConnectionInfo;
 
 class nsLoadGroup : public nsILoadGroup,
                     public nsISupportsPriority,
                     public nsSupportsWeakReference
 {
 public:
     NS_DECL_AGGREGATED
     
@@ -59,16 +60,17 @@ private:
                                 bool defaultRequest);
 
 protected:
     uint32_t                        mForegroundCount;
     uint32_t                        mLoadFlags;
 
     nsCOMPtr<nsILoadGroup>          mLoadGroup; // load groups can contain load groups
     nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+    nsCOMPtr<nsILoadGroupConnectionInfo> mConnectionInfo;
 
     nsCOMPtr<nsIRequest>            mDefaultLoadRequest;
     PLDHashTable                    mRequests;
 
     nsWeakPtr                       mObserver;
     
     nsresult                        mStatus;
     int32_t                         mPriority;
--- a/netwerk/protocol/http/nsHttp.h
+++ b/netwerk/protocol/http/nsHttp.h
@@ -88,23 +88,29 @@ typedef uint8_t nsHttpVersion;
 
 // a transaction with this caps flag will not pass SSL client-certificates
 // to the server (see bug #466080), but is may also be used for other things
 #define NS_HTTP_LOAD_ANONYMOUS       (1<<4)
 
 // a transaction with this caps flag keeps timing information
 #define NS_HTTP_TIMING_ENABLED       (1<<5)
 
-// (1<<6) is unused
+// a transaction with this flag blocks the initiation of other transactons
+// in the same load group until it is complete
+#define NS_HTTP_LOAD_AS_BLOCKING     (1<<6)
 
 // Disallow the use of the SPDY protocol. This is meant for the contexts
 // such as HTTP upgrade which are nonsensical for SPDY, it is not the
 // SPDY configuration variable.
 #define NS_HTTP_DISALLOW_SPDY        (1<<7)
 
+// a transaction with this flag loads without respect to whether the load
+// group is currently blocking on some resources
+#define NS_HTTP_LOAD_UNBLOCKED       (1<<8)
+
 //-----------------------------------------------------------------------------
 // some default values
 //-----------------------------------------------------------------------------
 
 #define NS_HTTP_DEFAULT_PORT  80
 #define NS_HTTPS_DEFAULT_PORT 443
 
 #define NS_HTTP_HEADER_SEPS ", \t"
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -676,16 +676,41 @@ nsHttpChannel::ContinueHandleAsyncFallba
     mIsPending = false;
 
     if (mLoadGroup)
         mLoadGroup->RemoveRequest(this, nullptr, mStatus);
 
     return rv;
 }
 
+void
+nsHttpChannel::SetupTransactionLoadGroupInfo()
+{
+    // Find the loadgroup at the end of the chain in order
+    // to make sure all channels derived from the load group
+    // use the same connection scope.
+    nsCOMPtr<nsILoadGroup> rootLoadGroup = mLoadGroup;
+    while (rootLoadGroup) {
+        nsCOMPtr<nsILoadGroup> tmp;
+        rootLoadGroup->GetLoadGroup(getter_AddRefs(tmp));
+        if (tmp)
+            rootLoadGroup.swap(tmp);
+        else
+            break;
+    }
+
+    // Set the load group connection scope on the transaction
+    if (rootLoadGroup) {
+        nsCOMPtr<nsILoadGroupConnectionInfo> ci;
+        rootLoadGroup->GetConnectionInfo(getter_AddRefs(ci));
+        if (ci)
+            mTransaction->SetLoadGroupConnectionInfo(ci);
+    }
+}
+
 nsresult
 nsHttpChannel::SetupTransaction()
 {
     LOG(("nsHttpChannel::SetupTransaction [this=%p]\n", this));
 
     NS_ENSURE_TRUE(!mTransaction, NS_ERROR_ALREADY_INITIALIZED);
 
     nsresult rv;
@@ -849,16 +874,18 @@ nsHttpChannel::SetupTransaction()
                             mUploadStream, mUploadStreamHasHeaders,
                             NS_GetCurrentThread(), callbacks, this,
                             getter_AddRefs(responseStream));
     if (NS_FAILED(rv)) {
         mTransaction = nullptr;
         return rv;
     }
 
+    SetupTransactionLoadGroupInfo();
+    
     rv = nsInputStreamPump::Create(getter_AddRefs(mTransactionPump),
                                    responseStream);
     return rv;
 }
 
 // NOTE: This function duplicates code from nsBaseChannel. This will go away
 // once HTTP uses nsBaseChannel (part of bug 312760)
 static void
@@ -4413,16 +4440,21 @@ nsHttpChannel::BeginConnect()
     //    trying to establish a keep-alive connection.
     if (mRequestHead.HasHeaderValue(nsHttp::Connection, "close"))
         mCaps &= ~(NS_HTTP_ALLOW_KEEPALIVE | NS_HTTP_ALLOW_PIPELINING);
     
     if ((mLoadFlags & VALIDATE_ALWAYS) || 
         (BYPASS_LOCAL_CACHE(mLoadFlags)))
         mCaps |= NS_HTTP_REFRESH_DNS;
 
+    if (mLoadAsBlocking)
+        mCaps |= NS_HTTP_LOAD_AS_BLOCKING;
+    if (mLoadUnblocked)
+        mCaps |= NS_HTTP_LOAD_UNBLOCKED;
+
     // Force-Reload should reset the persistent connection pool for this host
     if (mLoadFlags & LOAD_FRESH_CONNECTION) {
         // just the initial document resets the whole pool
         if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI)
             gHttpHandler->ConnMgr()->ClosePersistentConnections();
         // each sub resource gets a fresh connection
         mCaps &= ~(NS_HTTP_ALLOW_KEEPALIVE | NS_HTTP_ALLOW_PIPELINING);
     }
--- a/netwerk/protocol/http/nsHttpChannel.h
+++ b/netwerk/protocol/http/nsHttpChannel.h
@@ -155,16 +155,17 @@ private:
     typedef nsresult (nsHttpChannel::*nsContinueRedirectionFunc)(nsresult result);
 
     bool     RequestIsConditional();
     nsresult BeginConnect();
     nsresult Connect();
     nsresult ContinueConnect();
     void     SpeculativeConnect();
     nsresult SetupTransaction();
+    void     SetupTransactionLoadGroupInfo();
     nsresult CallOnStartRequest();
     nsresult ProcessResponse();
     nsresult ContinueProcessResponse(nsresult);
     nsresult ProcessNormal();
     nsresult ContinueProcessNormal(nsresult);
     nsresult ProcessNotModified();
     nsresult AsyncProcessRedirection(uint32_t httpStatus);
     nsresult ContinueProcessRedirection(nsresult);
--- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp
@@ -55,16 +55,17 @@ InsertTransactionSorted(nsTArray<nsHttpT
 nsHttpConnectionMgr::nsHttpConnectionMgr()
     : mReentrantMonitor("nsHttpConnectionMgr.mReentrantMonitor")
     , mMaxConns(0)
     , mMaxPersistConnsPerHost(0)
     , mMaxPersistConnsPerProxy(0)
     , mIsShuttingDown(false)
     , mNumActiveConns(0)
     , mNumIdleConns(0)
+    , mNumHalfOpenConns(0)
     , mTimeOfNextWakeUp(UINT64_MAX)
     , mTimeoutTickArmed(false)
 {
     LOG(("Creating nsHttpConnectionMgr @%x\n", this));
     mCT.Init();
     mAlternateProtocolHash.Init(16);
     mSpdyPreferredHash.Init();
 }
@@ -397,16 +398,23 @@ nsHttpConnectionMgr::ProcessPendingQ(nsH
 
     NS_ADDREF(ci);
     nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgProcessPendingQ, 0, ci);
     if (NS_FAILED(rv))
         NS_RELEASE(ci);
     return rv;
 }
 
+nsresult
+nsHttpConnectionMgr::ProcessPendingQ()
+{
+    LOG(("nsHttpConnectionMgr::ProcessPendingQ [All CI]\n"));
+    return PostEvent(&nsHttpConnectionMgr::OnMsgProcessPendingQ, 0, nullptr);
+}
+
 // Given a nsHttpConnectionInfo find the connection entry object that
 // contains either the nshttpconnection or nshttptransaction parameter.
 // Normally this is done by the hashkey lookup of connectioninfo,
 // but if spdy coalescing is in play it might be found in a redirected
 // entry
 nsHttpConnectionMgr::nsConnectionEntry *
 nsHttpConnectionMgr::LookupConnectionEntry(nsHttpConnectionInfo *ci,
                                            nsHttpConnection *conn,
@@ -719,22 +727,32 @@ nsHttpConnectionMgr::RemoveSpdyPreferred
 
 PLDHashOperator
 nsHttpConnectionMgr::ProcessOneTransactionCB(const nsACString &key,
                                              nsAutoPtr<nsConnectionEntry> &ent,
                                              void *closure)
 {
     nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure;
 
-    if (self->ProcessPendingQForEntry(ent))
+    if (self->ProcessPendingQForEntry(ent, false))
         return PL_DHASH_STOP;
 
     return PL_DHASH_NEXT;
 }
 
+PLDHashOperator
+nsHttpConnectionMgr::ProcessAllTransactionsCB(const nsACString &key,
+                                              nsAutoPtr<nsConnectionEntry> &ent,
+                                              void *closure)
+{
+    nsHttpConnectionMgr *self = (nsHttpConnectionMgr *) closure;
+    self->ProcessPendingQForEntry(ent, true);
+    return PL_DHASH_NEXT;
+}
+
 // If the global number of idle connections is preventing the opening of
 // new connections to a host without idle connections, then
 // close them regardless of their TTL
 PLDHashOperator
 nsHttpConnectionMgr::PurgeExcessIdleConnectionsCB(const nsACString &key,
                                                   nsAutoPtr<nsConnectionEntry> &ent,
                                                   void *closure)
 {
@@ -890,32 +908,33 @@ nsHttpConnectionMgr::ShutdownPassCB(cons
         ent->mHalfOpens[i]->Abandon();
 
     return PL_DHASH_REMOVE;
 }
 
 //-----------------------------------------------------------------------------
 
 bool
-nsHttpConnectionMgr::ProcessPendingQForEntry(nsConnectionEntry *ent)
+nsHttpConnectionMgr::ProcessPendingQForEntry(nsConnectionEntry *ent, bool considerAll)
 {
     NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
 
     LOG(("nsHttpConnectionMgr::ProcessPendingQForEntry [ci=%s]\n",
          ent->mConnInfo->HashKey().get()));
 
     ProcessSpdyPendingQ(ent);
 
     uint32_t count = ent->mPendingQ.Length();
     nsHttpTransaction *trans;
     nsresult rv;
     bool dispatchedSuccessfully = false;
 
-    // iterate the pending list until one is dispatched successfully. Keep
-    // iterating afterwards only until a transaction fails to dispatch.
+    // if !considerAll iterate the pending list until one is dispatched successfully.
+    // Keep iterating afterwards only until a transaction fails to dispatch.
+    // if considerAll == true then try and dispatch all items.
     for (uint32_t i = 0; i < count; ++i) {
         trans = ent->mPendingQ[i];
 
         // When this transaction has already established a half-open
         // connection, we want to prevent any duplicate half-open
         // connections from being established and bound to this
         // transaction. Allow only use of an idle persistent connection
         // (if found) for transactions referred by a half-open connection.
@@ -940,34 +959,34 @@ nsHttpConnectionMgr::ProcessPendingQForE
 
             // reset index and array length after RemoveElementAt()
             dispatchedSuccessfully = true;
             count = ent->mPendingQ.Length();
             --i;
             continue;
         }
 
-        if (dispatchedSuccessfully)
-            return true;
-
+        if (dispatchedSuccessfully && !considerAll)
+            break;
+    
         NS_ABORT_IF_FALSE(count == ent->mPendingQ.Length(),
                           "something mutated pending queue from "
                           "GetConnection()");
     }
-    return false;
+    return dispatchedSuccessfully;
 }
 
 bool
 nsHttpConnectionMgr::ProcessPendingQForEntry(nsHttpConnectionInfo *ci)
 {
     NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
 
     nsConnectionEntry *ent = mCT.Get(ci->HashKey());
     if (ent)
-        return ProcessPendingQForEntry(ent);
+        return ProcessPendingQForEntry(ent, false);
     return false;
 }
 
 bool
 nsHttpConnectionMgr::SupportsPipelining(nsHttpConnectionInfo *ci)
 {
     NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
 
@@ -1439,21 +1458,46 @@ nsHttpConnectionMgr::TryDispatchTransact
     // step 0
     // look for existing spdy connection - that's always best because it is
     // essentially pipelining without head of line blocking
 
     if (!(caps & NS_HTTP_DISALLOW_SPDY) && gHttpHandler->IsSpdyEnabled()) {
         nsRefPtr<nsHttpConnection> conn = GetSpdyPreferredConn(ent);
         if (conn) {
             LOG(("   dispatch to spdy: [conn=%x]\n", conn.get()));
+            trans->RemoveDispatchedAsBlocking();  /* just in case */
             DispatchTransaction(ent, trans, conn);
             return NS_OK;
         }
     }
 
+    // If this is not a blocking transaction and the loadgroup for it is
+    // currently processing one or more blocking transactions then we
+    // need to just leave it in the queue until those are complete unless it is
+    // explicitly marked as unblocked.
+    if (!(caps & NS_HTTP_LOAD_AS_BLOCKING)) {
+        if (!(caps & NS_HTTP_LOAD_UNBLOCKED)) {
+            nsILoadGroupConnectionInfo *loadGroupCI = trans->LoadGroupConnectionInfo();
+            if (loadGroupCI) {
+                uint32_t blockers = 0;
+                if (NS_SUCCEEDED(loadGroupCI->GetBlockingTransactionCount(&blockers)) &&
+                    blockers) {
+                    // need to wait for blockers to clear
+                    LOG(("   blocked by load group: [blockers=%d]\n", blockers));
+                    return NS_ERROR_NOT_AVAILABLE;
+                }
+            }
+        }
+    }
+    else {
+        // Mark the transaction and its load group as blocking right now to prevent
+        // other transactions from being reordered in the queue due to slow syns.
+        trans->DispatchedAsBlocking();
+    }
+
     // step 1
     // If connection pressure, then we want to favor pipelining of any kind
     if (IsUnderPressure(ent, classification) && !attemptedOptimisticPipeline) {
         attemptedOptimisticPipeline = true;
         if (AddToShortestPipeline(ent, trans,
                                   classification,
                                   mMaxOptimisticPipelinedRequests)) {
             return NS_OK;
@@ -1569,16 +1613,17 @@ nsHttpConnectionMgr::DispatchTransaction
                       "DispatchTranaction() on non spdy active connection");
 
     if (!(caps & NS_HTTP_ALLOW_PIPELINING))
         conn->Classify(nsAHttpTransaction::CLASS_SOLO);
     else
         conn->Classify(trans->Classification());
 
     rv = DispatchAbstractTransaction(ent, trans, caps, conn, priority);
+
     if (NS_SUCCEEDED(rv) && !trans->GetPendingTime().IsNull()) {
         if (trans->UsesPipelining())
             AccumulateTimeDelta(Telemetry::TRANSACTION_WAIT_TIME_HTTP_PIPELINES,
                 trans->GetPendingTime(), TimeStamp::Now());
         else
             AccumulateTimeDelta(Telemetry::TRANSACTION_WAIT_TIME_HTTP,
                 trans->GetPendingTime(), TimeStamp::Now());
         trans->SetPendingTime(false);
@@ -1788,22 +1833,23 @@ nsHttpConnectionMgr::RecvdConnect()
 
 nsresult
 nsHttpConnectionMgr::CreateTransport(nsConnectionEntry *ent,
                                      nsAHttpTransaction *trans,
                                      uint32_t caps,
                                      bool speculative)
 {
     NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
-
+    
     nsRefPtr<nsHalfOpenSocket> sock = new nsHalfOpenSocket(ent, trans, caps);
     nsresult rv = sock->SetupPrimaryStreams();
     NS_ENSURE_SUCCESS(rv, rv);
 
     ent->mHalfOpens.AppendElement(sock);
+    mNumHalfOpenConns++;
     if (speculative)
         sock->SetSpeculative(true);
     return NS_OK;
 }
 
 // This function tries to dispatch the pending spdy transactions on
 // the connection entry sent in as an argument. It will do so on the
 // active spdy connection either in that same entry or in the
@@ -1991,21 +2037,29 @@ nsHttpConnectionMgr::OnMsgCancelTransact
 }
 
 void
 nsHttpConnectionMgr::OnMsgProcessPendingQ(int32_t, void *param)
 {
     NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
     nsHttpConnectionInfo *ci = (nsHttpConnectionInfo *) param;
 
-    LOG(("nsHttpConnectionMgr::OnMsgProcessPendingQ [ci=%s]\n", ci->HashKey().get()));
+    if (!ci) {
+        LOG(("nsHttpConnectionMgr::OnMsgProcessPendingQ [ci=nullptr]\n"));
+        // Try and dispatch everything
+        mCT.Enumerate(ProcessAllTransactionsCB, this);
+        return;
+    }
+
+    LOG(("nsHttpConnectionMgr::OnMsgProcessPendingQ [ci=%s]\n",
+         ci->HashKey().get()));
 
     // start by processing the queue identified by the given connection info.
     nsConnectionEntry *ent = mCT.Get(ci->HashKey());
-    if (!(ent && ProcessPendingQForEntry(ent))) {
+    if (!(ent && ProcessPendingQForEntry(ent, false))) {
         // if we reach here, it means that we couldn't dispatch a transaction
         // for the specified connection info.  walk the connection table...
         mCT.Enumerate(ProcessOneTransactionCB, this);
     }
 
     NS_RELEASE(ci);
 }
 
@@ -2352,20 +2406,24 @@ nsHttpConnectionMgr::OnMsgSpeculativeCon
 
     // If spdy has previously made a preferred entry for this host via
     // the ip pooling rules. If so, connect to the preferred host instead of
     // the one directly passed in here.
     nsConnectionEntry *preferredEntry = GetSpdyPreferredEnt(ent);
     if (preferredEntry)
         ent = preferredEntry;
 
-    if (!ent->mIdleConns.Length() && !RestrictConnections(ent) &&
+    if (mNumHalfOpenConns <= gHttpHandler->ParallelSpeculativeConnectLimit() &&
+        !ent->mIdleConns.Length() && !RestrictConnections(ent) &&
         !AtActiveConnectionLimit(ent, trans->Caps())) {
         CreateTransport(ent, trans, trans->Caps(), true);
     }
+    else {
+        LOG(("  Transport not created due to existing connection count\n"));
+    }
 }
 
 bool
 nsHttpConnectionMgr::nsConnectionHandle::IsPersistent()
 {
     return mConn->IsPersistent();
 }
 
@@ -3147,15 +3205,16 @@ nsHttpConnectionMgr::nsConnectionEntry::
 void
 nsHttpConnectionMgr::
 nsConnectionEntry::RemoveHalfOpen(nsHalfOpenSocket *halfOpen)
 {
     // A failure to create the transport object at all
     // will result in it not being present in the halfopen table
     // so ignore failures of RemoveElement()
     mHalfOpens.RemoveElement(halfOpen);
+    gHttpHandler->ConnMgr()->mNumHalfOpenConns--;
 
     if (!UnconnectedHalfOpens())
         // perhaps this reverted RestrictConnections()
         // use the PostEvent version of processpendingq to avoid
         // altering the pending q vector from an arbitrary stack
         gHttpHandler->ConnMgr()->ProcessPendingQ(mConnInfo);
 }
--- a/netwerk/protocol/http/nsHttpConnectionMgr.h
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.h
@@ -202,16 +202,19 @@ public:
     // NOTE: functions below may be called only on the socket thread.
     //-------------------------------------------------------------------------
 
     // called to force the transaction queue to be processed once more, giving
     // preference to the specified connection.
     nsresult ProcessPendingQ(nsHttpConnectionInfo *);
     bool     ProcessPendingQForEntry(nsHttpConnectionInfo *);
 
+    // Try and process all pending transactions
+    nsresult ProcessPendingQ();
+
     // This is used to force an idle connection to be closed and removed from
     // the idle connection list. It is called when the idle connection detects
     // that the network peer has closed the transport.
     nsresult CloseIdleConnection(nsHttpConnection *);
 
     // The connection manager needs to know when a normal HTTP connection has been
     // upgraded to SPDY because the dispatch and idle semantics are a little
     // bit different.
@@ -448,22 +451,23 @@ private:
     uint16_t mMaxOptimisticPipelinedRequests;
     bool mIsShuttingDown;
 
     //-------------------------------------------------------------------------
     // NOTE: these members are only accessed on the socket transport thread
     //-------------------------------------------------------------------------
 
     static PLDHashOperator ProcessOneTransactionCB(const nsACString &, nsAutoPtr<nsConnectionEntry> &, void *);
+    static PLDHashOperator ProcessAllTransactionsCB(const nsACString &, nsAutoPtr<nsConnectionEntry> &, void *);
 
     static PLDHashOperator PruneDeadConnectionsCB(const nsACString &, nsAutoPtr<nsConnectionEntry> &, void *);
     static PLDHashOperator ShutdownPassCB(const nsACString &, nsAutoPtr<nsConnectionEntry> &, void *);
     static PLDHashOperator PurgeExcessIdleConnectionsCB(const nsACString &, nsAutoPtr<nsConnectionEntry> &, void *);
     static PLDHashOperator ClosePersistentConnectionsCB(const nsACString &, nsAutoPtr<nsConnectionEntry> &, void *);
-    bool     ProcessPendingQForEntry(nsConnectionEntry *);
+    bool     ProcessPendingQForEntry(nsConnectionEntry *, bool considerAll);
     bool     IsUnderPressure(nsConnectionEntry *ent,
                              nsHttpTransaction::Classifier classification);
     bool     AtActiveConnectionLimit(nsConnectionEntry *, uint32_t caps);
     nsresult TryDispatchTransaction(nsConnectionEntry *ent,
                                     bool onlyReusedConnection,
                                     nsHttpTransaction *trans);
     nsresult DispatchTransaction(nsConnectionEntry *,
                                  nsHttpTransaction *,
@@ -574,16 +578,19 @@ private:
     void OnMsgProcessFeedback      (int32_t, void *);
 
     // Total number of active connections in all of the ConnectionEntry objects
     // that are accessed from mCT connection table.
     uint16_t mNumActiveConns;
     // Total number of idle connections in all of the ConnectionEntry objects
     // that are accessed from mCT connection table.
     uint16_t mNumIdleConns;
+    // Total number of connections in mHalfOpens ConnectionEntry objects
+    // that are accessed from mCT connection table
+    uint32_t mNumHalfOpenConns;
 
     // Holds time in seconds for next wake-up to prune dead connections. 
     uint64_t mTimeOfNextWakeUp;
     // Timer for next pruning of dead connections.
     nsCOMPtr<nsITimer> mTimer;
 
     // A 1s tick to call nsHttpConnection::ReadTimeoutTick on
     // active http/1 connections and check for orphaned half opens.
--- a/netwerk/protocol/http/nsHttpHandler.cpp
+++ b/netwerk/protocol/http/nsHttpHandler.cpp
@@ -173,16 +173,17 @@ nsHttpHandler::nsHttpHandler()
     , mSpdyV3(true)
     , mCoalesceSpdy(true)
     , mUseAlternateProtocol(false)
     , mSpdySendingChunkSize(ASpdySession::kSendingChunkSize)
     , mSpdySendBufferSize(ASpdySession::kTCPSendBufferSize)
     , mSpdyPingThreshold(PR_SecondsToInterval(58))
     , mSpdyPingTimeout(PR_SecondsToInterval(8))
     , mConnectTimeout(90000)
+    , mParallelSpeculativeConnectLimit(6)
 {
 #if defined(PR_LOGGING)
     gHttpLog = PR_NewLogModule("nsHttp");
 #endif
 
     LOG(("Creating nsHttpHandler [this=%x].\n", this));
 
     NS_ASSERTION(!gHttpHandler, "HTTP handler already created!");
@@ -1133,16 +1134,24 @@ nsHttpHandler::PrefsChanged(nsIPrefBranc
     // established
     if (PREF_CHANGED(HTTP_PREF("connection-timeout"))) {
         rv = prefs->GetIntPref(HTTP_PREF("connection-timeout"), &val);
         if (NS_SUCCEEDED(rv))
             // the pref is in seconds, but the variable is in milliseconds
             mConnectTimeout = clamped(val, 1, 0xffff) * PR_MSEC_PER_SEC;
     }
 
+    // The maximum number of current global half open sockets allowable
+    // for starting a new speculative connection.
+    if (PREF_CHANGED(HTTP_PREF("speculative-parallel-limit"))) {
+        rv = prefs->GetIntPref(HTTP_PREF("speculative-parallel-limit"), &val);
+        if (NS_SUCCEEDED(rv))
+            mParallelSpeculativeConnectLimit = (uint32_t) clamped(val, 0, 1024);
+    }
+
     // on transition of network.http.diagnostics to true print
     // a bunch of information to the console
     if (pref && PREF_CHANGED(HTTP_PREF("diagnostics"))) {
         rv = prefs->GetBoolPref(HTTP_PREF("diagnostics"), &cVar);
         if (NS_SUCCEEDED(rv) && cVar) {
             if (mConnMgr)
                 mConnMgr->PrintDiagnostics();
         }
--- a/netwerk/protocol/http/nsHttpHandler.h
+++ b/netwerk/protocol/http/nsHttpHandler.h
@@ -92,16 +92,17 @@ public:
     bool           IsSpdyV3Enabled() { return mSpdyV3; }
     bool           CoalesceSpdy() { return mCoalesceSpdy; }
     bool           UseAlternateProtocol() { return mUseAlternateProtocol; }
     uint32_t       SpdySendingChunkSize() { return mSpdySendingChunkSize; }
     uint32_t       SpdySendBufferSize()      { return mSpdySendBufferSize; }
     PRIntervalTime SpdyPingThreshold() { return mSpdyPingThreshold; }
     PRIntervalTime SpdyPingTimeout() { return mSpdyPingTimeout; }
     uint32_t       ConnectTimeout()  { return mConnectTimeout; }
+    uint32_t       ParallelSpeculativeConnectLimit() { return mParallelSpeculativeConnectLimit; }
 
     bool           PromptTempRedirect()      { return mPromptTempRedirect; }
 
     nsHttpAuthCache     *AuthCache(bool aPrivate) {
         return aPrivate ? &mPrivateAuthCache : &mAuthCache;
     }
     nsHttpConnectionMgr *ConnMgr()   { return mConnMgr; }
 
@@ -149,16 +150,21 @@ public:
         return mConnMgr->ReclaimConnection(conn);
     }
 
     nsresult ProcessPendingQ(nsHttpConnectionInfo *cinfo)
     {
         return mConnMgr->ProcessPendingQ(cinfo);
     }
 
+    nsresult ProcessPendingQ()
+    {
+        return mConnMgr->ProcessPendingQ();
+    }
+
     nsresult GetSocketThreadTarget(nsIEventTarget **target)
     {
         return mConnMgr->GetSocketThreadTarget(target);
     }
 
     nsresult SpeculativeConnect(nsHttpConnectionInfo *ci,
                                 nsIInterfaceRequestor *callbacks)
     {
@@ -385,16 +391,20 @@ private:
     uint32_t       mSpdySendingChunkSize;
     uint32_t       mSpdySendBufferSize;
     PRIntervalTime mSpdyPingThreshold;
     PRIntervalTime mSpdyPingTimeout;
 
     // The maximum amount of time to wait for socket transport to be
     // established. In milliseconds.
     uint32_t       mConnectTimeout;
+
+    // The maximum number of current global half open sockets allowable
+    // when starting a new speculative connection.
+    uint32_t       mParallelSpeculativeConnectLimit;
 };
 
 //-----------------------------------------------------------------------------
 
 extern nsHttpHandler *gHttpHandler;
 
 //-----------------------------------------------------------------------------
 // nsHttpsHandler - thin wrapper to distinguish the HTTP handler from the
--- a/netwerk/protocol/http/nsHttpTransaction.cpp
+++ b/netwerk/protocol/http/nsHttpTransaction.cpp
@@ -102,16 +102,17 @@ nsHttpTransaction::nsHttpTransaction()
     , mNoContent(false)
     , mSentData(false)
     , mReceivedData(false)
     , mStatusEventPending(false)
     , mHasRequestBody(false)
     , mProxyConnectFailed(false)
     , mHttpResponseMatched(false)
     , mPreserveStream(false)
+    , mDispatchedAsBlocking(false)
     , mReportedStart(false)
     , mReportedResponseHeader(false)
     , mForTakeResponseHead(nullptr)
     , mResponseHeadTaken(false)
 {
     LOG(("Creating nsHttpTransaction @%x\n", this));
     gHttpHandler->GetMaxPipelineObjectSize(&mMaxPipelineObjectSize);
 }
@@ -124,16 +125,17 @@ nsHttpTransaction::~nsHttpTransaction()
     mCallbacks = nullptr;
 
     NS_IF_RELEASE(mConnection);
     NS_IF_RELEASE(mConnInfo);
 
     delete mResponseHead;
     delete mForTakeResponseHead;
     delete mChunkedDecoder;
+    ReleaseBlockingTransaction();
 }
 
 nsHttpTransaction::Classifier
 nsHttpTransaction::Classify()
 {
     if (!(mCaps & NS_HTTP_ALLOW_PIPELINING))
         return (mClassification = CLASS_SOLO);
 
@@ -781,16 +783,17 @@ nsHttpTransaction::Close(nsresult reason
         mTimings.responseEnd = TimeStamp::Now();
 
     if (relConn && mConnection)
         NS_RELEASE(mConnection);
 
     mStatus = reason;
     mTransactionDone = true; // forcibly flag the transaction as complete
     mClosed = true;
+    ReleaseBlockingTransaction();
 
     // release some resources that we no longer need
     mRequestStream = nullptr;
     mReqHeaderBuf.Truncate();
     mLineBuf.Truncate();
     if (mChunkedDecoder) {
         delete mChunkedDecoder;
         mChunkedDecoder = nullptr;
@@ -1378,16 +1381,17 @@ nsHttpTransaction::HandleContent(char *b
     }
 
     // check for end-of-file
     if ((mContentRead == mContentLength) ||
         (mChunkedDecoder && mChunkedDecoder->ReachedEOF())) {
         // the transaction is done with a complete response.
         mTransactionDone = true;
         mResponseIsComplete = true;
+        ReleaseBlockingTransaction();
 
         if (TimingEnabled())
             mTimings.responseEnd = TimeStamp::Now();
 
         // report the entire response has arrived
         if (mActivityDistributor)
             mActivityDistributor->ObserveActivity(
                 mChannel,
@@ -1490,16 +1494,67 @@ nsHttpTransaction::CancelPipeline(uint32
     mConnection->CancelPipeline(NS_ERROR_ABORT);
 
     // Avoid pipelining this transaction on restart by classifying it as solo.
     // This also prevents BadUnexpectedLarge from being reported more
     // than one time per transaction.
     mClassification = CLASS_SOLO;
 }
 
+// Called when the transaction marked for blocking is associated with a connection
+// (i.e. added to a spdy session, an idle http connection, or placed into
+// a http pipeline). It is safe to call this multiple times with it only
+// having an effect once.
+void
+nsHttpTransaction::DispatchedAsBlocking()
+{
+    if (mDispatchedAsBlocking)
+        return;
+
+    LOG(("nsHttpTransaction %p dispatched as blocking\n", this));
+    
+    if (!mLoadGroupCI)
+        return;
+
+    LOG(("nsHttpTransaction adding blocking channel %p from "
+         "loadgroup %p\n", this, mLoadGroupCI.get()));
+    
+    mLoadGroupCI->AddBlockingTransaction();
+    mDispatchedAsBlocking = true;
+}
+
+void
+nsHttpTransaction::RemoveDispatchedAsBlocking()
+{
+    if (!mLoadGroupCI || !mDispatchedAsBlocking)
+        return;
+    
+    uint32_t blockers = 0;
+    nsresult rv = mLoadGroupCI->RemoveBlockingTransaction(&blockers);
+
+    LOG(("nsHttpTransaction removing blocking channel %p from "
+         "loadgroup %p. %d blockers remain.\n", this,
+         mLoadGroupCI.get(), blockers));
+
+    if (NS_SUCCEEDED(rv) && !blockers) {
+        LOG(("nsHttpTransaction %p triggering release of blocked channels.\n",
+             this));
+        gHttpHandler->ConnMgr()->ProcessPendingQ();
+    }
+    
+    mDispatchedAsBlocking = false;
+}
+
+void
+nsHttpTransaction::ReleaseBlockingTransaction()
+{
+    RemoveDispatchedAsBlocking();
+    mLoadGroupCI = nullptr;
+}
+
 //-----------------------------------------------------------------------------
 // nsHttpTransaction deletion event
 //-----------------------------------------------------------------------------
 
 class nsDeleteHttpTransaction : public nsRunnable {
 public:
     nsDeleteHttpTransaction(nsHttpTransaction *trans)
         : mTrans(trans)
--- a/netwerk/protocol/http/nsHttpTransaction.h
+++ b/netwerk/protocol/http/nsHttpTransaction.h
@@ -9,16 +9,17 @@
 #include "nsHttp.h"
 #include "nsHttpHeaderArray.h"
 #include "nsAHttpTransaction.h"
 #include "nsAHttpConnection.h"
 #include "nsCOMPtr.h"
 
 #include "nsIPipe.h"
 #include "nsIInputStream.h"
+#include "nsILoadGroup.h"
 #include "nsIOutputStream.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsISocketTransportService.h"
 #include "nsITransport.h"
 #include "nsIEventTarget.h"
 #include "TimingStruct.h"
 
 //-----------------------------------------------------------------------------
@@ -106,28 +107,34 @@ public:
 
     void PrintDiagnostics(nsCString &log);
 
     // Sets mPendingTime to the current time stamp or to a null time stamp (if now is false)
     void SetPendingTime(bool now = true) { mPendingTime = now ? mozilla::TimeStamp::Now() : mozilla::TimeStamp(); }
     const mozilla::TimeStamp GetPendingTime() { return mPendingTime; }
     bool UsesPipelining() const { return mCaps & NS_HTTP_ALLOW_PIPELINING; }
 
+    void SetLoadGroupConnectionInfo(nsILoadGroupConnectionInfo *aLoadGroupCI) { mLoadGroupCI = aLoadGroupCI; } 
+    nsILoadGroupConnectionInfo *LoadGroupConnectionInfo() { return mLoadGroupCI.get(); }
+    void DispatchedAsBlocking();
+    void RemoveDispatchedAsBlocking();
+
 private:
     nsresult Restart();
     nsresult RestartInProgress();
     char    *LocateHttpStart(char *buf, uint32_t len,
                              bool aAllowPartialMatch);
     nsresult ParseLine(char *line);
     nsresult ParseLineSegment(char *seg, uint32_t len);
     nsresult ParseHead(char *, uint32_t count, uint32_t *countRead);
     nsresult HandleContentStart();
     nsresult HandleContent(char *, uint32_t count, uint32_t *contentRead, uint32_t *contentRemaining);
     nsresult ProcessData(char *, uint32_t, uint32_t *);
     void     DeleteSelfOnConsumerThread();
+    void     ReleaseBlockingTransaction();
 
     Classifier Classify();
     void       CancelPipeline(uint32_t reason);
 
     static NS_METHOD ReadRequestSegment(nsIInputStream *, void *, const char *,
                                         uint32_t, uint32_t, uint32_t *);
     static NS_METHOD WritePipeSegment(nsIOutputStream *, void *, char *,
                                       uint32_t, uint32_t, uint32_t *);
@@ -138,16 +145,17 @@ private:
     mozilla::Mutex mCallbacksLock;
 
     nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
     nsCOMPtr<nsITransportEventSink> mTransportSink;
     nsCOMPtr<nsIEventTarget>        mConsumerTarget;
     nsCOMPtr<nsISupports>           mSecurityInfo;
     nsCOMPtr<nsIAsyncInputStream>   mPipeIn;
     nsCOMPtr<nsIAsyncOutputStream>  mPipeOut;
+    nsCOMPtr<nsILoadGroupConnectionInfo> mLoadGroupCI;
 
     nsCOMPtr<nsISupports>             mChannel;
     nsCOMPtr<nsIHttpActivityObserver> mActivityDistributor;
 
     nsCString                       mReqHeaderBuf;    // flattened request headers
     nsCOMPtr<nsIInputStream>        mRequestStream;
     uint64_t                        mRequestSize;
 
@@ -199,16 +207,17 @@ private:
     bool                            mNoContent; // expecting an empty entity body
     bool                            mSentData;
     bool                            mReceivedData;
     bool                            mStatusEventPending;
     bool                            mHasRequestBody;
     bool                            mProxyConnectFailed;
     bool                            mHttpResponseMatched;
     bool                            mPreserveStream;
+    bool                            mDispatchedAsBlocking;
 
     // mClosed           := transaction has been explicitly closed
     // mTransactionDone  := transaction ran to completion or was interrupted
     // mResponseComplete := transaction ran to completion
 
     // For Restart-In-Progress Functionality
     bool                            mReportedStart;
     bool                            mReportedResponseHeader;