bug 599164 pipeline with type and state r=honzab
authorPatrick McManus <mcmanus@ducksong.com>
Thu, 22 Mar 2012 19:39:31 -0400
changeset 90098 ee6328d11bfbdb4522005db634ff4647768520d6
parent 90097 fd6f1cca200c3b750198ec03cbd82e8fa44b59ec
child 90099 e83a04b972e9e858ae3a025a4ac0eac4286e1d22
push id22312
push usermak77@bonardo.net
push dateFri, 23 Mar 2012 11:50:43 +0000
treeherdermozilla-central@c20ec27eb0e8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewershonzab
bugs599164
milestone14.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
bug 599164 pipeline with type and state r=honzab
content/base/src/nsXMLHttpRequest.cpp
content/html/content/src/nsHTMLMediaElement.cpp
modules/libpref/src/init/all.js
netwerk/base/public/nsIRequest.idl
netwerk/protocol/http/SpdySession.cpp
netwerk/protocol/http/nsAHttpTransaction.h
netwerk/protocol/http/nsHttp.h
netwerk/protocol/http/nsHttpChannel.cpp
netwerk/protocol/http/nsHttpConnection.cpp
netwerk/protocol/http/nsHttpConnection.h
netwerk/protocol/http/nsHttpConnectionInfo.cpp
netwerk/protocol/http/nsHttpConnectionInfo.h
netwerk/protocol/http/nsHttpConnectionMgr.cpp
netwerk/protocol/http/nsHttpConnectionMgr.h
netwerk/protocol/http/nsHttpHandler.cpp
netwerk/protocol/http/nsHttpHandler.h
netwerk/protocol/http/nsHttpPipeline.cpp
netwerk/protocol/http/nsHttpPipeline.h
netwerk/protocol/http/nsHttpTransaction.cpp
netwerk/protocol/http/nsHttpTransaction.h
--- a/content/base/src/nsXMLHttpRequest.cpp
+++ b/content/base/src/nsXMLHttpRequest.cpp
@@ -2774,16 +2774,20 @@ nsXMLHttpRequest::Send(nsIVariant *aBody
   nsCOMPtr<nsIStreamListener> listener = this;
   if (mState & XML_HTTP_REQUEST_MULTIPART) {
     listener = new nsMultipartProxyListener(listener);
     if (!listener) {
       return NS_ERROR_OUT_OF_MEMORY;
     }
   }
 
+  // Blocking gets are common enough out of XHR that we should mark
+  // the channel slow by default for pipeline purposes
+  AddLoadFlags(mChannel, nsIRequest::INHIBIT_PIPELINE);
+
   if (!IsSystemXHR()) {
     // Always create a nsCORSListenerProxy here even if it's
     // a same-origin request right now, since it could be redirected.
     listener = new nsCORSListenerProxy(listener, mPrincipal, mChannel,
                                        withCredentials, true, &rv);
     NS_ENSURE_TRUE(listener, NS_ERROR_OUT_OF_MEMORY);
     NS_ENSURE_SUCCESS(rv, rv);
   }
--- a/content/html/content/src/nsHTMLMediaElement.cpp
+++ b/content/html/content/src/nsHTMLMediaElement.cpp
@@ -52,16 +52,18 @@
 #include "nsIDOMDocument.h"
 #include "nsDOMError.h"
 #include "nsNodeInfoManager.h"
 #include "nsNetUtil.h"
 #include "nsXPCOMStrings.h"
 #include "nsThreadUtils.h"
 #include "nsIThreadInternal.h"
 #include "nsContentUtils.h"
+#include "nsIRequest.h"
+
 #include "nsFrameManager.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsIXPConnect.h"
 #include "jsapi.h"
 
 #include "nsITimer.h"
 
 #include "nsEventDispatcher.h"
@@ -2845,16 +2847,23 @@ nsresult nsHTMLMediaElement::GetBuffered
   return NS_OK;
 }
 
 void nsHTMLMediaElement::SetRequestHeaders(nsIHttpChannel* aChannel)
 {
   // Send Accept header for video and audio types only (Bug 489071)
   SetAcceptHeader(aChannel);
 
+  // Media elements are likely candidates for HTTP Pipeline head of line
+  // blocking problems, so disable pipelines.
+  nsLoadFlags loadflags;
+  aChannel->GetLoadFlags(&loadflags);
+  loadflags |= nsIRequest::INHIBIT_PIPELINE;
+  aChannel->SetLoadFlags(loadflags);
+
   // Apache doesn't send Content-Length when gzip transfer encoding is used,
   // which prevents us from estimating the video length (if explicit Content-Duration
   // and a length spec in the container are not present either) and from seeking.
   // So, disable the standard "Accept-Encoding: gzip,deflate" that we usually send.
   // See bug 614760.
   aChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept-Encoding"),
                              EmptyCString(), false);
 
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -804,17 +804,23 @@ pref("network.http.redirection-limit", 2
 // NOTE: separate values with comma+space (", "): see bug 576033
 pref("network.http.accept-encoding", "gzip, deflate");
 
 pref("network.http.pipelining"      , false);
 pref("network.http.pipelining.ssl"  , false); // disable pipelining over SSL
 pref("network.http.proxy.pipelining", false);
 
 // Max number of requests in the pipeline
-pref("network.http.pipelining.maxrequests" , 4);
+pref("network.http.pipelining.maxrequests" , 32);
+
+// An optimistic request is one pipelined when policy might allow a new
+// connection instead
+pref("network.http.pipelining.max-optimistic-requests" , 4);
+
+pref("network.http.pipelining.aggressive", false);
 
 // Prompt for 307 redirects
 pref("network.http.prompt-temp-redirect", true);
 
 // If true generate CORRUPTED_CONTENT errors for entities that
 // contain an invalid Assoc-Req response header
 pref("network.http.assoc-req.enforce", false);
 
--- a/netwerk/base/public/nsIRequest.idl
+++ b/netwerk/base/public/nsIRequest.idl
@@ -155,16 +155,23 @@ interface nsIRequest : nsISupports
      */
     const unsigned long LOAD_BACKGROUND = 1 << 0; 
 
     /**************************************************************************
      * The following flags control the flow of data into the cache.
      */
 
     /**
+     *  This flag prevents loading of the request with an HTTP pipeline.
+     *  Generally this is because the resource is expected to take a
+     *  while to load and may cause head of line blocking problems.
+     */
+    const unsigned long INHIBIT_PIPELINE = 1 << 6;
+
+    /**
      * This flag prevents caching of any kind.  It does not, however, prevent
      * cached content from being used to satisfy this request.
      */
     const unsigned long INHIBIT_CACHING = 1 << 7;
 
     /**
      * This flag prevents caching on disk (or other persistent media), which
      * may be needed to preserve privacy.  For HTTPS, this flag is set auto-
--- a/netwerk/protocol/http/SpdySession.cpp
+++ b/netwerk/protocol/http/SpdySession.cpp
@@ -2059,18 +2059,17 @@ void
 SpdySession::SetSSLConnectFailed()
 {
   NS_ABORT_IF_FALSE(false, "SpdySession::SetSSLConnectFailed()");
 }
 
 bool
 SpdySession::IsDone()
 {
-  NS_ABORT_IF_FALSE(false, "SpdySession::IsDone()");
-  return false;
+  return !mStreamTransactionHash.Count();
 }
 
 nsresult
 SpdySession::Status()
 {
   NS_ABORT_IF_FALSE(false, "SpdySession::Status()");
   return NS_ERROR_UNEXPECTED;
 }
@@ -2146,22 +2145,20 @@ SpdySession::AddTransaction(nsAHttpTrans
   // extended with AddStream()
 
   NS_ABORT_IF_FALSE(false,
                     "SpdySession::AddTransaction() should not be called");
 
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
-PRUint16
-SpdySession::PipelineDepthAvailable()
+PRUint32
+SpdySession::PipelineDepth()
 {
-  // any attempt at pipelining will be turned into parallelism
-
-  return 0;
+  return IsDone() ? 0 : 1;
 }
 
 nsresult
 SpdySession::SetPipelinePosition(PRInt32 position)
 {
   // This API is meant for pipelining, SpdySession's should be
   // extended with AddStream()
 
--- a/netwerk/protocol/http/nsAHttpTransaction.h
+++ b/netwerk/protocol/http/nsAHttpTransaction.h
@@ -58,16 +58,18 @@ class nsHttpRequestHead;
 // write function returns NS_BASE_STREAM_WOULD_BLOCK in this case).
 //----------------------------------------------------------------------------
 
 class nsAHttpTransaction : public nsISupports
 {
 public:
     // called by the connection when it takes ownership of the transaction.
     virtual void SetConnection(nsAHttpConnection *) = 0;
+
+    // used to obtain the connection associated with this transaction
     virtual nsAHttpConnection *Connection() = 0;
 
     // called by the connection to get security callbacks to set on the
     // socket transport.
     virtual void GetSecurityCallbacks(nsIInterfaceRequestor **,
                                       nsIEventTarget **) = 0;
 
     // called to report socket status (see nsITransportEventSink)
@@ -116,24 +118,48 @@ public:
     virtual nsresult TakeSubTransactions(
         nsTArray<nsRefPtr<nsAHttpTransaction> > &outTransactions) = 0;
 
     // called to add a sub-transaction in the case of pipelined transactions
     // classes that do not implement sub transactions
     // return NS_ERROR_NOT_IMPLEMENTED
     virtual nsresult AddTransaction(nsAHttpTransaction *transaction) = 0;
     
-    // called to count the number of sub transactions that can be added
-    virtual PRUint16 PipelineDepthAvailable() = 0;
+    // The total length of the outstanding pipeline comprised of transacations
+    // and sub-transactions.
+    virtual PRUint32 PipelineDepth() = 0;
 
     // Used to inform the connection that it is being used in a pipelined
     // context. That may influence the handling of some errors.
-    // The value is the pipeline position.
+    // The value is the pipeline position (> 1).
     virtual nsresult SetPipelinePosition(PRInt32) = 0;
     virtual PRInt32  PipelinePosition() = 0;
+
+    // Every transaction is classified into one of the types below. When using
+    // HTTP pipelines, only transactions with the same type appear on the same
+    // pipeline.
+    enum Classifier  {
+        // Transactions that expect a short 304 (no-content) response
+        CLASS_REVALIDATION,
+
+        // Transactions for content expected to be CSS or JS
+        CLASS_SCRIPT,
+
+        // Transactions for content expected to be an image
+        CLASS_IMAGE,
+
+        // Transactions that cannot involve a pipeline 
+        CLASS_SOLO,
+
+        // Transactions that do not fit any of the other categories. HTML
+        // is normally GENERAL.
+        CLASS_GENERAL,
+
+        CLASS_MAX
+    };
 };
 
 #define NS_DECL_NSAHTTPTRANSACTION \
     void SetConnection(nsAHttpConnection *); \
     nsAHttpConnection *Connection(); \
     void GetSecurityCallbacks(nsIInterfaceRequestor **, \
                               nsIEventTarget **);       \
     void OnTransportStatus(nsITransport* transport, \
@@ -145,17 +171,17 @@ public:
     nsresult ReadSegments(nsAHttpSegmentReader *, PRUint32, PRUint32 *); \
     nsresult WriteSegments(nsAHttpSegmentWriter *, PRUint32, PRUint32 *); \
     void     Close(nsresult reason);                                    \
     void     SetSSLConnectFailed();                                     \
     nsHttpRequestHead *RequestHead();                                   \
     PRUint32 Http1xTransactionCount();                                  \
     nsresult TakeSubTransactions(nsTArray<nsRefPtr<nsAHttpTransaction> > &outTransactions); \
     nsresult AddTransaction(nsAHttpTransaction *);                      \
-    PRUint16 PipelineDepthAvailable();                                  \
+    PRUint32 PipelineDepth();                                           \
     nsresult SetPipelinePosition(PRInt32);                              \
     PRInt32  PipelinePosition();
 
 //-----------------------------------------------------------------------------
 // nsAHttpSegmentReader
 //-----------------------------------------------------------------------------
 
 class nsAHttpSegmentReader
--- a/netwerk/protocol/http/nsHttp.h
+++ b/netwerk/protocol/http/nsHttp.h
@@ -135,19 +135,16 @@ typedef PRUint8 nsHttpVersion;
 // such as HTTP upgrade which are nonsensical for SPDY, it is not the
 // SPDY configuration variable.
 #define NS_HTTP_DISALLOW_SPDY        (1<<7)
 
 //-----------------------------------------------------------------------------
 // some default values
 //-----------------------------------------------------------------------------
 
-// hard upper limit on the number of requests that can be pipelined
-#define NS_HTTP_MAX_PIPELINED_REQUESTS 8 
-
 #define NS_HTTP_DEFAULT_PORT  80
 #define NS_HTTPS_DEFAULT_PORT 443
 
 #define NS_HTTP_HEADER_SEPS ", \t"
 
 //-----------------------------------------------------------------------------
 // http atoms...
 //-----------------------------------------------------------------------------
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -502,26 +502,27 @@ nsHttpChannel::SetupTransaction()
 
     NS_ENSURE_TRUE(!mTransaction, NS_ERROR_ALREADY_INITIALIZED);
 
     nsresult rv;
 
     if (mCaps & NS_HTTP_ALLOW_PIPELINING) {
         //
         // disable pipelining if:
-        //   (1) pipelining has been explicitly disabled
-        //   (2) request corresponds to a top-level document load (link click)
-        //   (3) request method is non-idempotent
+        //   (1) pipelining has been disabled by config
+        //   (2) pipelining has been disabled by connection mgr info
+        //   (3) request corresponds to a top-level document load (link click)
+        //   (4) request method is non-idempotent
+        //   (5) request is marked slow (e.g XHR)
         //
-        // XXX does the toplevel document check really belong here?  or, should
-        //     we push it out entirely to necko consumers?
-        //
-        if (!mAllowPipelining || (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) ||
+        if (!mAllowPipelining ||
+           (mLoadFlags & (LOAD_INITIAL_DOCUMENT_URI | INHIBIT_PIPELINE)) ||
             !(mRequestHead.Method() == nsHttp::Get ||
               mRequestHead.Method() == nsHttp::Head ||
+              mRequestHead.Method() == nsHttp::Options ||
               mRequestHead.Method() == nsHttp::Propfind ||
               mRequestHead.Method() == nsHttp::Proppatch)) {
             LOG(("  pipelining disallowed\n"));
             mCaps &= ~NS_HTTP_ALLOW_PIPELINING;
         }
     }
 
     if (!mAllowSpdy)
@@ -1773,17 +1774,20 @@ nsHttpChannel::EnsureAssocReq()
     // check the method
     PRInt32 methodlen = PL_strlen(mRequestHead.Method().get());
     if ((methodlen != (endofmethod - method)) ||
         PL_strncmp(method,
                    mRequestHead.Method().get(),
                    endofmethod - method)) {
         LOG(("  Assoc-Req failure Method %s", method));
         if (mConnectionInfo)
-            mConnectionInfo->BanPipelining();
+            gHttpHandler->ConnMgr()->
+                PipelineFeedbackInfo(mConnectionInfo,
+                                     nsHttpConnectionMgr::RedCorruptedContent,
+                                     nsnull, 0);
 
         nsCOMPtr<nsIConsoleService> consoleService =
             do_GetService(NS_CONSOLESERVICE_CONTRACTID);
         if (consoleService) {
             nsAutoString message
                 (NS_LITERAL_STRING("Failed Assoc-Req. Received "));
             AppendASCIItoUTF16(
                 mResponseHead->PeekHeader(nsHttp::Assoc_Req),
@@ -1803,17 +1807,20 @@ nsHttpChannel::EnsureAssocReq()
     if (NS_FAILED(NS_NewURI(getter_AddRefs(assoc_url), assoc_val)) ||
         !assoc_url)
         return NS_OK;
 
     mURI->Equals(assoc_url, &equals);
     if (!equals) {
         LOG(("  Assoc-Req failure URL %s", assoc_val));
         if (mConnectionInfo)
-            mConnectionInfo->BanPipelining();
+            gHttpHandler->ConnMgr()->
+                PipelineFeedbackInfo(mConnectionInfo,
+                                     nsHttpConnectionMgr::RedCorruptedContent,
+                                     nsnull, 0);
 
         nsCOMPtr<nsIConsoleService> consoleService =
             do_GetService(NS_CONSOLESERVICE_CONTRACTID);
         if (consoleService) {
             nsAutoString message
                 (NS_LITERAL_STRING("Failed Assoc-Req. Received "));
             AppendASCIItoUTF16(
                 mResponseHead->PeekHeader(nsHttp::Assoc_Req),
--- a/netwerk/protocol/http/nsHttpConnection.cpp
+++ b/netwerk/protocol/http/nsHttpConnection.cpp
@@ -67,32 +67,31 @@ static NS_DEFINE_CID(kSocketTransportSer
 using namespace mozilla::net;
 
 //-----------------------------------------------------------------------------
 // nsHttpConnection <public>
 //-----------------------------------------------------------------------------
 
 nsHttpConnection::nsHttpConnection()
     : mTransaction(nsnull)
-    , mLastReadTime(0)
-    , mIdleTimeout(0)
     , mConsiderReusedAfterInterval(0)
     , mConsiderReusedAfterEpoch(0)
     , mCurrentBytesRead(0)
     , mMaxBytesRead(0)
     , mTotalBytesRead(0)
     , mKeepAlive(true) // assume to keep-alive by default
     , mKeepAliveMask(true)
     , mSupportsPipelining(false) // assume low-grade server
     , mIsReused(false)
     , mCompletedProxyConnect(false)
     , mLastTransactionExpectedNoContent(false)
     , mIdleMonitoring(false)
     , mProxyConnectInProgress(false)
     , mHttp1xTransactionCount(0)
+    , mClassification(nsAHttpTransaction::CLASS_GENERAL)
     , mNPNComplete(false)
     , mSetupNPNCalled(false)
     , mUsingSpdy(false)
     , mPriority(nsISupportsPriority::PRIORITY_NORMAL)
     , mReportedSpdy(false)
     , mEverUsedSpdy(false)
 {
     LOG(("Creating nsHttpConnection @%x\n", this));
@@ -137,31 +136,35 @@ nsHttpConnection::~nsHttpConnection()
 
 nsresult
 nsHttpConnection::Init(nsHttpConnectionInfo *info,
                        PRUint16 maxHangTime,
                        nsISocketTransport *transport,
                        nsIAsyncInputStream *instream,
                        nsIAsyncOutputStream *outstream,
                        nsIInterfaceRequestor *callbacks,
-                       nsIEventTarget *callbackTarget)
+                       nsIEventTarget *callbackTarget,
+                       PRIntervalTime rtt)
 {
     NS_ABORT_IF_FALSE(transport && instream && outstream,
                       "invalid socket information");
     LOG(("nsHttpConnection::Init [this=%p "
-         "transport=%p instream=%p outstream=%p]\n",
-         this, transport, instream, outstream));
+         "transport=%p instream=%p outstream=%p rtt=%d]\n",
+         this, transport, instream, outstream,
+         PR_IntervalToMilliseconds(rtt)));
 
     NS_ENSURE_ARG_POINTER(info);
     NS_ENSURE_TRUE(!mConnInfo, NS_ERROR_ALREADY_INITIALIZED);
 
     mConnInfo = info;
-    mSupportsPipelining = mConnInfo->SupportsPipelining();
+    mLastReadTime = PR_IntervalNow();
+    mSupportsPipelining =
+        gHttpHandler->ConnMgr()->SupportsPipelining(mConnInfo);
+    mRtt = rtt;
     mMaxHangTime = PR_SecondsToInterval(maxHangTime);
-    mLastReadTime = PR_IntervalNow();
 
     mSocketTransport = transport;
     mSocketIn = instream;
     mSocketOut = outstream;
     nsresult rv = mSocketTransport->SetEventSink(this, nsnull);
     NS_ENSURE_SUCCESS(rv, rv);
 
     mCallbacks = callbacks;
@@ -338,16 +341,19 @@ nsHttpConnection::Activate(nsAHttpTransa
 
     mPriority = pri;
     if (mTransaction && mUsingSpdy)
         return AddTransaction(trans, pri);
 
     NS_ENSURE_ARG_POINTER(trans);
     NS_ENSURE_TRUE(!mTransaction, NS_ERROR_IN_PROGRESS);
 
+    // reset the read timers to wash away any idle time
+    mLastReadTime = PR_IntervalNow();
+
     // Update security callbacks
     nsCOMPtr<nsIInterfaceRequestor> callbacks;
     nsCOMPtr<nsIEventTarget>        callbackTarget;
     trans->GetSecurityCallbacks(getter_AddRefs(callbacks),
                                 getter_AddRefs(callbackTarget));
     if (callbacks != mCallbacks) {
         mCallbacks.swap(callbacks);
         if (callbacks)
@@ -535,17 +541,17 @@ bool
 nsHttpConnection::CanReuse()
 {
     bool canReuse;
     
     if (mSpdySession)
         canReuse = mSpdySession->CanReuse();
     else
         canReuse = IsKeepAlive();
-    
+
     canReuse = canReuse && (IdleTime() < mIdleTimeout) && IsAlive();
 
     // An idle persistent connection should not have data waiting to be read
     // before a request is sent. Data here is likely a 408 timeout response
     // which we would deal with later on through the restart logic, but that
     // path is more expensive than just closing the socket now.
 
     PRUint32 dataSize;
@@ -621,31 +627,29 @@ nsHttpConnection::IsAlive()
 
 bool
 nsHttpConnection::SupportsPipelining(nsHttpResponseHead *responseHead)
 {
     // SPDY supports infinite parallelism, so no need to pipeline.
     if (mUsingSpdy)
         return false;
 
-    // XXX there should be a strict mode available that disables this
-    // blacklisting.
-
     // assuming connection is HTTP/1.1 with keep-alive enabled
     if (mConnInfo->UsingHttpProxy() && !mConnInfo->UsingSSL()) {
         // XXX check for bad proxy servers...
         return true;
     }
 
-    // XXX what about checking for a Via header? (transparent proxies)
-
     // check for bad origin servers
     const char *val = responseHead->PeekHeader(nsHttp::Server);
+
+    // If there is no server header we will assume it should not be banned
+    // as facebook and some other prominent sites do this
     if (!val)
-        return false; // no header, no love
+        return true;
 
     // The blacklist is indexed by the first character. All of these servers are
     // known to return their identifier as the first thing in the server string,
     // so we can do a leading match. 
 
     static const char *bad_servers[26][6] = {
         { nsnull }, { nsnull }, { nsnull }, { nsnull },                 // a - d
         { "EFAServer/", nsnull },                                       // e
@@ -662,16 +666,18 @@ nsHttpConnection::SupportsPipelining(nsH
     };  
 
     int index = val[0] - 'A'; // the whole table begins with capital letters
     if ((index >= 0) && (index <= 25))
     {
         for (int i = 0; bad_servers[index][i] != nsnull; i++) {
             if (!PL_strncmp (val, bad_servers[index][i], strlen (bad_servers[index][i]))) {
                 LOG(("looks like this server does not support pipelining"));
+                gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+                    mConnInfo, nsHttpConnectionMgr::RedBannedServer, this , 0);
                 return false;
             }
         }
     }
 
     // ok, let's allow pipelining to this server
     return true;
 }
@@ -717,21 +723,29 @@ nsHttpConnection::OnHeadersAvailable(nsA
 
     if ((responseHead->Version() < NS_HTTP_VERSION_1_1) ||
         (requestHead->Version() < NS_HTTP_VERSION_1_1)) {
         // HTTP/1.0 connections are by default NOT persistent
         if (val && !PL_strcasecmp(val, "keep-alive"))
             mKeepAlive = true;
         else
             mKeepAlive = false;
+        
+        // We need at least version 1.1 to use pipelines
+        gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+            mConnInfo, nsHttpConnectionMgr::RedVersionTooLow, this, 0);
     }
     else {
         // HTTP/1.1 connections are by default persistent
-        if (val && !PL_strcasecmp(val, "close")) 
+        if (val && !PL_strcasecmp(val, "close")) {
             mKeepAlive = false;
+            // persistent connections are required for pipelining to work
+            gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+                mConnInfo, nsHttpConnectionMgr::BadExplicitClose, this, 0);
+        }
         else {
             mKeepAlive = true;
 
             // Do not support pipelining when we are establishing
             // an SSL tunnel though an HTTP proxy. Pipelining support
             // determination must be based on comunication with the
             // target server in this case. See bug 422016 for futher
             // details.
@@ -740,40 +754,59 @@ nsHttpConnection::OnHeadersAvailable(nsA
         }
     }
     mKeepAliveMask = mKeepAlive;
 
     // Update the pipelining status in the connection info object
     // and also read it back. It is possible the ci status is
     // locked to false if pipelining has been banned on this ci due to
     // some kind of observed flaky behavior
-    mSupportsPipelining = mConnInfo->SetSupportsPipelining(mSupportsPipelining);
+    if (mSupportsPipelining) {
+        // report the pipelining-compatible header to the connection manager
+        // as positive feedback. This will undo 1 penalty point the host
+        // may have accumulated in the past.
+
+        gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+            mConnInfo, nsHttpConnectionMgr::NeutralExpectedOK, this, 0);
+
+        mSupportsPipelining =
+            gHttpHandler->ConnMgr()->SupportsPipelining(mConnInfo);
+    }
+
+    // If this connection is reserved for revalidations and we are
+    // receiving a document that failed revalidation then switch the
+    // classification to general to avoid pipelining more revalidations behind
+    // it.
+    if (mClassification == nsAHttpTransaction::CLASS_REVALIDATION &&
+        responseHead->Status() != 304) {
+        mClassification = nsAHttpTransaction::CLASS_GENERAL;
+    }
     
     // if this connection is persistent, then the server may send a "Keep-Alive"
     // header specifying the maximum number of times the connection can be
     // reused as well as the maximum amount of time the connection can be idle
     // before the server will close it.  we ignore the max reuse count, because
     // a "keep-alive" connection is by definition capable of being reused, and
     // we only care about being able to reuse it once.  if a timeout is not 
     // specified then we use our advertized timeout value.
     if (mKeepAlive) {
         val = responseHead->PeekHeader(nsHttp::Keep_Alive);
 
         if (!mUsingSpdy) {
             const char *cp = PL_strcasestr(val, "timeout=");
             if (cp)
                 mIdleTimeout = PR_SecondsToInterval((PRUint32) atoi(cp + 8));
             else
-                mIdleTimeout = gHttpHandler->IdleTimeout();
+                mIdleTimeout = gHttpHandler->SpdyTimeout();
         }
         else {
             mIdleTimeout = gHttpHandler->SpdyTimeout();
         }
         
-        LOG(("Connection can be reused [this=%x idle-timeout=%usec]\n",
+        LOG(("Connection can be reused [this=%p idle-timeout=%usec]\n",
              this, PR_IntervalToSeconds(mIdleTimeout)));
     }
 
     if (!mProxyConnectStream)
         HandleAlternateProtocol(responseHead);
 
     // if we're doing an SSL proxy connect, then we need to check whether or not
     // the connect was successful.  if so, then we have to reset the transaction
@@ -938,16 +971,24 @@ nsHttpConnection::ResumeSend()
 
 nsresult
 nsHttpConnection::ResumeRecv()
 {
     LOG(("nsHttpConnection::ResumeRecv [this=%p]\n", this));
 
     NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
 
+    // the mLastReadTime timestamp is used for finding slowish readers
+    // and can be pretty sensitive. For that reason we actually reset it
+    // when we ask to read (resume recv()) so that when we get called back
+    // with actual read data in OnSocketReadable() we are only measuring
+    // the latency between those two acts and not all the processing that
+    // may get done before the ResumeRecv() call
+    mLastReadTime = PR_IntervalNow();
+
     if (mSocketIn)
         return mSocketIn->AsyncWait(this, 0, 0, nsnull);
 
     NS_NOTREACHED("no socket input stream");
     return NS_ERROR_UNEXPECTED;
 }
 
 void
@@ -1060,17 +1101,18 @@ nsHttpConnection::OnReadSegment(const ch
         mSocketOutCondition = NS_OK; // reset condition
 
     return mSocketOutCondition;
 }
 
 nsresult
 nsHttpConnection::OnSocketWritable()
 {
-    LOG(("nsHttpConnection::OnSocketWritable [this=%x]\n", this));
+    LOG(("nsHttpConnection::OnSocketWritable [this=%p] host=%s\n",
+         this, mConnInfo->Host()));
 
     nsresult rv;
     PRUint32 n;
     bool again = true;
 
     do {
         mSocketOutCondition = NS_OK;
 
@@ -1140,17 +1182,17 @@ nsHttpConnection::OnSocketWritable()
             // must wait for the server's response.  we manufacture a status message
             // here to reflect the fact that we are waiting.  this message will be
             // trumped (overwritten) if the server responds quickly.
             //
             mTransaction->OnTransportStatus(mSocketTransport,
                                             nsISocketTransport::STATUS_WAITING_FOR,
                                             LL_ZERO);
 
-            rv = mSocketIn->AsyncWait(this, 0, 0, nsnull); // start reading
+            rv = ResumeRecv(); // start reading
             again = false;
         }
         // write more to the socket until error or end-of-request...
     } while (again);
 
     return rv;
 }
 
@@ -1179,24 +1221,51 @@ nsHttpConnection::OnWriteSegment(char *b
 }
 
 nsresult
 nsHttpConnection::OnSocketReadable()
 {
     LOG(("nsHttpConnection::OnSocketReadable [this=%x]\n", this));
 
     PRIntervalTime now = PR_IntervalNow();
+    PRIntervalTime delta = now - mLastReadTime;
 
-    if (mKeepAliveMask && ((now - mLastReadTime) >= mMaxHangTime)) {
+    if (mKeepAliveMask && (delta >= mMaxHangTime)) {
         LOG(("max hang time exceeded!\n"));
         // give the handler a chance to create a new persistent connection to
         // this host if we've been busy for too long.
         mKeepAliveMask = false;
         gHttpHandler->ProcessPendingQ(mConnInfo);
     }
+
+    // Look for data being sent in bursts with large pauses. If the pauses
+    // are caused by server bottlenecks such as think-time, disk i/o, or
+    // cpu exhaustion (as opposed to network latency) then we generate negative
+    // pipelining feedback to prevent head of line problems
+    
+    // Reduce the estimate of the time since last read by up to 1 RTT to
+    // accommodate exhausted sender TCP congestion windows or minor I/O delays.
+
+    if (delta > mRtt)
+        delta -= mRtt;
+    else
+        delta = 0;
+
+    const PRIntervalTime k400ms  = PR_MillisecondsToInterval(400);
+    const PRIntervalTime k1200ms = PR_MillisecondsToInterval(1200);
+
+    if (delta > k1200ms) {
+        gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+            mConnInfo, nsHttpConnectionMgr::BadSlowReadMajor, this, 0);
+    }
+    else if (delta > k400ms) {
+        gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+            mConnInfo, nsHttpConnectionMgr::BadSlowReadMinor, this, 0);
+    }
+
     mLastReadTime = now;
 
     nsresult rv;
     PRUint32 n;
     bool again = true;
 
     do {
         rv = mTransaction->WriteSegments(this, nsIOService::gDefaultSegmentSize, &n);
@@ -1208,17 +1277,17 @@ nsHttpConnection::OnSocketReadable()
             again = false;
         }
         else {
             mCurrentBytesRead += n;
             mTotalBytesRead += n;
             if (NS_FAILED(mSocketInCondition)) {
                 // continue waiting for the socket if necessary...
                 if (mSocketInCondition == NS_BASE_STREAM_WOULD_BLOCK)
-                    rv = mSocketIn->AsyncWait(this, 0, 0, nsnull);
+                    rv = ResumeRecv();
                 else
                     rv = mSocketInCondition;
                 again = false;
             }
         }
         // read more from the socket until error...
     } while (again);
 
--- a/netwerk/protocol/http/nsHttpConnection.h
+++ b/netwerk/protocol/http/nsHttpConnection.h
@@ -42,16 +42,17 @@
 #include "nsHttp.h"
 #include "nsHttpConnectionInfo.h"
 #include "nsAHttpTransaction.h"
 #include "nsXPIDLString.h"
 #include "nsCOMPtr.h"
 #include "nsAutoPtr.h"
 #include "prinrval.h"
 #include "SpdySession.h"
+#include "mozilla/TimeStamp.h"
 
 #include "nsIStreamListener.h"
 #include "nsISocketTransport.h"
 #include "nsIAsyncInputStream.h"
 #include "nsIAsyncOutputStream.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIEventTarget.h"
 
@@ -87,30 +88,30 @@ public:
     // Initialize the connection:
     //  info        - specifies the connection parameters.
     //  maxHangTime - limits the amount of time this connection can spend on a
     //                single transaction before it should no longer be kept 
     //                alive.  a value of 0xffff indicates no limit.
     nsresult Init(nsHttpConnectionInfo *info, PRUint16 maxHangTime,
                   nsISocketTransport *, nsIAsyncInputStream *,
                   nsIAsyncOutputStream *, nsIInterfaceRequestor *,
-                  nsIEventTarget *);
+                  nsIEventTarget *, PRIntervalTime);
 
     // Activate causes the given transaction to be processed on this
     // connection.  It fails if there is already an existing transaction unless
     // a multiplexing protocol such as SPDY is being used
     nsresult Activate(nsAHttpTransaction *, PRUint8 caps, PRInt32 pri);
 
     // Close the underlying socket transport.
     void Close(nsresult reason);
 
     //-------------------------------------------------------------------------
     // XXX document when these are ok to call
 
-    bool     SupportsPipelining() { return mSupportsPipelining; }
+    bool     SupportsPipelining() { return mSupportsPipelining && IsKeepAlive(); }
     bool     IsKeepAlive() { return mUsingSpdy ||
                                     (mKeepAliveMask && mKeepAlive); }
     bool     CanReuse();   // can this connection be reused?
     bool     CanDirectlyActivate();
 
     // Returns time in seconds for how long connection can be reused.
     PRUint32 TimeToLive();
 
@@ -163,16 +164,22 @@ public:
     void BeginIdleMonitoring();
     void EndIdleMonitoring();
 
     bool UsingSpdy() { return mUsingSpdy; }
 
     // When the connection is active this is called every 15 seconds
     void  ReadTimeoutTick(PRIntervalTime now);
 
+    nsAHttpTransaction::Classifier Classification() { return mClassification; }
+    void Classify(nsAHttpTransaction::Classifier newclass)
+    {
+        mClassification = newclass;
+    }
+
 private:
     // called to cause the underlying socket to start speaking SSL
     nsresult ProxyStartSSL();
 
     nsresult OnTransactionDone(nsresult reason);
     nsresult OnSocketWritable();
     nsresult OnSocketReadable();
 
@@ -212,40 +219,44 @@ private:
     // transaction is open, otherwise it is null.
     nsRefPtr<nsAHttpTransaction>    mTransaction;
 
     nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
     nsCOMPtr<nsIEventTarget>        mCallbackTarget;
 
     nsRefPtr<nsHttpConnectionInfo> mConnInfo;
 
-    PRUint32                        mLastReadTime;
+    PRIntervalTime                  mLastReadTime;
     PRIntervalTime                  mMaxHangTime;    // max download time before dropping keep-alive status
     PRIntervalTime                  mIdleTimeout;    // value of keep-alive: timeout=
     PRIntervalTime                  mConsiderReusedAfterInterval;
     PRIntervalTime                  mConsiderReusedAfterEpoch;
     PRInt64                         mCurrentBytesRead;   // data read per activation
     PRInt64                         mMaxBytesRead;       // max read in 1 activation
     PRInt64                         mTotalBytesRead;     // total data read
 
     nsRefPtr<nsIAsyncInputStream>   mInputOverflow;
 
+    PRIntervalTime                  mRtt;
+
     bool                            mKeepAlive;
     bool                            mKeepAliveMask;
     bool                            mSupportsPipelining;
     bool                            mIsReused;
     bool                            mCompletedProxyConnect;
     bool                            mLastTransactionExpectedNoContent;
     bool                            mIdleMonitoring;
     bool                            mProxyConnectInProgress;
 
     // The number of <= HTTP/1.1 transactions performed on this connection. This
     // excludes spdy transactions.
     PRUint32                        mHttp1xTransactionCount;
 
+    nsAHttpTransaction::Classifier  mClassification;
+
     // SPDY related
     bool                            mNPNComplete;
     bool                            mSetupNPNCalled;
     bool                            mUsingSpdy;
     nsRefPtr<mozilla::net::SpdySession> mSpdySession;
     PRInt32                         mPriority;
     bool                            mReportedSpdy;
 
--- a/netwerk/protocol/http/nsHttpConnectionInfo.cpp
+++ b/netwerk/protocol/http/nsHttpConnectionInfo.cpp
@@ -96,37 +96,16 @@ nsHttpConnectionInfo::Clone() const
 
     // Make sure the anonymous flag is transferred!
     clone->SetAnonymous(mHashKey.CharAt(2) == 'A');
     
     return clone;
 }
 
 bool
-nsHttpConnectionInfo::SupportsPipelining()
-{
-    return mSupportsPipelining;
-}
-
-bool
-nsHttpConnectionInfo::SetSupportsPipelining(bool support)
-{
-    if (!mBannedPipelining)
-        mSupportsPipelining = support;
-    return mSupportsPipelining;
-}
-
-void
-nsHttpConnectionInfo::BanPipelining()
-{
-    mBannedPipelining = true;
-    mSupportsPipelining = false;
-}
-
-bool
 nsHttpConnectionInfo::ShouldForceConnectMethod()
 {
     if (!mProxyInfo)
         return false;
     
     PRUint32 resolveFlags;
     nsresult rv;
     
--- a/netwerk/protocol/http/nsHttpConnectionInfo.h
+++ b/netwerk/protocol/http/nsHttpConnectionInfo.h
@@ -55,18 +55,16 @@ class nsHttpConnectionInfo
 {
 public:
     nsHttpConnectionInfo(const nsACString &host, PRInt32 port,
                          nsProxyInfo* proxyInfo,
                          bool usingSSL=false)
         : mRef(0)
         , mProxyInfo(proxyInfo)
         , mUsingSSL(usingSSL)
-        , mSupportsPipelining(false)
-        , mBannedPipelining(false)
     {
         LOG(("Creating nsHttpConnectionInfo @%x\n", this));
 
         mUsingHttpProxy = (proxyInfo && !nsCRT::strcmp(proxyInfo->Type(), "http"));
 
         SetOriginServer(host, port);
     }
     
@@ -126,25 +124,19 @@ public:
     PRInt32       DefaultPort() const    { return mUsingSSL ? NS_HTTPS_DEFAULT_PORT : NS_HTTP_DEFAULT_PORT; }
     void          SetAnonymous(bool anon)         
                                          { mHashKey.SetCharAt(anon ? 'A' : '.', 2); }
     bool          GetAnonymous()         { return mHashKey.CharAt(2) == 'A'; }
 
     bool          ShouldForceConnectMethod();
     const nsCString &GetHost() { return mHost; }
 
-    bool          SupportsPipelining();
-    bool          SetSupportsPipelining(bool support);
-    void          BanPipelining();
-
 private:
     nsrefcnt               mRef;
     nsCString              mHashKey;
     nsCString              mHost;
     PRInt32                mPort;
     nsCOMPtr<nsProxyInfo>  mProxyInfo;
     bool                   mUsingHttpProxy;
     bool                   mUsingSSL;
-    bool                   mSupportsPipelining;
-    bool                   mBannedPipelining;
 };
 
 #endif // nsHttpConnectionInfo_h__
--- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp
@@ -138,30 +138,32 @@ nsHttpConnectionMgr::EnsureSocketThreadT
 
 nsresult
 nsHttpConnectionMgr::Init(PRUint16 maxConns,
                           PRUint16 maxConnsPerHost,
                           PRUint16 maxConnsPerProxy,
                           PRUint16 maxPersistConnsPerHost,
                           PRUint16 maxPersistConnsPerProxy,
                           PRUint16 maxRequestDelay,
-                          PRUint16 maxPipelinedRequests)
+                          PRUint16 maxPipelinedRequests,
+                          PRUint16 maxOptimisticPipelinedRequests)
 {
     LOG(("nsHttpConnectionMgr::Init\n"));
 
     {
         ReentrantMonitorAutoEnter mon(mReentrantMonitor);
 
         mMaxConns = maxConns;
         mMaxConnsPerHost = maxConnsPerHost;
         mMaxConnsPerProxy = maxConnsPerProxy;
         mMaxPersistConnsPerHost = maxPersistConnsPerHost;
         mMaxPersistConnsPerProxy = maxPersistConnsPerProxy;
         mMaxRequestDelay = maxRequestDelay;
         mMaxPipelinedRequests = maxPipelinedRequests;
+        mMaxOptimisticPipelinedRequests = maxOptimisticPipelinedRequests;
 
         mIsShuttingDown = false;
     }
 
     return EnsureSocketThreadTargetIfOnline();
 }
 
 nsresult
@@ -206,20 +208,17 @@ nsHttpConnectionMgr::PostEvent(nsConnEve
 
     nsresult rv;
     if (!mSocketThreadTarget) {
         NS_WARNING("cannot post event if not initialized");
         rv = NS_ERROR_NOT_INITIALIZED;
     }
     else {
         nsRefPtr<nsIRunnable> event = new nsConnEvent(this, handler, iparam, vparam);
-        if (!event)
-            rv = NS_ERROR_OUT_OF_MEMORY;
-        else
-            rv = mSocketThreadTarget->Dispatch(event, NS_DISPATCH_NORMAL);
+        rv = mSocketThreadTarget->Dispatch(event, NS_DISPATCH_NORMAL);
     }
     return rv;
 }
 
 void
 nsHttpConnectionMgr::PruneDeadConnectionsAfter(PRUint32 timeInSeconds)
 {
     LOG(("nsHttpConnectionMgr::PruneDeadConnectionsAfter\n"));
@@ -344,57 +343,16 @@ nsHttpConnectionMgr::GetSocketThreadTarg
     // care of initializing the socket thread target if that's the case.
     EnsureSocketThreadTargetIfOnline();
 
     ReentrantMonitorAutoEnter mon(mReentrantMonitor);
     NS_IF_ADDREF(*target = mSocketThreadTarget);
     return NS_OK;
 }
 
-void
-nsHttpConnectionMgr::AddTransactionToPipeline(nsHttpPipeline *pipeline)
-{
-    /* called on an existing pipeline anytime we might add more data to an
-       existing pipeline such as when a transaction completes (and
-       therefore the quota has new room), or when we receive headers which
-       might change our view of pipelining */
-   
-    LOG(("nsHttpConnectionMgr::AddTransactionToPipeline [pipeline=%x]\n", pipeline));
-
-    NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
-    PRUint16 avail = pipeline->PipelineDepthAvailable();
-
-    nsRefPtr<nsHttpConnectionInfo> ci;
-    pipeline->GetConnectionInfo(getter_AddRefs(ci));
-    if (ci && avail && ci->SupportsPipelining()) {
-        nsConnectionEntry *ent = mCT.Get(ci->HashKey());
-        if (ent) {
-            // search for another request to pipeline...
-            PRInt32 i, count = ent->mPendingQ.Length();
-            for (i = 0; i < count; ) {
-                nsHttpTransaction *trans = ent->mPendingQ[i];
-                if (trans->Caps() & NS_HTTP_ALLOW_PIPELINING) {
-                    pipeline->AddTransaction(trans);
-
-                    // remove transaction from pending queue
-                    ent->mPendingQ.RemoveElementAt(i);
-                    --count;
-
-                    NS_RELEASE(trans);
-                    if (--avail == 0)
-                        break;
-                }
-                else {
-                    ++i;
-                }
-            }
-        }
-    }
-}
-
 nsresult
 nsHttpConnectionMgr::ReclaimConnection(nsHttpConnection *conn)
 {
     LOG(("nsHttpConnectionMgr::ReclaimConnection [conn=%x]\n", conn));
 
     NS_ADDREF(conn);
     nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgReclaimConnection, 0, conn);
     if (NS_FAILED(rv))
@@ -817,39 +775,35 @@ nsHttpConnectionMgr::PruneDeadConnection
         // mTimeOfNextWakeUp, we need to schedule the pruning to happen
         // after timeToNextExpire.
         if (!self->mTimer || timeOfNextExpire < self->mTimeOfNextWakeUp) {
             self->PruneDeadConnectionsAfter(timeToNextExpire);
         }
     } else {
         self->ConditionallyStopPruneDeadConnectionsTimer();
     }
-#ifdef DEBUG
-    count = ent->mActiveConns.Length();
-    if (count > 0) {
-        for (PRInt32 i=count-1; i>=0; --i) {
-            nsHttpConnection *conn = ent->mActiveConns[i];
-            LOG(("    active conn [%x] with trans [%x]\n", conn, conn->Transaction()));
-        }
-    }
-#endif
-
-    // if this entry is empty, then we can remove it.
-    if (ent->mIdleConns.Length()   == 0 &&
+
+    // if this entry is empty, we have too many entries,
+    // and this doesn't represent some painfully determined
+    // red condition, then we can clean it up and restart from
+    // yellow
+    if (ent->PipelineState()       != PS_RED &&
+        self->mCT.Count()          >  125 &&
+        ent->mIdleConns.Length()   == 0 &&
         ent->mActiveConns.Length() == 0 &&
         ent->mHalfOpens.Length()   == 0 &&
         ent->mPendingQ.Length()    == 0 &&
         ((!ent->mTestedSpdy && !ent->mUsingSpdy) ||
          !gHttpHandler->IsSpdyEnabled() ||
          self->mCT.Count() > 300)) {
         LOG(("    removing empty connection entry\n"));
         return PL_DHASH_REMOVE;
     }
 
-    // else, use this opportunity to compact our arrays...
+    // otherwise use this opportunity to compact our arrays...
     ent->mIdleConns.Compact();
     ent->mActiveConns.Compact();
     ent->mPendingQ.Compact();
 
     return PL_DHASH_NEXT;
 }
 
 PLDHashOperator
@@ -905,135 +859,129 @@ nsHttpConnectionMgr::ShutdownPassCB(cons
 }
 
 //-----------------------------------------------------------------------------
 
 bool
 nsHttpConnectionMgr::ProcessPendingQForEntry(nsConnectionEntry *ent)
 {
     NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+
     LOG(("nsHttpConnectionMgr::ProcessPendingQForEntry [ci=%s]\n",
-        ent->mConnInfo->HashKey().get()));
+         ent->mConnInfo->HashKey().get()));
 
     ProcessSpdyPendingQ(ent);
 
-    PRUint32 i, count = ent->mPendingQ.Length();
-    if (count > 0) {
-        LOG(("  pending-count=%u\n", count));
-        nsHttpTransaction *trans = nsnull;
-        nsHttpConnection *conn = nsnull;
-        for (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.
-            bool alreadyHalfOpen = false;
-            for (PRInt32 j = 0; j < ((PRInt32) ent->mHalfOpens.Length()); j++) {
-                if (ent->mHalfOpens[j]->Transaction() == trans) {
-                    alreadyHalfOpen = true;
-                    break;
-                }
-            }
-
-            GetConnection(ent, trans, alreadyHalfOpen, &conn);
-            if (conn)
+    PRInt32 count = ent->mPendingQ.Length();
+    nsHttpTransaction *trans;
+    nsresult rv;
+    
+    for (PRInt32 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.
+        bool alreadyHalfOpen = false;
+        for (PRInt32 j = 0; j < ((PRInt32) ent->mHalfOpens.Length()); ++j) {
+            if (ent->mHalfOpens[j]->Transaction() == trans) {
+                alreadyHalfOpen = true;
                 break;
-
-            NS_ABORT_IF_FALSE(count == ent->mPendingQ.Length(),
-                              "something mutated pending queue from "
-                              "GetConnection()");
+            }
         }
-        if (conn) {
+
+        rv = TryDispatchTransaction(ent, alreadyHalfOpen, trans);
+        if (NS_SUCCEEDED(rv)) {
             LOG(("  dispatching pending transaction...\n"));
-
-            // remove pending transaction
             ent->mPendingQ.RemoveElementAt(i);
-
-            nsresult rv = DispatchTransaction(ent, trans, trans->Caps(), conn);
-            if (NS_SUCCEEDED(rv))
-                NS_RELEASE(trans);
-            else {
-                LOG(("  DispatchTransaction failed [rv=%x]\n", rv));
-                // on failure, just put the transaction back
-                ent->mPendingQ.InsertElementAt(i, trans);
-                // might be something wrong with the connection... close it.
-                conn->Close(rv);
-            }
-
-            NS_RELEASE(conn);
+            NS_RELEASE(trans);
             return true;
         }
+
+        NS_ABORT_IF_FALSE(count == ((PRInt32) ent->mPendingQ.Length()),
+                          "something mutated pending queue from "
+                          "GetConnection()");
     }
     return false;
 }
 
 bool
-nsHttpConnectionMgr::ProcessPipelinePendingQForEntry(nsConnectionEntry *ent)
+nsHttpConnectionMgr::ProcessPendingQForEntry(nsHttpConnectionInfo *ci)
 {
-    LOG(("nsHttpConnectionMgr::ProcessPipelinePendingQForEntry [ci=%s]\n",
-         ent->mConnInfo->HashKey().get()));
-
-    NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
-
-    if (mMaxPipelinedRequests < 2)
-        return false;
-
-    PRUint32 activeCount = ent->mActiveConns.Length();
-    PRUint32 originalPendingCount = ent->mPendingQ.Length();
-    PRUint32 pendingCount = originalPendingCount;
-    PRUint32 pendingIndex = 0;
-
-    for (PRUint32 activeIndex = 0;
-         (activeIndex < activeCount) && (pendingIndex < pendingCount);
-         ++activeIndex) {
-        nsHttpConnection *conn = ent->mActiveConns[activeIndex];
-
-        if (!conn->SupportsPipelining())
-            continue;
-
-        nsAHttpTransaction *activeTrans = conn->Transaction();
-        if (!activeTrans)
-            continue;
-
-        nsresult rv = NS_OK;
-        PRUint16 avail = activeTrans->PipelineDepthAvailable();
-
-        while (NS_SUCCEEDED(rv) && avail && (pendingIndex < pendingCount)) {
-            nsHttpTransaction *trans = ent->mPendingQ[pendingIndex];
-            if (trans->Caps() & NS_HTTP_ALLOW_PIPELINING) {
-                rv = activeTrans->AddTransaction(trans);
-                if (NS_SUCCEEDED(rv)) {
-                    // remove transaction from pending queue
-                    ent->mPendingQ.RemoveElementAt(pendingIndex);
-
-                    // adjust iterator to reflect coalesced queue
-                    --pendingCount;
-                    --avail;
-                    NS_RELEASE(trans);
-                }
-            }
-            else
-                // skip over this one
-                ++pendingIndex;
-        }
-    }
-    return originalPendingCount != pendingCount;
+    NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+
+    nsConnectionEntry *ent = mCT.Get(ci->HashKey());
+    if (ent)
+        return ProcessPendingQForEntry(ent);
+    return false;
 }
 
 bool
-nsHttpConnectionMgr::ProcessPipelinePendingQForCI(nsHttpConnectionInfo *ci)
+nsHttpConnectionMgr::SupportsPipelining(nsHttpConnectionInfo *ci)
+{
+    NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+
+    nsConnectionEntry *ent = mCT.Get(ci->HashKey());
+    if (ent)
+        return ent->SupportsPipelining();
+    return false;
+}
+
+// nsHttpPipelineFeedback used to hold references across events
+
+class nsHttpPipelineFeedback
 {
-    NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+public:
+    nsHttpPipelineFeedback(nsHttpConnectionInfo *ci,
+                           nsHttpConnectionMgr::PipelineFeedbackInfoType info,
+                           nsHttpConnection *conn, PRUint32 data)
+        : mConnInfo(ci)
+        , mConn(conn)
+        , mInfo(info)
+        , mData(data)
+        {
+        }
+    
+    ~nsHttpPipelineFeedback()
+    {
+    }
     
+    nsRefPtr<nsHttpConnectionInfo> mConnInfo;
+    nsRefPtr<nsHttpConnection> mConn;
+    nsHttpConnectionMgr::PipelineFeedbackInfoType mInfo;
+    PRUint32 mData;
+};
+
+void
+nsHttpConnectionMgr::PipelineFeedbackInfo(nsHttpConnectionInfo *ci,
+                                          PipelineFeedbackInfoType info,
+                                          nsHttpConnection *conn,
+                                          PRUint32 data)
+{
+    if (!ci)
+        return;
+
+    // Post this to the socket thread if we are not running there already
+    if (PR_GetCurrentThread() != gSocketThread) {
+        nsHttpPipelineFeedback *fb = new nsHttpPipelineFeedback(ci, info,
+                                                                conn, data);
+
+        nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgProcessFeedback,
+                                0, fb);
+        if (NS_FAILED(rv))
+            delete fb;
+        return;
+    }
+
     nsConnectionEntry *ent = mCT.Get(ci->HashKey());
 
-    return ent && ProcessPipelinePendingQForEntry(ent);
+    if (ent)
+        ent->OnPipelineFeedbackInfo(info, conn, data);
 }
 
 // we're at the active connection limit if any one of the following conditions is true:
 //  (1) at max-connections
 //  (2) keep-alive enabled and at max-persistent-connections-per-server/proxy
 //  (3) keep-alive disabled and at max-connections-per-server
 bool
 nsHttpConnectionMgr::AtActiveConnectionLimit(nsConnectionEntry *ent, PRUint8 caps)
@@ -1119,132 +1067,468 @@ nsHttpConnectionMgr::ClosePersistentConn
                                                   nsAutoPtr<nsConnectionEntry> &ent,
                                                   void *closure)
 {
     nsHttpConnectionMgr *self = static_cast<nsHttpConnectionMgr *>(closure);
     self->ClosePersistentConnections(ent);
     return PL_DHASH_NEXT;
 }
 
-void
-nsHttpConnectionMgr::GetConnection(nsConnectionEntry *ent,
-                                   nsHttpTransaction *trans,
-                                   bool onlyReusedConnection,
-                                   nsHttpConnection **result)
+bool
+nsHttpConnectionMgr::MakeNewConnection(nsConnectionEntry *ent,
+                                       nsHttpTransaction *trans)
+{
+    LOG(("nsHttpConnectionMgr::MakeNewConnection %p ent=%p trans=%p",
+         this, ent, trans));
+    NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+        
+    // If this host is trying to negotiate a SPDY session right now,
+    // don't create any new connections until the result of the
+    // negotiation is known.
+
+    if (gHttpHandler->IsSpdyEnabled() &&
+        ent->mConnInfo->UsingSSL() &&
+        !ent->mConnInfo->UsingHttpProxy() &&
+        !(trans->Caps() & NS_HTTP_DISALLOW_SPDY) &&
+        (!ent->mTestedSpdy || ent->mUsingSpdy) &&
+        (ent->mHalfOpens.Length() || ent->mActiveConns.Length())) {
+        return false;
+    }
+
+    // We need to make a new connection. If that is going to exceed the
+    // global connection limit then try and free up some room by closing
+    // an idle connection to another host. We know it won't select "ent"
+    // beacuse we have already determined there are no idle connections
+    // to our destination
+
+    if ((mNumIdleConns + mNumActiveConns + 1 >= mMaxConns) && mNumIdleConns)
+        mCT.Enumerate(PurgeExcessIdleConnectionsCB, this);
+
+    if (AtActiveConnectionLimit(ent, trans->Caps()))
+        return false;
+
+    nsresult rv = CreateTransport(ent, trans);
+    if (NS_FAILED(rv))                            /* hard failure */
+        trans->Close(rv);
+
+    return true;
+}
+
+bool
+nsHttpConnectionMgr::AddToShortestPipeline(nsConnectionEntry *ent,
+                                           nsHttpTransaction *trans,
+                                           nsHttpTransaction::Classifier classification,
+                                           PRUint16 depthLimit)
 {
-    LOG(("nsHttpConnectionMgr::GetConnection [ci=%s caps=%x]\n",
-        ent->mConnInfo->HashKey().get(), PRUint32(trans->Caps())));
-
-    // First, see if an existing connection can be used - either an idle
-    // persistent connection or an active spdy session may be reused instead of
-    // establishing a new socket. We do not need to check the connection limits
-    // yet as they govern the maximum number of open connections and reusing
-    // an old connection never increases that.
-
-    *result = nsnull;
-
-    nsHttpConnection *conn = nsnull;
-    bool addConnToActiveList = true;
-
-    if (trans->Caps() & NS_HTTP_ALLOW_KEEPALIVE) {
-
-        // if willing to use spdy look for an active spdy connections
-        // before considering idle http ones.
-        if (gHttpHandler->IsSpdyEnabled()) {
-            conn = GetSpdyPreferredConn(ent);
-            if (conn)
-                addConnToActiveList = false;
+    if (classification == nsAHttpTransaction::CLASS_SOLO)
+        return false;
+
+    PRUint32 maxdepth = ent->MaxPipelineDepth(classification);
+    if (maxdepth == 0) {
+        ent->CreditPenalty();
+        maxdepth = ent->MaxPipelineDepth(classification);
+    }
+    
+    if (ent->PipelineState() == PS_RED)
+        return false;
+
+    if (ent->PipelineState() == PS_YELLOW && ent->mYellowConnection)
+        return false;
+
+    // The maximum depth of a pipeline in yellow is 1 pipeline of
+    // depth 2 for entire CI. When that transaction completes successfully
+    // we transition to green and that expands the allowed depth
+    // to any number of pipelines of up to depth 4.  When a transaction
+    // queued at position 3 or deeper succeeds we open it all the way
+    // up to depths limited only by configuration. The staggered start
+    // in green is simply because a successful yellow test of depth=2
+    // might really just be a race condition (i.e. depth=1 from the
+    // server's point of view), while depth=3 is a stronger indicator -
+    // keeping the pipelines to a modest depth during that period limits
+    // the damage if something is going to go wrong.
+
+    maxdepth = PR_MIN(maxdepth, depthLimit);
+
+    if (maxdepth < 2)
+        return false;
+
+    nsAHttpTransaction *activeTrans;
+
+    nsHttpConnection *bestConn = nsnull;
+    PRUint32 activeCount = ent->mActiveConns.Length();
+    PRUint32 bestConnLength = 0;
+    PRUint32 connLength;
+
+    for (PRUint32 i = 0; i < activeCount; ++i) {
+        nsHttpConnection *conn = ent->mActiveConns[i];
+        if (!conn->SupportsPipelining())
+            continue;
+
+        if (conn->Classification() != classification)
+            continue;
+
+        activeTrans = conn->Transaction();
+        if (!activeTrans ||
+            activeTrans->IsDone() ||
+            NS_FAILED(activeTrans->Status()))
+            continue;
+
+        connLength = activeTrans->PipelineDepth();
+
+        if (maxdepth <= connLength)
+            continue;
+
+        if (!bestConn || (connLength < bestConnLength)) {
+            bestConn = conn;
+            bestConnLength = connLength;
         }
-        
-        // search the idle connection list. Each element in the list
-        // has a reference, so if we remove it from the list into a local
-        // ptr, that ptr now owns the reference
+    }
+
+    if (!bestConn)
+        return false;
+
+    activeTrans = bestConn->Transaction();
+    nsresult rv = activeTrans->AddTransaction(trans);
+    if (NS_FAILED(rv))
+        return false;
+
+    LOG(("   scheduling trans %p on pipeline at position %d\n",
+         trans, trans->PipelinePosition()));
+
+    if ((ent->PipelineState() == PS_YELLOW) && (trans->PipelinePosition() > 1))
+        ent->SetYellowConnection(bestConn);
+    return true;
+}
+
+bool
+nsHttpConnectionMgr::IsUnderPressure(nsConnectionEntry *ent,
+                                   nsHttpTransaction::Classifier classification)
+{
+    // A connection entry is declared to be "under pressure" if most of the 
+    // allowed parallel connections are already used up. In that case we want to
+    // favor existing pipelines over more parallelism so as to reserve any
+    // unused parallel connections for types that don't have existing pipelines.
+    //
+    // The defintion of connection pressure is a pretty liberal one here - that
+    // is why we are using the more restrictive maxPersist* counters.
+    //
+    // Pipelines are also favored when the requested classification is already
+    // using 3 or more of the connections. Failure to do this could result in
+    // one class (e.g. images) establishing self replenishing queues on all the
+    // connections that would starve the other transaction types.
+    
+    PRInt32 currentConns = ent->mActiveConns.Length();
+    PRInt32 maxConns =
+        (ent->mConnInfo->UsingHttpProxy() && !ent->mConnInfo->UsingSSL()) ?
+        mMaxPersistConnsPerProxy : mMaxPersistConnsPerHost;
+
+    // Leave room for at least 3 distinct types to operate concurrently,
+    // this satisfies the typical {html, js/css, img} page.
+    if (currentConns >= (maxConns - 2))
+        return true;                           /* prefer pipeline */
+
+    PRInt32 sameClass = 0;
+    for (PRInt32 i = 0; i < currentConns; ++i)
+        if (classification == ent->mActiveConns[i]->Classification())
+            if (++sameClass == 3)
+                return true;                   /* prefer pipeline */
+    
+    return false;                              /* normal behavior */
+}
+
+// returns OK if a connection is found for the transaction
+// and the transaction is started.
+// returns ERROR_NOT_AVAILABLE if no connection can be found and it
+// should be queued
+nsresult
+nsHttpConnectionMgr::TryDispatchTransaction(nsConnectionEntry *ent,
+                                            bool onlyReusedConnection,
+                                            nsHttpTransaction *trans)
+{
+    NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+    LOG(("nsHttpConnectionMgr::TryDispatchTransaction without conn "
+         "[ci=%s caps=%x]\n",
+         ent->mConnInfo->HashKey().get(), PRUint32(trans->Caps())));
+
+    nsHttpTransaction::Classifier classification = trans->Classification();
+    PRUint8 caps = trans->Caps();
+
+    // no keep-alive means no pipelines either
+    if (!(caps & NS_HTTP_ALLOW_KEEPALIVE))
+        caps = caps & ~NS_HTTP_ALLOW_PIPELINING;
+
+    // 0 - If this should use spdy then dispatch it post haste.
+    // 1 - If there is connection pressure then see if we can pipeline this on
+    //     a connection of a matching type instead of using a new conn
+    // 2 - If there is an idle connection, use it!
+    // 3 - if class == reval or script and there is an open conn of that type
+    //     then pipeline onto shortest pipeline of that class if limits allow
+    // 4 - If we aren't up against our connection limit,
+    //     then open a new one
+    // 5 - Try a pipeline if we haven't already - this will be unusual because
+    //     it implies a low connection pressure situation where
+    //     MakeNewConnection() failed.. that is possible, but unlikely, due to
+    //     global limits
+    // 6 - no connection is available - queue it
+
+    bool attemptedOptimisticPipeline = !(caps & NS_HTTP_ALLOW_PIPELINING);
+
+    // 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()));
+            DispatchTransaction(ent, trans, conn);
+            return NS_OK;
+        }
+    }
+
+    // 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;
+        }
+    }
+
+    // step 2
+    // consider an idle persistent connection
+    if (caps & NS_HTTP_ALLOW_KEEPALIVE) {
+        nsRefPtr<nsHttpConnection> conn;
         while (!conn && (ent->mIdleConns.Length() > 0)) {
             conn = ent->mIdleConns[0];
+            ent->mIdleConns.RemoveElementAt(0);
+            mNumIdleConns--;
+            nsHttpConnection *temp = conn;
+            NS_RELEASE(temp);
+            
             // we check if the connection can be reused before even checking if
             // it is a "matching" connection.
             if (!conn->CanReuse()) {
-                LOG(("   dropping stale connection: [conn=%x]\n", conn));
+                LOG(("   dropping stale connection: [conn=%x]\n", conn.get()));
                 conn->Close(NS_ERROR_ABORT);
-                NS_RELEASE(conn);
+                conn = nsnull;
             }
             else {
-                LOG(("   reusing connection [conn=%x]\n", conn));
+                LOG(("   reusing connection [conn=%x]\n", conn.get()));
                 conn->EndIdleMonitoring();
             }
 
-            ent->mIdleConns.RemoveElementAt(0);
-            mNumIdleConns--;
-
             // If there are no idle connections left at all, we need to make
             // sure that we are not pruning dead connections anymore.
             ConditionallyStopPruneDeadConnectionsTimer();
         }
-    }
-
-    if (!conn) {
-
-        // If the onlyReusedConnection parameter is TRUE, then GetConnection()
-        // does not create new transports under any circumstances.
-        if (onlyReusedConnection)
-            return;
-        
-        if (gHttpHandler->IsSpdyEnabled() &&
-            ent->mConnInfo->UsingSSL() &&
-            !ent->mConnInfo->UsingHttpProxy())
-        {
-            // If this host is trying to negotiate a SPDY session right now,
-            // don't create any new connections until the result of the
-            // negotiation is known.
-    
-            if ((!ent->mTestedSpdy || ent->mUsingSpdy) &&
-                (ent->mHalfOpens.Length() || ent->mActiveConns.Length()))
-                return;
+        if (conn) {
+            // This will update the class of the connection to be the class of
+            // the transaction dispatched on it.
+            AddActiveConn(conn, ent);
+            DispatchTransaction(ent, trans, conn);
+            return NS_OK;
         }
-        
-        // Check if we need to purge an idle connection. Note that we may have
-        // removed one above; if so, this will be a no-op. We do this before
-        // checking the active connection limit to catch the case where we do
-        // have an idle connection, but the purge timer hasn't fired yet.
-        // XXX this just purges a random idle connection.  we should instead
-        // enumerate the entire hash table to find the eldest idle connection.
-        if (mNumIdleConns && mNumIdleConns + mNumActiveConns + 1 >= mMaxConns)
-            mCT.Enumerate(PurgeExcessIdleConnectionsCB, this);
-
-        // Need to make a new TCP connection. First, we check if we've hit
-        // either the maximum connection limit globally or for this particular
-        // host or proxy. If we have, we're done.
-        if (AtActiveConnectionLimit(ent, trans->Caps())) {
-            LOG(("nsHttpConnectionMgr::GetConnection [ci = %s]"
-                 "at active connection limit - will queue\n",
-                 ent->mConnInfo->HashKey().get()));
-            return;
+    }
+
+    // step 3
+    // consider pipelining scripts and revalidations
+    if (!attemptedOptimisticPipeline &&
+        (classification == nsHttpTransaction::CLASS_REVALIDATION ||
+         classification == nsHttpTransaction::CLASS_SCRIPT)) {
+        attemptedOptimisticPipeline = true;
+        if (AddToShortestPipeline(ent, trans,
+                                  classification,
+                                  mMaxOptimisticPipelinedRequests)) {
+            return NS_OK;
         }
-
-        LOG(("nsHttpConnectionMgr::GetConnection Open Connection "
-             "%s %s ent=%p spdy=%d",
-             ent->mConnInfo->Host(), ent->mCoalescingKey.get(),
-             ent, ent->mUsingSpdy));
-        
-        nsresult rv = CreateTransport(ent, trans);
-        if (NS_FAILED(rv))
-            trans->Close(rv);
-        return;
+    }
+
+    // step 4
+    if (!onlyReusedConnection && MakeNewConnection(ent, trans)) {
+        return NS_ERROR_IN_PROGRESS;
     }
-
-    if (addConnToActiveList) {
-        // hold an owning ref to this connection
-        ent->mActiveConns.AppendElement(conn);
-        mNumActiveConns++;
+    
+    // step 5
+    if (caps & NS_HTTP_ALLOW_PIPELINING) {
+        if (AddToShortestPipeline(ent, trans,
+                                  classification,
+                                  mMaxPipelinedRequests)) {
+            return NS_OK;
+        }
     }
     
-    NS_ADDREF(conn);
-    *result = conn;
+    // step 6
+    return NS_ERROR_NOT_AVAILABLE;                /* queue it */
+}
+
+nsresult
+nsHttpConnectionMgr::DispatchTransaction(nsConnectionEntry *ent,
+                                         nsHttpTransaction *trans,
+                                         nsHttpConnection *conn)
+{
+    PRUint8 caps = trans->Caps();
+    PRInt32 priority = trans->Priority();
+
+    LOG(("nsHttpConnectionMgr::DispatchTransaction "
+         "[ci=%s trans=%x caps=%x conn=%x priority=%d]\n",
+         ent->mConnInfo->HashKey().get(), trans, caps, conn, priority));
+
+    if (conn->UsingSpdy()) {
+        LOG(("Spdy Dispatch Transaction via Activate(). Transaction host = %s,"
+             "Connection host = %s\n",
+             trans->ConnectionInfo()->Host(),
+             conn->ConnectionInfo()->Host()));
+        nsresult rv = conn->Activate(trans, caps, priority);
+        NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv), "SPDY Cannot Fail Dispatch");
+        return rv;
+    }
+
+    NS_ABORT_IF_FALSE(conn && !conn->Transaction(),
+                      "DispatchTranaction() on non spdy active connection");
+
+    /* Use pipeline datastructure even if connection does not currently qualify
+       to pipeline this transaction because a different pipeline-eligible
+        transaction might be placed on the active connection */
+
+    nsRefPtr<nsHttpPipeline> pipeline;
+    nsresult rv = BuildPipeline(ent, trans, getter_AddRefs(pipeline));
+    if (!NS_SUCCEEDED(rv))
+        return rv;
+
+    nsRefPtr<nsConnectionHandle> handle = new nsConnectionHandle(conn);
+
+    // give the transaction the indirect reference to the connection.
+    pipeline->SetConnection(handle);
+
+    if (!(caps & NS_HTTP_ALLOW_PIPELINING))
+        conn->Classify(nsAHttpTransaction::CLASS_SOLO);
+    else
+        conn->Classify(trans->Classification());
+
+    rv = conn->Activate(pipeline, caps, priority);
+    if (NS_FAILED(rv)) {
+        LOG(("  conn->Activate failed [rv=%x]\n", rv));
+        ent->mActiveConns.RemoveElement(conn);
+        if (conn == ent->mYellowConnection)
+            ent->OnYellowComplete();
+        mNumActiveConns--;
+        // sever back references to connection, and do so without triggering
+        // a call to ReclaimConnection ;-)
+        pipeline->SetConnection(nsnull);
+        NS_RELEASE(handle->mConn);
+        // destroy the connection
+        NS_RELEASE(conn);
+    }
+
+    // As pipeline goes out of scope it will drop the last refernece to the
+    // pipeline if activation failed, in which case this will destroy
+    // the pipeline, which will cause each the transactions owned by the 
+    // pipeline to be restarted.
+
+    return rv;
 }
 
+nsresult
+nsHttpConnectionMgr::BuildPipeline(nsConnectionEntry *ent,
+                                   nsAHttpTransaction *firstTrans,
+                                   nsHttpPipeline **result)
+{
+    NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+
+    /* form a pipeline here even if nothing is pending so that we
+       can stream-feed it as new transactions arrive */
+
+    /* the first transaction can go in unconditionally - 1 transaction
+       on a nsHttpPipeline object is not a real HTTP pipeline */
+   
+    nsRefPtr<nsHttpPipeline> pipeline = new nsHttpPipeline();
+    pipeline->AddTransaction(firstTrans);
+    NS_ADDREF(*result = pipeline);
+    return NS_OK;
+}
+
+nsresult
+nsHttpConnectionMgr::ProcessNewTransaction(nsHttpTransaction *trans)
+{
+    NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+
+    // since "adds" and "cancels" are processed asynchronously and because
+    // various events might trigger an "add" directly on the socket thread,
+    // we must take care to avoid dispatching a transaction that has already
+    // been canceled (see bug 190001).
+    if (NS_FAILED(trans->Status())) {
+        LOG(("  transaction was canceled... dropping event!\n"));
+        return NS_OK;
+    }
+
+    nsresult rv = NS_OK;
+    nsHttpConnectionInfo *ci = trans->ConnectionInfo();
+    NS_ASSERTION(ci, "no connection info");
+
+    nsConnectionEntry *ent = mCT.Get(ci->HashKey());
+    if (!ent) {
+        nsHttpConnectionInfo *clone = ci->Clone();
+        if (!clone)
+            return NS_ERROR_OUT_OF_MEMORY;
+        ent = new nsConnectionEntry(clone);
+        mCT.Put(ci->HashKey(), ent);
+    }
+
+    // SPDY coalescing of hostnames means we might redirect from this
+    // connection entry onto the preferred one.
+    nsConnectionEntry *preferredEntry = GetSpdyPreferredEnt(ent);
+    if (preferredEntry && (preferredEntry != ent)) {
+        LOG(("nsHttpConnectionMgr::ProcessNewTransaction trans=%p "
+             "redirected via coalescing from %s to %s\n", trans,
+             ent->mConnInfo->Host(), preferredEntry->mConnInfo->Host()));
+
+        ent = preferredEntry;
+    }
+
+    // If we are doing a force reload then close out any existing conns
+    // to this host so that changes in DNS, LBs, etc.. are reflected
+    if (trans->Caps() & NS_HTTP_CLEAR_KEEPALIVES)
+        ClosePersistentConnections(ent);
+
+    // Check if the transaction already has a sticky reference to a connection.
+    // If so, then we can just use it directly by transferring its reference
+    // to the new connection variable instead of searching for a new one
+
+    nsAHttpConnection *wrappedConnection = trans->Connection();
+    nsRefPtr<nsHttpConnection> conn;
+    if (wrappedConnection)
+        conn = dont_AddRef(wrappedConnection->TakeHttpConnection());
+
+    if (conn) {
+        NS_ASSERTION(trans->Caps() & NS_HTTP_STICKY_CONNECTION,
+                     "unexpected caps");
+        NS_ABORT_IF_FALSE(((PRInt32)ent->mActiveConns.IndexOf(conn)) != -1,
+                          "Sticky Connection Not In Active List");
+        trans->SetConnection(nsnull);
+        rv = DispatchTransaction(ent, trans, conn);
+    }
+    else
+        rv = TryDispatchTransaction(ent, false, trans);
+
+    if (NS_FAILED(rv)) {
+        LOG(("  adding transaction to pending queue "
+             "[trans=%p pending-count=%u]\n",
+             trans, ent->mPendingQ.Length()+1));
+        // put this transaction on the pending queue...
+        InsertTransactionSorted(ent->mPendingQ, trans);
+        NS_ADDREF(trans);
+    }
+
+    return NS_OK;
+}
+
+
 void
 nsHttpConnectionMgr::AddActiveConn(nsHttpConnection *conn,
                                    nsConnectionEntry *ent)
 {
     NS_ADDREF(conn);
     ent->mActiveConns.AppendElement(conn);
     mNumActiveConns++;
     ActivateTimeoutTick();
@@ -1271,214 +1555,16 @@ nsHttpConnectionMgr::CreateTransport(nsC
     nsRefPtr<nsHalfOpenSocket> sock = new nsHalfOpenSocket(ent, trans);
     nsresult rv = sock->SetupPrimaryStreams();
     NS_ENSURE_SUCCESS(rv, rv);
 
     ent->mHalfOpens.AppendElement(sock);
     return NS_OK;
 }
 
-nsresult
-nsHttpConnectionMgr::DispatchTransaction(nsConnectionEntry *ent,
-                                         nsHttpTransaction *aTrans,
-                                         PRUint8 caps,
-                                         nsHttpConnection *conn)
-{
-    LOG(("nsHttpConnectionMgr::DispatchTransaction [ci=%s trans=%x caps=%x conn=%x]\n",
-        ent->mConnInfo->HashKey().get(), aTrans, caps, conn));
-    nsresult rv;
-    
-    PRInt32 priority = aTrans->Priority();
-
-    if (conn->UsingSpdy()) {
-        LOG(("Spdy Dispatch Transaction via Activate(). Transaction host = %s,"
-             "Connection host = %s\n",
-             aTrans->ConnectionInfo()->Host(),
-             conn->ConnectionInfo()->Host()));
-        rv = conn->Activate(aTrans, caps, priority);
-        NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv), "SPDY Cannot Fail Dispatch");
-        return rv;
-    }
-
-    nsConnectionHandle *handle = new nsConnectionHandle(conn);
-    if (!handle)
-        return NS_ERROR_OUT_OF_MEMORY;
-    NS_ADDREF(handle);
-
-    nsHttpPipeline *pipeline = nsnull;
-    nsAHttpTransaction *trans = aTrans;
-   
-    /* Use pipeline datastructure even if connection does not currently qualify
-       to pipeline this transaction because a different pipeline-eligible
-       transaction might be placed on the active connection */
-
-    if (BuildPipeline(ent, trans, &pipeline))
-        trans = pipeline;
-
-    // give the transaction the indirect reference to the connection.
-    trans->SetConnection(handle);
-
-    rv = conn->Activate(trans, caps, priority);
-
-    if (NS_FAILED(rv)) {
-        LOG(("  conn->Activate failed [rv=%x]\n", rv));
-        ent->mActiveConns.RemoveElement(conn);
-        mNumActiveConns--;
-        // sever back references to connection, and do so without triggering
-        // a call to ReclaimConnection ;-)
-        trans->SetConnection(nsnull);
-        NS_RELEASE(handle->mConn);
-        // destroy the connection
-        NS_RELEASE(conn);
-    }
-
-    // if we were unable to activate the pipeline, then this will destroy
-    // the pipeline, which will cause each the transactions owned by the 
-    // pipeline to be restarted.
-    NS_IF_RELEASE(pipeline);
-
-    NS_RELEASE(handle);
-    return rv;
-}
-
-bool
-nsHttpConnectionMgr::BuildPipeline(nsConnectionEntry *ent,
-                                   nsAHttpTransaction *firstTrans,
-                                   nsHttpPipeline **result)
-{
-    NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
-
-    if (mMaxPipelinedRequests < 2)
-        return false;
-
-    /* form a pipeline here even if nothing is pending so that we
-       can stream-feed it as new transactions arrive */
-
-    nsHttpPipeline *pipeline = new nsHttpPipeline(mMaxPipelinedRequests);
-
-    /* the first transaction can go in unconditionally - 1 transaction
-       on a nsHttpPipeline object is not a real HTTP pipeline */
-   
-    PRUint16 numAdded = 1;
-    pipeline->AddTransaction(firstTrans);
-
-    if (ent->mConnInfo->SupportsPipelining() &&
-        firstTrans->Caps() & NS_HTTP_ALLOW_PIPELINING) {
-        PRUint32 i = 0;
-        nsHttpTransaction *trans;
-
-        while (i < ent->mPendingQ.Length()) {
-            trans = ent->mPendingQ[i];
-            if (trans->Caps() & NS_HTTP_ALLOW_PIPELINING) {
-                pipeline->AddTransaction(trans);
-
-                // remove transaction from pending queue
-                ent->mPendingQ.RemoveElementAt(i);
-                NS_RELEASE(trans);
-
-                if (++numAdded == mMaxPipelinedRequests)
-                    break;
-            }
-            else {
-                ++i; // skip to next pending transaction
-            }
-        }
-    }
-
-    if (numAdded > 1)
-        LOG(("  pipelined %u transactions\n", numAdded));
- 
-    NS_ADDREF(*result = pipeline);
-    return true;
-}
-
-nsresult
-nsHttpConnectionMgr::ProcessNewTransaction(nsHttpTransaction *trans)
-{
-    NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
-
-    // since "adds" and "cancels" are processed asynchronously and because
-    // various events might trigger an "add" directly on the socket thread,
-    // we must take care to avoid dispatching a transaction that has already
-    // been canceled (see bug 190001).
-    if (NS_FAILED(trans->Status())) {
-        LOG(("  transaction was canceled... dropping event!\n"));
-        return NS_OK;
-    }
-
-    PRUint8 caps = trans->Caps();
-    nsHttpConnectionInfo *ci = trans->ConnectionInfo();
-    NS_ASSERTION(ci, "no connection info");
-
-    nsConnectionEntry *ent = mCT.Get(ci->HashKey());
-    if (!ent) {
-        nsHttpConnectionInfo *clone = ci->Clone();
-        if (!clone)
-            return NS_ERROR_OUT_OF_MEMORY;
-        ent = new nsConnectionEntry(clone);
-        if (!ent)
-            return NS_ERROR_OUT_OF_MEMORY;
-        mCT.Put(ci->HashKey(), ent);
-    }
-
-    // SPDY coalescing of hostnames means we might redirect from this
-    // connection entry onto the preferred one.
-    nsConnectionEntry *preferredEntry = GetSpdyPreferredEnt(ent);
-    if (preferredEntry && (preferredEntry != ent)) {
-        LOG(("nsHttpConnectionMgr::ProcessNewTransaction trans=%p "
-             "redirected via coalescing from %s to %s\n", trans,
-             ent->mConnInfo->Host(), preferredEntry->mConnInfo->Host()));
-
-        ent = preferredEntry;
-    }
-
-    // If we are doing a force reload then close out any existing conns
-    // to this host so that changes in DNS, LBs, etc.. are reflected
-    if (caps & NS_HTTP_CLEAR_KEEPALIVES)
-        ClosePersistentConnections(ent);
-
-    // Check if the transaction already has a sticky reference to a connection.
-    // If so, then we can just use it directly by transferring its reference
-    // to the new connection var instead of calling GetConnection() to search
-    // for an available one.
-
-    nsAHttpConnection *wrappedConnection = trans->Connection();
-    nsHttpConnection  *conn;
-    conn = wrappedConnection ? wrappedConnection->TakeHttpConnection() : nsnull;
-
-    if (conn) {
-        NS_ASSERTION(caps & NS_HTTP_STICKY_CONNECTION, "unexpected caps");
-
-        trans->SetConnection(nsnull);
-    }
-    else
-        GetConnection(ent, trans, false, &conn);
-
-    nsresult rv;
-    if (!conn) {
-        LOG(("  adding transaction to pending queue [trans=%x pending-count=%u]\n",
-            trans, ent->mPendingQ.Length()+1));
-        // put this transaction on the pending queue...
-        InsertTransactionSorted(ent->mPendingQ, trans);
-        NS_ADDREF(trans);
-
-        /* there still remains the possibility that the transaction we just
-           queued could go out right away as a pipelined request on an existing
-           connection */
-        ProcessPipelinePendingQForEntry(ent);
-        rv = NS_OK;
-    }
-    else {
-        rv = DispatchTransaction(ent, trans, caps, conn);
-        NS_RELEASE(conn);
-    }
-
-    return rv;
-}
-
 // 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
 // redirected 'preferred' entry for the same coalescing hash key if
 // coalescing is enabled.
 
 void
 nsHttpConnectionMgr::ProcessSpdyPendingQ(nsConnectionEntry *ent)
@@ -1493,17 +1579,17 @@ nsHttpConnectionMgr::ProcessSpdyPendingQ
         nsHttpTransaction *trans = ent->mPendingQ[index];
 
         if (!(trans->Caps() & NS_HTTP_ALLOW_KEEPALIVE) ||
             trans->Caps() & NS_HTTP_DISALLOW_SPDY)
             continue;
  
         ent->mPendingQ.RemoveElementAt(index);
 
-        nsresult rv = DispatchTransaction(ent, trans, trans->Caps(), conn);
+        nsresult rv = DispatchTransaction(ent, trans, conn);
         if (NS_FAILED(rv)) {
             // this cannot happen, but if due to some bug it does then
             // close the transaction
             NS_ABORT_IF_FALSE(false, "Dispatch SPDY Transaction");
             LOG(("ProcessSpdyPendingQ Dispatch Transaction failed trans=%p\n",
                     trans));
             trans->Close(rv);
         }
@@ -1558,16 +1644,17 @@ nsHttpConnectionMgr::GetSpdyPreferredCon
     return conn;
 }
 
 //-----------------------------------------------------------------------------
 
 void
 nsHttpConnectionMgr::OnMsgShutdown(PRInt32, void *)
 {
+    NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
     LOG(("nsHttpConnectionMgr::OnMsgShutdown\n"));
 
     mCT.Enumerate(ShutdownPassCB, this);
 
     if (mReadTimeoutTick) {
         mReadTimeoutTick->Cancel();
         mReadTimeoutTick = nsnull;
         mReadTimeoutTickArmed = false;
@@ -1589,17 +1676,18 @@ nsHttpConnectionMgr::OnMsgNewTransaction
     if (NS_FAILED(rv))
         trans->Close(rv); // for whatever its worth
     NS_RELEASE(trans);
 }
 
 void
 nsHttpConnectionMgr::OnMsgReschedTransaction(PRInt32 priority, void *param)
 {
-    LOG(("nsHttpConnectionMgr::OnMsgNewTransaction [trans=%p]\n", param));
+    NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+    LOG(("nsHttpConnectionMgr::OnMsgReschedTransaction [trans=%p]\n", param));
 
     nsHttpTransaction *trans = (nsHttpTransaction *) param;
     trans->SetPriority(priority);
 
     nsConnectionEntry *ent = LookupConnectionEntry(trans->ConnectionInfo(),
                                                    nsnull, trans);
 
     if (ent) {
@@ -1611,16 +1699,17 @@ nsHttpConnectionMgr::OnMsgReschedTransac
     }
 
     NS_RELEASE(trans);
 }
 
 void
 nsHttpConnectionMgr::OnMsgCancelTransaction(PRInt32 reason, void *param)
 {
+    NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
     LOG(("nsHttpConnectionMgr::OnMsgCancelTransaction [trans=%p]\n", param));
 
     nsHttpTransaction *trans = (nsHttpTransaction *) param;
     //
     // if the transaction owns a connection and the transaction is not done,
     // then ask the connection to close the transaction.  otherwise, close the
     // transaction directly (removing it from the pending queue first).
     //
@@ -1642,16 +1731,17 @@ nsHttpConnectionMgr::OnMsgCancelTransact
         trans->Close(reason);
     }
     NS_RELEASE(trans);
 }
 
 void
 nsHttpConnectionMgr::OnMsgProcessPendingQ(PRInt32, void *param)
 {
+    NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
     nsHttpConnectionInfo *ci = (nsHttpConnectionInfo *) param;
 
     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 we reach here, it means that we couldn't dispatch a transaction
@@ -1660,16 +1750,17 @@ nsHttpConnectionMgr::OnMsgProcessPending
     }
 
     NS_RELEASE(ci);
 }
 
 void
 nsHttpConnectionMgr::OnMsgPruneDeadConnections(PRInt32, void *)
 {
+    NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
     LOG(("nsHttpConnectionMgr::OnMsgPruneDeadConnections\n"));
 
     // Reset mTimeOfNextWakeUp so that we can find a new shortest value.
     mTimeOfNextWakeUp = LL_MAXUINT;
 
     // check canreuse() for all idle connections plus any active connections on
     // connection entries that are using spdy.
     if (mNumIdleConns || (mNumActiveConns && gHttpHandler->IsSpdyEnabled()))
@@ -1682,16 +1773,17 @@ nsHttpConnectionMgr::OnMsgClosePersisten
     LOG(("nsHttpConnectionMgr::OnMsgClosePersistentConnections\n"));
 
     mCT.Enumerate(ClosePersistentConnectionsCB, this);
 }
 
 void
 nsHttpConnectionMgr::OnMsgReclaimConnection(PRInt32, void *param)
 {
+    NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
     LOG(("nsHttpConnectionMgr::OnMsgReclaimConnection [conn=%p]\n", param));
 
     nsHttpConnection *conn = (nsHttpConnection *) param;
 
     // 
     // 1) remove the connection from the active list
     // 2) if keep-alive, add connection to idle list
     // 3) post event to process the pending transaction queue
@@ -1721,16 +1813,18 @@ nsHttpConnectionMgr::OnMsgReclaimConnect
             // conns. Even when they have 0 transactions on them they are
             // considered active connections. So when one is reclaimed it
             // is really complete and is meant to be shut down and not
             // reused.
             conn->DontReuse();
         }
         
         if (ent->mActiveConns.RemoveElement(conn)) {
+            if (conn == ent->mYellowConnection)
+                ent->OnYellowComplete();
             nsHttpConnection *temp = conn;
             NS_RELEASE(temp);
             mNumActiveConns--;
         }
 
         if (conn->CanReuse()) {
             LOG(("  adding connection to idle list\n"));
             // Keep The idle connection list sorted with the connections that
@@ -1756,17 +1850,16 @@ nsHttpConnectionMgr::OnMsgReclaimConnect
             // time to live among the watched connections, pruning dead
             // connections needs to be done when it can't be reused anymore.
             PRUint32 timeToLive = conn->TimeToLive();
             if(!mTimer || NowInSeconds() + timeToLive < mTimeOfNextWakeUp)
                 PruneDeadConnectionsAfter(timeToLive);
         }
         else {
             LOG(("  connection cannot be reused; closing connection\n"));
-            // make sure the connection is closed and release our reference.
             conn->Close(NS_ERROR_ABORT);
         }
     }
  
     OnMsgProcessPendingQ(NS_OK, ci); // releases |ci|
     NS_RELEASE(conn);
 }
 
@@ -1793,30 +1886,43 @@ nsHttpConnectionMgr::OnMsgUpdateParam(PR
         mMaxPersistConnsPerProxy = value;
         break;
     case MAX_REQUEST_DELAY:
         mMaxRequestDelay = value;
         break;
     case MAX_PIPELINED_REQUESTS:
         mMaxPipelinedRequests = value;
         break;
+    case MAX_OPTIMISTIC_PIPELINED_REQUESTS:
+        mMaxOptimisticPipelinedRequests = value;
+        break;
     default:
         NS_NOTREACHED("unexpected parameter name");
     }
 }
 
 // nsHttpConnectionMgr::nsConnectionEntry
 nsHttpConnectionMgr::nsConnectionEntry::~nsConnectionEntry()
 {
     if (mSpdyPreferred)
         gHttpHandler->ConnMgr()->RemoveSpdyPreferredEnt(mCoalescingKey);
 
     NS_RELEASE(mConnInfo);
 }
 
+void
+nsHttpConnectionMgr::OnMsgProcessFeedback(PRInt32, void *param)
+{
+    NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+    nsHttpPipelineFeedback *fb = (nsHttpPipelineFeedback *)param;
+    
+    PipelineFeedbackInfo(fb->mConnInfo, fb->mInfo, fb->mConn, fb->mData);
+    delete fb;
+}
+
 // Read Timeout Tick handlers
 
 void
 nsHttpConnectionMgr::ActivateTimeoutTick()
 {
     NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
     LOG(("nsHttpConnectionMgr::ActivateTimeoutTick() "
          "this=%p mReadTimeoutTick=%p\n"));
@@ -2075,16 +2181,17 @@ nsHalfOpenSocket::SetupStreams(nsISocket
 
 nsresult
 nsHttpConnectionMgr::nsHalfOpenSocket::SetupPrimaryStreams()
 {
     NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
 
     nsresult rv;
 
+    mPrimarySynStarted = mozilla::TimeStamp::Now();
     rv = SetupStreams(getter_AddRefs(mSocketTransport),
                       getter_AddRefs(mStreamIn),
                       getter_AddRefs(mStreamOut),
                       false);
     LOG(("nsHalfOpenSocket::SetupPrimaryStream [this=%p ent=%s rv=%x]",
          this, mEnt->mConnInfo->Host(), rv));
     if (NS_FAILED(rv)) {
         if (mStreamOut)
@@ -2094,16 +2201,17 @@ nsHttpConnectionMgr::nsHalfOpenSocket::S
         mSocketTransport = nsnull;
     }
     return rv;
 }
 
 nsresult
 nsHttpConnectionMgr::nsHalfOpenSocket::SetupBackupStreams()
 {
+    mBackupSynStarted = mozilla::TimeStamp::Now();
     nsresult rv = SetupStreams(getter_AddRefs(mBackupTransport),
                                getter_AddRefs(mBackupStreamIn),
                                getter_AddRefs(mBackupStreamOut),
                                true);
     LOG(("nsHalfOpenSocket::SetupBackupStream [this=%p ent=%s rv=%x]",
          this, mEnt->mConnInfo->Host(), rv));
     if (NS_FAILED(rv)) {
         if (mBackupStreamOut)
@@ -2215,31 +2323,38 @@ nsHalfOpenSocket::OnOutputStreamReady(ns
     LOG(("nsHalfOpenSocket::OnOutputStreamReady "
          "Created new nshttpconnection %p\n", conn.get()));
 
     nsCOMPtr<nsIInterfaceRequestor> callbacks;
     nsCOMPtr<nsIEventTarget>        callbackTarget;
     mTransaction->GetSecurityCallbacks(getter_AddRefs(callbacks),
                                        getter_AddRefs(callbackTarget));
     if (out == mStreamOut) {
+        mozilla::TimeDuration rtt = 
+            mozilla::TimeStamp::Now() - mPrimarySynStarted;
         rv = conn->Init(mEnt->mConnInfo,
                         gHttpHandler->ConnMgr()->mMaxRequestDelay,
                         mSocketTransport, mStreamIn, mStreamOut,
-                        callbacks, callbackTarget);
+                        callbacks, callbackTarget,
+                        PR_MillisecondsToInterval(rtt.ToMilliseconds()));
 
         // The nsHttpConnection object now owns these streams and sockets
         mStreamOut = nsnull;
         mStreamIn = nsnull;
         mSocketTransport = nsnull;
     }
     else {
+        mozilla::TimeDuration rtt = 
+            mozilla::TimeStamp::Now() - mBackupSynStarted;
+        
         rv = conn->Init(mEnt->mConnInfo,
                         gHttpHandler->ConnMgr()->mMaxRequestDelay,
                         mBackupTransport, mBackupStreamIn, mBackupStreamOut,
-                        callbacks, callbackTarget);
+                        callbacks, callbackTarget,
+                        PR_MillisecondsToInterval(rtt.ToMilliseconds()));
 
         // The nsHttpConnection object now owns these streams and sockets
         mBackupStreamOut = nsnull;
         mBackupStreamIn = nsnull;
         mBackupTransport = nsnull;
     }
 
     if (NS_FAILED(rv)) {
@@ -2251,17 +2366,16 @@ nsHalfOpenSocket::OnOutputStreamReady(ns
     // if this is still in the pending list, remove it and dispatch it
     index = mEnt->mPendingQ.IndexOf(mTransaction);
     if (index != -1) {
         mEnt->mPendingQ.RemoveElementAt(index);
         nsHttpTransaction *temp = mTransaction;
         NS_RELEASE(temp);
         gHttpHandler->ConnMgr()->AddActiveConn(conn, mEnt);
         rv = gHttpHandler->ConnMgr()->DispatchTransaction(mEnt, mTransaction,
-                                                          mTransaction->Caps(),
                                                           conn);
     }
     else {
         // this transaction was dispatched off the pending q before all the
         // sockets established themselves.
 
         // We need to establish a small non-zero idle timeout so the connection
         // mgr perceives this socket as suitable for persistent connection reuse
@@ -2387,16 +2501,265 @@ nsHttpConnectionMgr::nsConnectionHandle:
 }
 
 bool
 nsHttpConnectionMgr::nsConnectionHandle::IsProxyConnectInProgress()
 {
     return mConn->IsProxyConnectInProgress();
 }
 
+// nsConnectionEntry
+
+nsHttpConnectionMgr::
+nsConnectionEntry::nsConnectionEntry(nsHttpConnectionInfo *ci)
+    : mConnInfo(ci)
+    , mPipelineState(PS_YELLOW)
+    , mYellowGoodEvents(0)
+    , mYellowBadEvents(0)
+    , mYellowConnection(nsnull)
+    , mGreenDepth(kPipelineOpen)
+    , mPipeliningPenalty(0)
+    , mUsingSpdy(false)
+    , mTestedSpdy(false)
+    , mSpdyPreferred(false)
+{
+    NS_ADDREF(mConnInfo);
+    if (gHttpHandler->GetPipelineAggressive()) {
+        mGreenDepth = kPipelineUnlimited;
+        mPipelineState = PS_GREEN;
+    }
+    mInitialGreenDepth = mGreenDepth;
+    memset(mPipeliningClassPenalty, 0, sizeof(PRInt16) * nsAHttpTransaction::CLASS_MAX);
+}
+
+bool
+nsHttpConnectionMgr::nsConnectionEntry::SupportsPipelining()
+{
+    return mPipelineState != nsHttpConnectionMgr::PS_RED;
+}
+
+nsHttpConnectionMgr::PipeliningState
+nsHttpConnectionMgr::nsConnectionEntry::PipelineState()
+{
+    return mPipelineState;
+}
+    
+void
+nsHttpConnectionMgr::
+nsConnectionEntry::OnPipelineFeedbackInfo(
+    nsHttpConnectionMgr::PipelineFeedbackInfoType info,
+    nsHttpConnection *conn,
+    PRUint32 data)
+{
+    NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+    
+    if (mPipelineState == PS_YELLOW) {
+        if (info & kPipelineInfoTypeBad)
+            mYellowBadEvents++;
+        else if (info & (kPipelineInfoTypeNeutral | kPipelineInfoTypeGood))
+            mYellowGoodEvents++;
+    }
+    
+    if (mPipelineState == PS_GREEN && info == GoodCompletedOK) {
+        PRInt32 depth = data;
+        LOG(("Transaction completed at pipeline depty of %d. Host = %s\n",
+             depth, mConnInfo->Host()));
+
+        if (depth >= 3)
+            mGreenDepth = kPipelineUnlimited;
+    }
+
+    nsAHttpTransaction::Classifier classification;
+    if (conn)
+        classification = conn->Classification();
+    else if (info == BadInsufficientFraming)
+        classification = (nsAHttpTransaction::Classifier) data;
+    else
+        classification = nsAHttpTransaction::CLASS_SOLO;
+
+    if (gHttpHandler->GetPipelineAggressive() &&
+        info & kPipelineInfoTypeBad &&
+        info != BadExplicitClose &&
+        info != RedVersionTooLow &&
+        info != RedBannedServer &&
+        info != RedCorruptedContent &&
+        info != BadInsufficientFraming) {
+        LOG(("minor negative feedback ignored "
+             "because of pipeline aggressive mode"));
+    }
+    else if (info & kPipelineInfoTypeBad) {
+        if ((info & kPipelineInfoTypeRed) && (mPipelineState != PS_RED)) {
+            LOG(("transition to red from %d. Host = %s.\n",
+                 mPipelineState, mConnInfo->Host()));
+            mPipelineState = PS_RED;
+            mPipeliningPenalty = 0;
+        }
+
+        if (mLastCreditTime.IsNull())
+            mLastCreditTime = mozilla::TimeStamp::Now();
+
+        // Red* events impact the host globally via mPipeliningPenalty, while
+        // Bad* events impact the per class penalty.
+        
+        // The individual penalties should be < 16bit-signed-maxint - 25000
+        // (approx 7500). Penalties are paid-off either when something promising
+        // happens (a successful transaction, or promising headers) or when
+        // time goes by at a rate of 1 penalty point every 16 seconds.
+
+        switch (info) {
+        case RedVersionTooLow:
+            mPipeliningPenalty += 1000;
+            break;
+        case RedBannedServer:
+            mPipeliningPenalty += 7000;
+            break;
+        case RedCorruptedContent:
+            mPipeliningPenalty += 7000;
+            break;
+        case RedCanceledPipeline:
+            mPipeliningPenalty += 60;
+            break;
+        case BadExplicitClose:
+            mPipeliningClassPenalty[classification] += 250;
+            break;
+        case BadSlowReadMinor:
+            mPipeliningClassPenalty[classification] += 5;
+            break;
+        case BadSlowReadMajor:
+            mPipeliningClassPenalty[classification] += 25;
+            break;
+        case BadInsufficientFraming:
+            mPipeliningClassPenalty[classification] += 7000;
+            break;
+
+        default:
+            NS_ABORT_IF_FALSE(0, "Unknown Bad/Red Pipeline Feedback Event");
+        }
+        
+        mPipeliningPenalty = PR_MIN(mPipeliningPenalty, 25000);
+        mPipeliningClassPenalty[classification] = PR_MIN(mPipeliningClassPenalty[classification], 25000);
+            
+        LOG(("Assessing red penalty to %s class %d for event %d. "
+             "Penalty now %d, throttle[%d] = %d\n", mConnInfo->Host(),
+             classification, info, mPipeliningPenalty, classification,
+             mPipeliningClassPenalty[classification]));
+    }
+    else {
+        // hand out credits for neutral and good events such as
+        // "headers look ok" events
+
+        mPipeliningPenalty = PR_MAX(mPipeliningPenalty - 1, 0);
+        mPipeliningClassPenalty[classification] = PR_MAX(mPipeliningClassPenalty[classification] - 1, 0);
+    }
+
+    if (mPipelineState == PS_RED && !mPipeliningPenalty)
+    {
+        LOG(("transition %s to yellow\n", mConnInfo->Host()));
+        mPipelineState = PS_YELLOW;
+        mYellowConnection = nsnull;
+    }
+}
+
+void
+nsHttpConnectionMgr::
+nsConnectionEntry::SetYellowConnection(nsHttpConnection *conn)
+{
+    NS_ABORT_IF_FALSE(!mYellowConnection && mPipelineState == PS_YELLOW,
+                      "yellow connection already set or state is not yellow");
+    mYellowConnection = conn;
+    mYellowGoodEvents = mYellowBadEvents = 0;
+}
+
+void
+nsHttpConnectionMgr::nsConnectionEntry::OnYellowComplete()
+{
+    if (mPipelineState == PS_YELLOW) {
+        if (mYellowGoodEvents && !mYellowBadEvents) {
+            LOG(("transition %s to green\n", mConnInfo->Host()));
+            mPipelineState = PS_GREEN;
+            mGreenDepth = mInitialGreenDepth;
+        }
+        else {
+            // The purpose of the yellow state is to witness at least
+            // one successful pipelined transaction without seeing any
+            // kind of negative feedback before opening the flood gates.
+            // If we haven't confirmed that, then transfer back to red.
+            LOG(("transition %s to red from yellow return\n",
+                 mConnInfo->Host()));
+            mPipelineState = PS_RED;
+        }
+    }
+
+    mYellowConnection = nsnull;
+}
+
+void
+nsHttpConnectionMgr::nsConnectionEntry::CreditPenalty()
+{
+    if (mLastCreditTime.IsNull())
+        return;
+    
+    // Decrease penalty values by 1 for every 16 seconds
+    // (i.e 3.7 per minute, or 1000 every 4h20m)
+
+    mozilla::TimeStamp now = mozilla::TimeStamp::Now();
+    mozilla::TimeDuration elapsedTime = now - mLastCreditTime;
+    PRUint32 creditsEarned =
+        static_cast<PRUint32>(elapsedTime.ToSeconds()) >> 4;
+    
+    bool failed = false;
+    if (creditsEarned > 0) {
+        mPipeliningPenalty = 
+            PR_MAX(PRInt32(mPipeliningPenalty - creditsEarned), 0);
+        if (mPipeliningPenalty > 0)
+            failed = true;
+        
+        for (PRInt32 i = 0; i < nsAHttpTransaction::CLASS_MAX; ++i) {
+            mPipeliningClassPenalty[i]  =
+                PR_MAX(PRInt32(mPipeliningClassPenalty[i] - creditsEarned), 0);
+            failed = failed || (mPipeliningClassPenalty[i] > 0);
+        }
+
+        // update last credit mark to reflect elapsed time
+        mLastCreditTime +=
+            mozilla::TimeDuration::FromSeconds(creditsEarned << 4);
+    }
+    else {
+        failed = true;                         /* just assume this */
+    }
+
+    // If we are no longer red then clear the credit counter - you only
+    // get credits for time spent in the red state
+    if (!failed)
+        mLastCreditTime = mozilla::TimeStamp();    /* reset to null timestamp */
+
+    if (mPipelineState == PS_RED && !mPipeliningPenalty)
+    {
+        LOG(("transition %s to yellow based on time credit\n",
+             mConnInfo->Host()));
+        mPipelineState = PS_YELLOW;
+        mYellowConnection = nsnull;
+    }    
+}
+
+PRUint32
+nsHttpConnectionMgr::
+nsConnectionEntry::MaxPipelineDepth(nsAHttpTransaction::Classifier aClass)
+{
+    // Still subject to configuration limit no matter return value
+    
+    if ((mPipelineState == PS_RED) || (mPipeliningClassPenalty[aClass] > 0))
+        return 0;
+
+    if (mPipelineState == PS_YELLOW)
+        return kPipelineRestricted;
+
+    return mGreenDepth;
+}
+
 bool
 nsHttpConnectionMgr::nsConnectionHandle::LastTransactionExpectedNoContent()
 {
     return mConn->LastTransactionExpectedNoContent();
 }
 
 void
 nsHttpConnectionMgr::
--- a/netwerk/protocol/http/nsHttpConnectionMgr.h
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.h
@@ -44,16 +44,17 @@
 #include "nsHttpTransaction.h"
 #include "nsTArray.h"
 #include "nsThreadUtils.h"
 #include "nsClassHashtable.h"
 #include "nsDataHashtable.h"
 #include "nsAutoPtr.h"
 #include "mozilla/ReentrantMonitor.h"
 #include "nsISocketTransportService.h"
+#include "mozilla/TimeStamp.h"
 
 #include "nsIObserver.h"
 #include "nsITimer.h"
 #include "nsIX509Cert3.h"
 
 class nsHttpPipeline;
 
 //-----------------------------------------------------------------------------
@@ -67,32 +68,34 @@ public:
     // parameter names
     enum nsParamName {
         MAX_CONNECTIONS,
         MAX_CONNECTIONS_PER_HOST,
         MAX_CONNECTIONS_PER_PROXY,
         MAX_PERSISTENT_CONNECTIONS_PER_HOST,
         MAX_PERSISTENT_CONNECTIONS_PER_PROXY,
         MAX_REQUEST_DELAY,
-        MAX_PIPELINED_REQUESTS
+        MAX_PIPELINED_REQUESTS,
+        MAX_OPTIMISTIC_PIPELINED_REQUESTS
     };
 
     //-------------------------------------------------------------------------
     // NOTE: functions below may only be called on the main thread.
     //-------------------------------------------------------------------------
 
     nsHttpConnectionMgr();
 
     nsresult Init(PRUint16 maxConnections,
                   PRUint16 maxConnectionsPerHost,
                   PRUint16 maxConnectionsPerProxy,
                   PRUint16 maxPersistentConnectionsPerHost,
                   PRUint16 maxPersistentConnectionsPerProxy,
                   PRUint16 maxRequestDelay,
-                  PRUint16 maxPipelinedRequests);
+                  PRUint16 maxPipelinedRequests,
+                  PRUint16 maxOptimisticPipelinedRequests);
     nsresult Shutdown();
 
     //-------------------------------------------------------------------------
     // NOTE: functions below may be called on any thread.
     //-------------------------------------------------------------------------
 
     // Schedules next pruning of dead connection to happen after
     // given time.
@@ -133,70 +136,185 @@ public:
     // been initialized.
     nsresult UpdateParam(nsParamName name, PRUint16 value);
 
     // Lookup/Cancel HTTP->SPDY redirections
     bool GetSpdyAlternateProtocol(nsACString &key);
     void ReportSpdyAlternateProtocol(nsHttpConnection *);
     void RemoveSpdyAlternateProtocol(nsACString &key);
 
+    // Pipielining Interfaces and Datatypes
+
+    const static PRUint32 kPipelineInfoTypeMask = 0xffff0000;
+    const static PRUint32 kPipelineInfoIDMask   = ~kPipelineInfoTypeMask;
+
+    const static PRUint32 kPipelineInfoTypeRed     = 0x00010000;
+    const static PRUint32 kPipelineInfoTypeBad     = 0x00020000;
+    const static PRUint32 kPipelineInfoTypeNeutral = 0x00040000;
+    const static PRUint32 kPipelineInfoTypeGood    = 0x00080000;
+
+    enum PipelineFeedbackInfoType
+    {
+        // Used when an HTTP response less than 1.1 is received
+        RedVersionTooLow = kPipelineInfoTypeRed | kPipelineInfoTypeBad | 0x0001,
+
+        // Used when a HTTP Server response header that is on the banned from
+        // pipelining list is received
+        RedBannedServer = kPipelineInfoTypeRed | kPipelineInfoTypeBad | 0x0002,
+    
+        // Used when a response is terminated early, or when it fails an
+        // integrity check such as assoc-req or md5
+        RedCorruptedContent = kPipelineInfoTypeRed | kPipelineInfoTypeBad | 0x0004,
+
+        // Used when a pipeline is only partly satisfied - for instance if the
+        // server closed the connection after responding to the first
+        // request but left some requests unprocessed.
+        RedCanceledPipeline = kPipelineInfoTypeRed | kPipelineInfoTypeBad | 0x0005,
+
+        // Used when a connection that we expected to stay persistently open
+        // was closed by the server. Not used when simply timed out.
+        BadExplicitClose = kPipelineInfoTypeBad | 0x0003,
+
+        // Used when there is a gap of around 400 - 1200ms in between data being
+        // read from the server
+        BadSlowReadMinor = kPipelineInfoTypeBad | 0x0006,
+
+        // Used when there is a gap of > 1200ms in between data being
+        // read from the server
+        BadSlowReadMajor = kPipelineInfoTypeBad | 0x0007,
+
+        // Used when a response is received that is not framed with either chunked
+        // encoding or a complete content length.
+        BadInsufficientFraming = kPipelineInfoTypeBad | 0x0008,
+
+        // Used when a response is received that has headers that appear to support
+        // pipelining.
+        NeutralExpectedOK = kPipelineInfoTypeNeutral | 0x0009,
+
+        // Used when a response is received successfully to a pipelined request.
+        GoodCompletedOK = kPipelineInfoTypeGood | 0x000A
+    };
+    
+    // called to provide information relevant to the pipelining manager
+    // may be called from any thread
+    void     PipelineFeedbackInfo(nsHttpConnectionInfo *,
+                                  PipelineFeedbackInfoType info,
+                                  nsHttpConnection *,
+                                  PRUint32);
+
     //-------------------------------------------------------------------------
     // NOTE: functions below may be called only on the socket thread.
     //-------------------------------------------------------------------------
 
-    // removes the next transactions for the specified connection from the
-    // pending transaction queue.
-    void AddTransactionToPipeline(nsHttpPipeline *);
-
     // called to force the transaction queue to be processed once more, giving
     // preference to the specified connection.
     nsresult ProcessPendingQ(nsHttpConnectionInfo *);
+    bool     ProcessPendingQForEntry(nsHttpConnectionInfo *);
 
     // 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.
     void ReportSpdyConnection(nsHttpConnection *, bool usingSpdy);
 
     
-    // Similar to ProcessPendingQ, but only considers adding transactions to
-    // existing connections
-    bool     ProcessPipelinePendingQForCI(nsHttpConnectionInfo *);
+    bool     SupportsPipelining(nsHttpConnectionInfo *);
+
 private:
     virtual ~nsHttpConnectionMgr();
+
+    enum PipeliningState {
+        // Host has proven itself pipeline capable through past experience and
+        // large pipeline depths are allowed on multiple connections.
+        PS_GREEN,
+
+        // Not enough information is available yet with this host to be certain
+        // of pipeline capability. Small pipelines on a single connection are
+        // allowed in order to decide whether or not to proceed to green.
+        PS_YELLOW,
+
+        // One or more bad events has happened that indicate that pipelining
+        // to this host (or a particular type of transaction with this host)
+        // is a bad idea. Pipelining is not currently allowed, but time and
+        // other positive experiences will eventually allow it to try again.
+        PS_RED
+    };
+    
     class nsHalfOpenSocket;
-    
+
     // nsConnectionEntry
     //
     // mCT maps connection info hash key to nsConnectionEntry object, which
     // contains list of active and idle connections as well as the list of
     // pending transactions.
     //
-    struct nsConnectionEntry
+    class nsConnectionEntry
     {
-        nsConnectionEntry(nsHttpConnectionInfo *ci)
-          : mConnInfo(ci),
-            mUsingSpdy(false),
-            mTestedSpdy(false),
-            mSpdyPreferred(false)
-        {
-            NS_ADDREF(mConnInfo);
-        }
+    public:
+        nsConnectionEntry(nsHttpConnectionInfo *ci);
         ~nsConnectionEntry();
 
         nsHttpConnectionInfo        *mConnInfo;
         nsTArray<nsHttpTransaction*> mPendingQ;    // pending transaction queue
         nsTArray<nsHttpConnection*>  mActiveConns; // active connections
         nsTArray<nsHttpConnection*>  mIdleConns;   // idle persistent connections
         nsTArray<nsHalfOpenSocket*>  mHalfOpens;
 
+        // Pipeline depths for various states
+        const static PRUint32 kPipelineUnlimited  = 1024; // fully open - extended green
+        const static PRUint32 kPipelineOpen       = 6;    // 6 on each conn - normal green
+        const static PRUint32 kPipelineRestricted = 2;    // 2 on just 1 conn in yellow
+        
+        nsHttpConnectionMgr::PipeliningState PipelineState();
+        void OnPipelineFeedbackInfo(
+            nsHttpConnectionMgr::PipelineFeedbackInfoType info,
+            nsHttpConnection *, PRUint32);
+        bool SupportsPipelining();
+        PRUint32 MaxPipelineDepth(nsAHttpTransaction::Classifier classification);
+        void CreditPenalty();
+
+        nsHttpConnectionMgr::PipeliningState mPipelineState;
+
+        void SetYellowConnection(nsHttpConnection *);
+        void OnYellowComplete();
+        PRUint32                  mYellowGoodEvents;
+        PRUint32                  mYellowBadEvents;
+        nsHttpConnection         *mYellowConnection;
+
+        // initialGreenDepth is the max depth of a pipeline when you first
+        // transition to green. Normally this is kPipelineOpen, but it can
+        // be kPipelineUnlimited in aggressive mode.
+        PRUint32                  mInitialGreenDepth;
+
+        // greenDepth is the current max allowed depth of a pipeline when
+        // in the green state. Normally this starts as kPipelineOpen and
+        // grows to kPipelineUnlimited after a pipeline of depth 3 has been
+        // successfully transacted.
+        PRUint32                  mGreenDepth;
+
+        // pipeliningPenalty is the current amount of penalty points this host
+        // entry has earned for participating in events that are not conducive
+        // to good pipelines - such as head of line blocking, canceled pipelines,
+        // etc.. penalties are paid back either through elapsed time or simply
+        // healthy transactions. Having penalty points means that this host is
+        // not currently eligible for pipelines.
+        PRInt16                   mPipeliningPenalty;
+
+        // some penalty points only apply to particular classifications of
+        // transactions - this allows a server that perhaps has head of line
+        // blocking problems on CGI queries to still serve JS pipelined.
+        PRInt16                   mPipeliningClassPenalty[nsAHttpTransaction::CLASS_MAX];
+
+        // for calculating penalty repair credits
+        mozilla::TimeStamp        mLastCreditTime;
+
         // Spdy sometimes resolves the address in the socket manager in order
         // to re-coalesce sharded HTTP hosts. The dotted decimal address is
         // combined with the Anonymous flag from the connection information
         // to build the hash key for hosts in the same ip pool.
         //
         // When a set of hosts are coalesced together one of them is marked
         // mSpdyPreferred. The mapping is maintained in the connection mananger
         // mSpdyPreferred hash.
@@ -271,16 +389,19 @@ private:
 
     private:
         nsConnectionEntry              *mEnt;
         nsRefPtr<nsHttpTransaction>    mTransaction;
         nsCOMPtr<nsISocketTransport>   mSocketTransport;
         nsCOMPtr<nsIAsyncOutputStream> mStreamOut;
         nsCOMPtr<nsIAsyncInputStream>  mStreamIn;
 
+        mozilla::TimeStamp             mPrimarySynStarted;
+        mozilla::TimeStamp             mBackupSynStarted;
+
         // for syn retry
         nsCOMPtr<nsITimer>             mSynTimer;
         nsCOMPtr<nsISocketTransport>   mBackupTransport;
         nsCOMPtr<nsIAsyncOutputStream> mBackupStreamOut;
         nsCOMPtr<nsIAsyncInputStream>  mBackupStreamIn;
     };
     friend class nsHalfOpenSocket;
 
@@ -295,45 +416,57 @@ private:
     // connection limits
     PRUint16 mMaxConns;
     PRUint16 mMaxConnsPerHost;
     PRUint16 mMaxConnsPerProxy;
     PRUint16 mMaxPersistConnsPerHost;
     PRUint16 mMaxPersistConnsPerProxy;
     PRUint16 mMaxRequestDelay; // in seconds
     PRUint16 mMaxPipelinedRequests;
-
+    PRUint16 mMaxOptimisticPipelinedRequests;
     bool mIsShuttingDown;
 
     //-------------------------------------------------------------------------
     // NOTE: these members are only accessed on the socket transport thread
     //-------------------------------------------------------------------------
 
     static PLDHashOperator ProcessOneTransactionCB(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     IsUnderPressure(nsConnectionEntry *ent,
+                             nsHttpTransaction::Classifier classification);
     bool     AtActiveConnectionLimit(nsConnectionEntry *, PRUint8 caps);
-    bool     ProcessPipelinePendingQForEntry(nsConnectionEntry *);
-    void     GetConnection(nsConnectionEntry *, nsHttpTransaction *,
-                           bool, nsHttpConnection **);
-    nsresult DispatchTransaction(nsConnectionEntry *, nsHttpTransaction *,
-                                 PRUint8 caps, nsHttpConnection *);
-    bool     BuildPipeline(nsConnectionEntry *, nsAHttpTransaction *, nsHttpPipeline **);
+    nsresult TryDispatchTransaction(nsConnectionEntry *ent,
+                                    bool onlyReusedConnection,
+                                    nsHttpTransaction *trans);
+    nsresult DispatchTransaction(nsConnectionEntry *,
+                                 nsHttpTransaction *,
+                                 nsHttpConnection *);
+    nsresult BuildPipeline(nsConnectionEntry *,
+                           nsAHttpTransaction *,
+                           nsHttpPipeline **);
     nsresult ProcessNewTransaction(nsHttpTransaction *);
     nsresult EnsureSocketThreadTargetIfOnline();
     void     ClosePersistentConnections(nsConnectionEntry *ent);
     nsresult CreateTransport(nsConnectionEntry *, nsHttpTransaction *);
     void     AddActiveConn(nsHttpConnection *, nsConnectionEntry *);
     void     StartedConnect();
     void     RecvdConnect();
 
+    bool     MakeNewConnection(nsConnectionEntry *ent,
+                               nsHttpTransaction *trans);
+    bool     AddToShortestPipeline(nsConnectionEntry *ent,
+                                   nsHttpTransaction *trans,
+                                   nsHttpTransaction::Classifier classification,
+                                   PRUint16 depthLimit);
+
     // Manage the preferred spdy connection entry for this address
     nsConnectionEntry *GetSpdyPreferredEnt(nsConnectionEntry *aOriginalEntry);
     void               RemoveSpdyPreferredEnt(nsACString &aDottedDecimal);
     nsHttpConnection  *GetSpdyPreferredConn(nsConnectionEntry *ent);
     nsDataHashtable<nsCStringHashKey, nsConnectionEntry *>   mSpdyPreferredHash;
     nsConnectionEntry *LookupConnectionEntry(nsHttpConnectionInfo *ci,
                                              nsHttpConnection *conn,
                                              nsHttpTransaction *trans);
@@ -396,16 +529,17 @@ private:
     void OnMsgNewTransaction       (PRInt32, void *);
     void OnMsgReschedTransaction   (PRInt32, void *);
     void OnMsgCancelTransaction    (PRInt32, void *);
     void OnMsgProcessPendingQ      (PRInt32, void *);
     void OnMsgPruneDeadConnections (PRInt32, void *);
     void OnMsgReclaimConnection    (PRInt32, void *);
     void OnMsgUpdateParam          (PRInt32, void *);
     void OnMsgClosePersistentConnections (PRInt32, void *);
+    void OnMsgProcessFeedback      (PRInt32, void *);
 
     // Total number of active connections in all of the ConnectionEntry objects
     // that are accessed from mCT connection table.
     PRUint16 mNumActiveConns;
     // Total number of idle connections in all of the ConnectionEntry objects
     // that are accessed from mCT connection table.
     PRUint16 mNumIdleConns;
 
--- a/netwerk/protocol/http/nsHttpHandler.cpp
+++ b/netwerk/protocol/http/nsHttpHandler.cpp
@@ -180,17 +180,19 @@ nsHttpHandler::nsHttpHandler()
     , mSpdyTimeout(PR_SecondsToInterval(180))
     , mMaxRequestAttempts(10)
     , mMaxRequestDelay(10)
     , mIdleSynTimeout(250)
     , mMaxConnections(24)
     , mMaxConnectionsPerServer(8)
     , mMaxPersistentConnectionsPerServer(2)
     , mMaxPersistentConnectionsPerProxy(4)
-    , mMaxPipelinedRequests(2)
+    , mMaxPipelinedRequests(32)
+    , mMaxOptimisticPipelinedRequests(4)
+    , mPipelineAggressive(false)
     , mRedirectionLimit(10)
     , mPhishyUserPassLength(1)
     , mQoSBits(0x00)
     , mPipeliningOverSSL(false)
     , mEnforceAssocReq(false)
     , mInPrivateBrowsingMode(PRIVATE_BROWSING_UNKNOWN)
     , mLastUniqueID(NowInSeconds())
     , mSessionStartTime(0)
@@ -359,17 +361,18 @@ nsHttpHandler::InitConnectionMgr()
     }
 
     rv = mConnMgr->Init(mMaxConnections,
                         mMaxConnectionsPerServer,
                         mMaxConnectionsPerServer,
                         mMaxPersistentConnectionsPerServer,
                         mMaxPersistentConnectionsPerProxy,
                         mMaxRequestDelay,
-                        mMaxPipelinedRequests);
+                        mMaxPipelinedRequests,
+                        mMaxOptimisticPipelinedRequests);
     return rv;
 }
 
 nsresult
 nsHttpHandler::AddStandardRequestHeaders(nsHttpHeaderArray *request,
                                          PRUint8 caps,
                                          bool useProxy)
 {
@@ -1011,23 +1014,41 @@ nsHttpHandler::PrefsChanged(nsIPrefBranc
             else
                 mCapabilities &= ~NS_HTTP_ALLOW_PIPELINING;
         }
     }
 
     if (PREF_CHANGED(HTTP_PREF("pipelining.maxrequests"))) {
         rv = prefs->GetIntPref(HTTP_PREF("pipelining.maxrequests"), &val);
         if (NS_SUCCEEDED(rv)) {
-            mMaxPipelinedRequests = clamped(val, 1, NS_HTTP_MAX_PIPELINED_REQUESTS);
+            mMaxPipelinedRequests = clamped(val, 1, 0xffff);
             if (mConnMgr)
                 mConnMgr->UpdateParam(nsHttpConnectionMgr::MAX_PIPELINED_REQUESTS,
                                       mMaxPipelinedRequests);
         }
     }
 
+    if (PREF_CHANGED(HTTP_PREF("pipelining.max-optimistic-requests"))) {
+        rv = prefs->
+            GetIntPref(HTTP_PREF("pipelining.max-optimistic-requests"), &val);
+        if (NS_SUCCEEDED(rv)) {
+            mMaxOptimisticPipelinedRequests = clamped(val, 1, 0xffff);
+            if (mConnMgr)
+                mConnMgr->UpdateParam
+                    (nsHttpConnectionMgr::MAX_OPTIMISTIC_PIPELINED_REQUESTS,
+                     mMaxOptimisticPipelinedRequests);
+        }
+    }
+
+    if (PREF_CHANGED(HTTP_PREF("pipelining.aggressive"))) {
+        rv = prefs->GetBoolPref(HTTP_PREF("pipelining.aggressive"), &cVar);
+        if (NS_SUCCEEDED(rv))
+            mPipelineAggressive = cVar;
+    }
+
     if (PREF_CHANGED(HTTP_PREF("pipelining.ssl"))) {
         rv = prefs->GetBoolPref(HTTP_PREF("pipelining.ssl"), &cVar);
         if (NS_SUCCEEDED(rv))
             mPipeliningOverSSL = cVar;
     }
 
     if (PREF_CHANGED(HTTP_PREF("proxy.pipelining"))) {
         rv = prefs->GetBoolPref(HTTP_PREF("proxy.pipelining"), &cVar);
--- a/netwerk/protocol/http/nsHttpHandler.h
+++ b/netwerk/protocol/http/nsHttpHandler.h
@@ -227,16 +227,18 @@ public:
         NotifyObservers(chan, NS_HTTP_ON_EXAMINE_CACHED_RESPONSE_TOPIC);
     }
 
     // Generates the host:port string for use in the Host: header as well as the
     // CONNECT line for proxies. This handles IPv6 literals correctly.
     static nsresult GenerateHostPort(const nsCString& host, PRInt32 port,
                                      nsCString& hostLine);
 
+    bool GetPipelineAggressive()     { return mPipelineAggressive; }
+
 private:
 
     //
     // Useragent/prefs helper methods
     //
     void     BuildUserAgent();
     void     InitUserAgentComponents();
     void     PrefsChanged(nsIPrefBranch *prefs, const char *pref);
@@ -283,17 +285,19 @@ private:
     PRUint16 mMaxRequestAttempts;
     PRUint16 mMaxRequestDelay;
     PRUint16 mIdleSynTimeout;
 
     PRUint16 mMaxConnections;
     PRUint8  mMaxConnectionsPerServer;
     PRUint8  mMaxPersistentConnectionsPerServer;
     PRUint8  mMaxPersistentConnectionsPerProxy;
-    PRUint8  mMaxPipelinedRequests;
+    PRUint16 mMaxPipelinedRequests;
+    PRUint16 mMaxOptimisticPipelinedRequests;
+    bool     mPipelineAggressive;
 
     PRUint8  mRedirectionLimit;
 
     // we'll warn the user if we load an URL containing a userpass field
     // unless its length is less than this threshold.  this warning is
     // intended to protect the user against spoofing attempts that use
     // the userpass field of the URL to obscure the actual origin server.
     PRUint8  mPhishyUserPassLength;
--- a/netwerk/protocol/http/nsHttpPipeline.cpp
+++ b/netwerk/protocol/http/nsHttpPipeline.cpp
@@ -87,19 +87,18 @@ private:
     const char *mBuf;
     PRUint32    mBufLen;
 };
 
 //-----------------------------------------------------------------------------
 // nsHttpPipeline <public>
 //-----------------------------------------------------------------------------
 
-nsHttpPipeline::nsHttpPipeline(PRUint16 maxPipelineDepth)
-    : mMaxPipelineDepth(maxPipelineDepth)
-    , mConnection(nsnull)
+nsHttpPipeline::nsHttpPipeline()
+    : mConnection(nsnull)
     , mStatus(NS_OK)
     , mRequestIsPartial(false)
     , mResponseIsPartial(false)
     , mClosed(false)
     , mUtilizedPipeline(false)
     , mPushBackBuf(nsnull)
     , mPushBackLen(0)
     , mPushBackMax(0)
@@ -126,55 +125,41 @@ nsHttpPipeline::AddTransaction(nsAHttpTr
 {
     LOG(("nsHttpPipeline::AddTransaction [this=%x trans=%x]\n", this, trans));
 
     if (mRequestQ.Length() || mResponseQ.Length())
         mUtilizedPipeline = true;
 
     NS_ADDREF(trans);
     mRequestQ.AppendElement(trans);
-    PRInt32 qlen = mRequestQ.Length();
+    PRUint32 qlen = PipelineDepth();
     
     if (qlen != 1) {
         trans->SetPipelinePosition(qlen);
     }
     else {
         // do it for this case in case an idempotent cancellation
         // is being repeated and an old value needs to be cleared
         trans->SetPipelinePosition(0);
     }
 
-    if (mConnection && !mClosed) {
-        trans->SetConnection(this);
-        if (qlen == 1)
-            mConnection->ResumeSend();
-    }
+    // trans->SetConnection() needs to be updated to point back at
+    // the pipeline object.
+    trans->SetConnection(this);
+
+    if (mConnection && !mClosed && mRequestQ.Length() == 1)
+        mConnection->ResumeSend();
 
     return NS_OK;
 }
 
-PRUint16
-nsHttpPipeline::PipelineDepthAvailable()
+PRUint32
+nsHttpPipeline::PipelineDepth()
 {
-    PRUint16 currentTransactions = mRequestQ.Length() + mResponseQ.Length();
-
-    // Check to see if there are too many transactions currently in use.
-    if (currentTransactions >= mMaxPipelineDepth)
-        return 0;
-
-    // Check to see if this connection is being used by a non-pipelineable
-    // transaction already.
-    nsAHttpTransaction *trans = Request(0);
-    if (!trans)
-        trans = Response(0);
-    if (trans && !(trans->Caps() & NS_HTTP_ALLOW_PIPELINING))
-        return 0;
-
-    // There is still some room available.
-    return mMaxPipelineDepth - currentTransactions;
+    return mRequestQ.Length() + mResponseQ.Length();
 }
 
 nsresult
 nsHttpPipeline::SetPipelinePosition(PRInt32 position)
 {
     nsAHttpTransaction *trans = Response(0);
     if (trans)
         return trans->SetPipelinePosition(position);
@@ -218,28 +203,28 @@ nsHttpPipeline::OnHeadersAvailable(nsAHt
     NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
     NS_ASSERTION(mConnection, "no connection");
     
     nsRefPtr<nsHttpConnectionInfo> ci;
     GetConnectionInfo(getter_AddRefs(ci));
 
     NS_ABORT_IF_FALSE(ci, "no connection info");
     
-    bool pipeliningBefore = ci->SupportsPipelining();
+    bool pipeliningBefore = gHttpHandler->ConnMgr()->SupportsPipelining(ci);
     
     // trans has now received its response headers; forward to the real connection
     nsresult rv = mConnection->OnHeadersAvailable(trans,
                                                   requestHead,
                                                   responseHead,
                                                   reset);
     
-    if (!pipeliningBefore && ci->SupportsPipelining())
+    if (!pipeliningBefore && gHttpHandler->ConnMgr()->SupportsPipelining(ci))
         // The received headers have expanded the eligible
         // pipeline depth for this connection
-        gHttpHandler->ConnMgr()->ProcessPipelinePendingQForCI(ci);
+        gHttpHandler->ConnMgr()->ProcessPendingQForEntry(ci);
 
     return rv;
 }
 
 nsresult
 nsHttpPipeline::ResumeSend()
 {
     if (mConnection)
@@ -256,17 +241,17 @@ nsHttpPipeline::ResumeRecv()
 }
 
 void
 nsHttpPipeline::CloseTransaction(nsAHttpTransaction *trans, nsresult reason)
 {
     LOG(("nsHttpPipeline::CloseTransaction [this=%x trans=%x reason=%x]\n",
         this, trans, reason));
 
-    NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+    NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
     NS_ASSERTION(NS_FAILED(reason), "expecting failure code");
 
     // the specified transaction is to be closed with the given "reason"
     
     PRInt32 index;
     bool killPipeline = false;
 
     index = mRequestQ.IndexOf(trans);
@@ -298,17 +283,21 @@ nsHttpPipeline::CloseTransaction(nsAHttp
         else
             Close(reason);
     }
 }
 
 void
 nsHttpPipeline::GetConnectionInfo(nsHttpConnectionInfo **result)
 {
-    NS_ASSERTION(mConnection, "no connection");
+    if (!mConnection) {
+        *result = nsnull;
+        return;
+    }
+
     mConnection->GetConnectionInfo(result);
 }
 
 nsresult
 nsHttpPipeline::TakeTransport(nsISocketTransport  **aTransport,
                               nsIAsyncInputStream **aInputStream,
                               nsIAsyncOutputStream **aOutputStream)
 {
@@ -336,17 +325,17 @@ nsHttpPipeline::IsReused()
     return true;
 }
 
 nsresult
 nsHttpPipeline::PushBack(const char *data, PRUint32 length)
 {
     LOG(("nsHttpPipeline::PushBack [this=%x len=%u]\n", this, length));
     
-    NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+    NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
     NS_ASSERTION(mPushBackLen == 0, "push back buffer already has data!");
 
     // If we have no chance for a pipeline (e.g. due to an Upgrade)
     // then push this data down to original connection
     if (!mConnection->IsPersistent())
         return mConnection->PushBack(data, length);
 
     // PushBack is called recursively from WriteSegments
@@ -460,32 +449,28 @@ nsHttpPipeline::TakeSubTransactions(
     }
     mRequestQ.Clear();
 
     LOG(("   took %d\n", count));
     return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
-// nsHttpPipeline::nsAHttpConnection
+// nsHttpPipeline::nsAHttpTransaction
 //-----------------------------------------------------------------------------
 
 void
 nsHttpPipeline::SetConnection(nsAHttpConnection *conn)
 {
     LOG(("nsHttpPipeline::SetConnection [this=%x conn=%x]\n", this, conn));
 
     NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
     NS_ASSERTION(!mConnection, "already have a connection");
 
     NS_IF_ADDREF(mConnection = conn);
-
-    PRInt32 i, count = mRequestQ.Length();
-    for (i=0; i<count; ++i)
-        Request(i)->SetConnection(this);
 }
 
 nsAHttpConnection *
 nsHttpPipeline::Connection()
 {
     LOG(("nsHttpPipeline::Connection [this=%x conn=%x]\n", this, mConnection));
 
     NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
@@ -748,17 +733,20 @@ nsHttpPipeline::WriteSegments(nsAHttpSeg
             trans->Close(NS_OK);
             NS_RELEASE(trans);
             mResponseQ.RemoveElementAt(0);
             mResponseIsPartial = false;
             ++mHttp1xTransactionCount;
 
             // ask the connection manager to add additional transactions
             // to our pipeline.
-            gHttpHandler->ConnMgr()->AddTransactionToPipeline(this);
+            nsRefPtr<nsHttpConnectionInfo> ci;
+            GetConnectionInfo(getter_AddRefs(ci));
+            if (ci)
+                gHttpHandler->ConnMgr()->ProcessPendingQForEntry(ci);
         }
         else
             mResponseIsPartial = true;
     }
 
     if (mPushBackLen) {
         nsHttpPushBackWriter writer(mPushBackBuf, mPushBackLen);
         PRUint32 len = mPushBackLen, n;
@@ -808,16 +796,23 @@ nsHttpPipeline::Close(nsresult reason)
             trans->Close(reason);
         else
             trans->Close(NS_ERROR_NET_RESET);
         NS_RELEASE(trans);
     }
     mRequestQ.Clear();
 
     trans = Response(0);
+    
+    nsRefPtr<nsHttpConnectionInfo> ci;
+    GetConnectionInfo(getter_AddRefs(ci));
+    if (ci && (trans || count))
+        gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+            ci, nsHttpConnectionMgr::RedCanceledPipeline, nsnull, 0);
+
     if (trans) {
         // The current transaction can be restarted via reset
         // if the response has not started to arrive and the reason
         // for failure is innocuous (e.g. not an SSL error)
         if (!mResponseIsPartial &&
             (reason == NS_ERROR_NET_RESET ||
              reason == NS_OK ||
              reason == NS_BASE_STREAM_CLOSED)) {
@@ -868,16 +863,23 @@ nsHttpPipeline::FillSendBuf()
 
     PRUint32 n, avail;
     nsAHttpTransaction *trans;
     nsITransport *transport = Transport();
 
     while ((trans = Request(0)) != nsnull) {
         avail = trans->Available();
         if (avail) {
+            // if there is already a response in the responseq then this
+            // new data comprises a pipeline. Update the transaction in the
+            // response queue to reflect that if necessary. We are now sending
+            // out a request while we haven't received all responses.
+            nsAHttpTransaction *response = Response(0);
+            if (response && !response->PipelinePosition())
+                response->SetPipelinePosition(1);
             rv = trans->ReadSegments(this, avail, &n);
             if (NS_FAILED(rv)) return rv;
             
             if (n == 0) {
                 LOG(("send pipe is full"));
                 break;
             }
 
@@ -898,14 +900,18 @@ nsHttpPipeline::FillSendBuf()
             mRequestIsPartial = false;
 
             if (!mSuppressSendEvents && transport) {
                 // Simulate a WAITING_FOR event
                 trans->OnTransportStatus(transport,
                                          NS_NET_STATUS_WAITING_FOR,
                                          mSendingToProgress);
             }
+
+            // It would be good to re-enable data read handlers via ResumeRecv()
+            // except the read handler code can be synchronously dispatched on
+            // the stack.
         }
         else
             mRequestIsPartial = true;
     }
     return NS_OK;
 }
--- a/netwerk/protocol/http/nsHttpPipeline.h
+++ b/netwerk/protocol/http/nsHttpPipeline.h
@@ -52,17 +52,17 @@ class nsHttpPipeline : public nsAHttpCon
                      , public nsAHttpSegmentReader
 {
 public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSAHTTPCONNECTION
     NS_DECL_NSAHTTPTRANSACTION
     NS_DECL_NSAHTTPSEGMENTREADER
 
-    nsHttpPipeline(PRUint16 maxPipelineDepth);
+    nsHttpPipeline();
     virtual ~nsHttpPipeline();
 
 private:
     nsresult FillSendBuf();
     
     static NS_METHOD ReadFromPipe(nsIInputStream *, void *, const char *,
                                   PRUint32, PRUint32, PRUint32 *);
 
@@ -77,17 +77,16 @@ private:
     nsAHttpTransaction *Response(PRInt32 i)
     {
         if (mResponseQ.Length() == 0)
             return nsnull;
 
         return mResponseQ[i];
     }
 
-    PRUint16                      mMaxPipelineDepth;
     nsAHttpConnection            *mConnection;
     nsTArray<nsAHttpTransaction*> mRequestQ;  // array of transactions
     nsTArray<nsAHttpTransaction*> mResponseQ; // array of transactions
     nsresult                      mStatus;
 
     // these flags indicate whether or not the first request or response
     // is partial.  a partial request means that Request(0) has been 
     // partially written out to the socket.  a partial response means
--- a/netwerk/protocol/http/nsHttpTransaction.cpp
+++ b/netwerk/protocol/http/nsHttpTransaction.cpp
@@ -114,16 +114,17 @@ nsHttpTransaction::nsHttpTransaction()
     , mContentLength(-1)
     , mContentRead(0)
     , mInvalidResponseBytesRead(0)
     , mChunkedDecoder(nsnull)
     , mStatus(NS_OK)
     , mPriority(0)
     , mRestartCount(0)
     , mCaps(0)
+    , mClassification(CLASS_GENERAL)
     , mPipelinePosition(0)
     , mClosed(false)
     , mConnected(false)
     , mHaveStatusLine(false)
     , mHaveAllHeaders(false)
     , mTransactionDone(false)
     , mResponseIsComplete(false)
     , mDidContentStart(false)
@@ -145,16 +146,50 @@ nsHttpTransaction::~nsHttpTransaction()
 
     NS_IF_RELEASE(mConnection);
     NS_IF_RELEASE(mConnInfo);
 
     delete mResponseHead;
     delete mChunkedDecoder;
 }
 
+nsHttpTransaction::Classifier
+nsHttpTransaction::Classify()
+{
+    if (!(mCaps & NS_HTTP_ALLOW_PIPELINING))
+        return (mClassification = CLASS_SOLO);
+
+    if (mRequestHead->PeekHeader(nsHttp::If_Modified_Since) ||
+        mRequestHead->PeekHeader(nsHttp::If_None_Match))
+        return (mClassification = CLASS_REVALIDATION);
+
+    const char *accept = mRequestHead->PeekHeader(nsHttp::Accept);
+    if (accept && !PL_strncmp(accept, "image/", 6))
+        return (mClassification = CLASS_IMAGE);
+
+    if (accept && !PL_strncmp(accept, "text/css", 8))
+        return (mClassification = CLASS_SCRIPT);
+
+    mClassification = CLASS_GENERAL;
+
+    PRInt32 queryPos = mRequestHead->RequestURI().FindChar('?');
+    if (queryPos == kNotFound) {
+        if (StringEndsWith(mRequestHead->RequestURI(),
+                           NS_LITERAL_CSTRING(".js")))
+            mClassification = CLASS_SCRIPT;
+    }
+    else if (queryPos >= 3 &&
+             Substring(mRequestHead->RequestURI(), queryPos - 3, 3).
+             EqualsLiteral(".js")) {
+        mClassification = CLASS_SCRIPT;
+    }
+
+    return mClassification;
+}
+
 nsresult
 nsHttpTransaction::Init(PRUint8 caps,
                         nsHttpConnectionInfo *cinfo,
                         nsHttpRequestHead *requestHead,
                         nsIInputStream *requestBody,
                         bool requestBodyHasHeaders,
                         nsIEventTarget *target,
                         nsIInterfaceRequestor *callbacks,
@@ -296,16 +331,18 @@ nsHttpTransaction::Init(PRUint8 caps,
     // create pipe for response stream
     rv = NS_NewPipe2(getter_AddRefs(mPipeIn),
                      getter_AddRefs(mPipeOut),
                      true, true,
                      nsIOService::gDefaultSegmentSize,
                      nsIOService::gDefaultSegmentCount);
     if (NS_FAILED(rv)) return rv;
 
+    Classify();
+
     NS_ADDREF(*responseBody = mPipeIn);
     return NS_OK;
 }
 
 nsAHttpConnection *
 nsHttpTransaction::Connection()
 {
     return mConnection;
@@ -656,26 +693,47 @@ nsHttpTransaction::Close(nsresult reason
     // have dire consequences including repeated purchases, etc.
     //
     // NOTE: because of the way SSL proxy CONNECT is implemented, it is
     // possible that the transaction may have received data without having
     // sent any data.  for this reason, mSendData == FALSE does not imply
     // mReceivedData == FALSE.  (see bug 203057 for more info.)
     //
     if (reason == NS_ERROR_NET_RESET || reason == NS_OK) {
-        if (!mReceivedData && (!mSentData || connReused)) {
+        if (!mReceivedData && (!mSentData || connReused || mPipelinePosition)) {
             // if restarting fails, then we must proceed to close the pipe,
             // which will notify the channel that the transaction failed.
+            
+            if (mPipelinePosition) {
+                gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+                    mConnInfo, nsHttpConnectionMgr::RedCanceledPipeline,
+                    nsnull, 0);
+            }
             if (NS_SUCCEEDED(Restart()))
                 return;
         }
     }
 
     bool relConn = true;
     if (NS_SUCCEEDED(reason)) {
+        if (!mResponseIsComplete) {
+            // The response has not been delimited with a high-confidence
+            // algorithm like Content-Length or Chunked Encoding. We
+            // need to use a strong framing mechanism to pipeline.
+            gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+                mConnInfo, nsHttpConnectionMgr::BadInsufficientFraming,
+                nsnull, mClassification);
+        }
+        else if (mPipelinePosition) {
+            // report this success as feedback
+            gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+                mConnInfo, nsHttpConnectionMgr::GoodCompletedOK,
+                nsnull, mPipelinePosition);
+        }
+
         // the server has not sent the final \r\n terminating the header
         // section, and there may still be a header line unparsed.  let's make
         // sure we parse the remaining header line, and then hopefully, the
         // response will be usable (see bug 88792).
         if (!mHaveAllHeaders) {
             char data = '\n';
             PRUint32 unused;
             ParseHead(&data, 1, &unused);
@@ -712,20 +770,20 @@ nsHttpTransaction::Close(nsresult reason
 }
 
 nsresult
 nsHttpTransaction::AddTransaction(nsAHttpTransaction *trans)
 {
     return NS_ERROR_NOT_IMPLEMENTED;
 }
 
-PRUint16
-nsHttpTransaction::PipelineDepthAvailable()
+PRUint32
+nsHttpTransaction::PipelineDepth()
 {
-    return 0;
+    return IsDone() ? 0 : 1;
 }
 
 nsresult
 nsHttpTransaction::SetPipelinePosition(PRInt32 position)
 {
     mPipelinePosition = position;
     return NS_OK;
 }
@@ -866,16 +924,19 @@ nsHttpTransaction::ParseLineSegment(char
         // not a continuation of the previous or if we haven't
         // parsed the status line yet, then parse the contents
         // of mLineBuf.
         mLineBuf.Truncate(mLineBuf.Length() - 1);
         if (!mHaveStatusLine || (*segment != ' ' && *segment != '\t')) {
             nsresult rv = ParseLine(mLineBuf.BeginWriting());
             mLineBuf.Truncate();
             if (NS_FAILED(rv)) {
+                gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+                    mConnInfo, nsHttpConnectionMgr::RedCorruptedContent,
+                    nsnull, 0);
                 return rv;
             }
         }
     }
 
     // append segment to mLineBuf...
     mLineBuf.Append(segment, len);
     
@@ -1060,16 +1121,20 @@ nsHttpTransaction::HandleContentStart()
         case 204:
         case 205:
         case 304:
             mNoContent = true;
             LOG(("this response should not contain a body.\n"));
             break;
         }
         mConnection->SetLastTransactionExpectedNoContent(mNoContent);
+        if (mInvalidResponseBytesRead)
+            gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+                mConnInfo, nsHttpConnectionMgr::BadInsufficientFraming,
+                nsnull, mClassification);
 
         if (mNoContent)
             mContentLength = 0;
         else {
             // grab the content-length from the response headers
             mContentLength = mResponseHead->ContentLength();
 
             // handle chunked encoding here, so we'll know immediately when
--- a/netwerk/protocol/http/nsHttpTransaction.h
+++ b/netwerk/protocol/http/nsHttpTransaction.h
@@ -129,29 +129,32 @@ public:
 
     bool      SSLConnectFailed() { return mSSLConnectFailed; }
 
     // SetPriority() may only be used by the connection manager.
     void    SetPriority(PRInt32 priority) { mPriority = priority; }
     PRInt32    Priority()                 { return mPriority; }
 
     const TimingStruct& Timings() const { return mTimings; }
+    enum Classifier Classification() { return mClassification; }
 
 private:
     nsresult Restart();
     char    *LocateHttpStart(char *buf, PRUint32 len,
                              bool aAllowPartialMatch);
     nsresult ParseLine(char *line);
     nsresult ParseLineSegment(char *seg, PRUint32 len);
     nsresult ParseHead(char *, PRUint32 count, PRUint32 *countRead);
     nsresult HandleContentStart();
     nsresult HandleContent(char *, PRUint32 count, PRUint32 *contentRead, PRUint32 *contentRemaining);
     nsresult ProcessData(char *, PRUint32, PRUint32 *);
     void     DeleteSelfOnConsumerThread();
 
+    Classifier Classify();
+
     static NS_METHOD ReadRequestSegment(nsIInputStream *, void *, const char *,
                                         PRUint32, PRUint32, PRUint32 *);
     static NS_METHOD WritePipeSegment(nsIOutputStream *, void *, char *,
                                       PRUint32, PRUint32, PRUint32 *);
 
     bool TimingEnabled() const { return mCaps & NS_HTTP_TIMING_ENABLED; }
 
 private:
@@ -194,16 +197,17 @@ private:
     TimingStruct                    mTimings;
 
     nsresult                        mStatus;
 
     PRInt16                         mPriority;
 
     PRUint16                        mRestartCount;        // the number of times this transaction has been restarted
     PRUint8                         mCaps;
+    enum Classifier                 mClassification;
     PRInt32                         mPipelinePosition;
 
     // state flags, all logically boolean, but not packed together into a
     // bitfield so as to avoid bitfield-induced races.  See bug 560579.
     bool                            mClosed;
     bool                            mConnected;
     bool                            mHaveStatusLine;
     bool                            mHaveAllHeaders;