bug 447866 http pipelining is bursty r=honzab
☠☠ backed out by 577da08878cb ☠ ☠
authorPatrick McManus <mcmanus@ducksong.com>
Tue, 20 Mar 2012 13:11:32 -0400
changeset 89849 1bbe7ea3a01e1e76470f78d5f66b36d4e63ef20d
parent 89848 a403afe78c47a2391d99bd5346e91f1cd6ef3f36
child 89850 0f3e5881da7b8443d187a61bc9f9811d14d58921
push id22293
push usermlamouri@mozilla.com
push dateWed, 21 Mar 2012 10:30:54 +0000
treeherdermozilla-central@f10862ac3217 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewershonzab
bugs447866
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 447866 http pipelining is bursty r=honzab
netwerk/protocol/http/SpdySession.cpp
netwerk/protocol/http/nsAHttpConnection.h
netwerk/protocol/http/nsAHttpTransaction.h
netwerk/protocol/http/nsHttpConnection.cpp
netwerk/protocol/http/nsHttpConnection.h
netwerk/protocol/http/nsHttpConnectionInfo.h
netwerk/protocol/http/nsHttpConnectionMgr.cpp
netwerk/protocol/http/nsHttpConnectionMgr.h
netwerk/protocol/http/nsHttpPipeline.cpp
netwerk/protocol/http/nsHttpPipeline.h
netwerk/protocol/http/nsHttpTransaction.cpp
netwerk/protocol/http/nsHttpTransaction.h
--- a/netwerk/protocol/http/SpdySession.cpp
+++ b/netwerk/protocol/http/SpdySession.cpp
@@ -2070,16 +2070,23 @@ SpdySession::IsDone()
 
 nsresult
 SpdySession::Status()
 {
   NS_ABORT_IF_FALSE(false, "SpdySession::Status()");
   return NS_ERROR_UNEXPECTED;
 }
 
+PRUint8
+SpdySession::Caps()
+{
+  NS_ABORT_IF_FALSE(false, "SpdySession::Caps()");
+  return 0;
+}
+
 PRUint32
 SpdySession::Available()
 {
   NS_ABORT_IF_FALSE(false, "SpdySession::Available()");
   return 0;
 }
 
 nsHttpRequestHead *
@@ -2127,16 +2134,36 @@ SpdySession::TakeSubTransactions(
     return NS_ERROR_ALREADY_OPENED;
 
   LOG3(("   taking %d\n", mStreamTransactionHash.Count()));
 
   mStreamTransactionHash.Enumerate(TakeStream, &outTransactions);
   return NS_OK;
 }
 
+nsresult
+SpdySession::AddTransaction(nsAHttpTransaction *)
+{
+  // This API is meant for pipelining, SpdySession's should be
+  // extended with AddStream()
+
+  NS_ABORT_IF_FALSE(false,
+                    "SpdySession::AddTransaction() should not be called");
+
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+PRUint16
+SpdySession::PipelineDepthAvailable()
+{
+  // any attempt at pipelining will be turned into parallelism
+
+  return 0;
+}
+
 //-----------------------------------------------------------------------------
 // Pass through methods of nsAHttpConnection
 //-----------------------------------------------------------------------------
 
 nsAHttpConnection *
 SpdySession::Connection()
 {
   NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
@@ -2175,16 +2202,23 @@ SpdySession::IsReused()
 
 nsresult
 SpdySession::PushBack(const char *buf, PRUint32 len)
 {
   return mConnection->PushBack(buf, len);
 }
 
 bool
+SpdySession::IsProxyConnectInProgress()
+{
+    NS_ABORT_IF_FALSE(mConnection, "no connection");
+    return mConnection->IsProxyConnectInProgress();
+}
+
+bool
 SpdySession::LastTransactionExpectedNoContent()
 {
   return mConnection->LastTransactionExpectedNoContent();
 }
 
 void
 SpdySession::SetLastTransactionExpectedNoContent(bool val)
 {
--- a/netwerk/protocol/http/nsAHttpConnection.h
+++ b/netwerk/protocol/http/nsAHttpConnection.h
@@ -122,16 +122,21 @@ public:
 
     // called to determine if a connection has been reused.
     virtual bool IsReused() = 0;
     
     // called by a transaction when the transaction reads more from the socket
     // than it should have (eg. containing part of the next pipelined response).
     virtual nsresult PushBack(const char *data, PRUint32 length) = 0;
 
+    // Used to determine if the connection wants read events even though
+    // it has not written out a transaction. Used when a connection has issued
+    // a preamble such as a proxy ssl CONNECT sequence.
+    virtual bool IsProxyConnectInProgress() = 0;
+
     // Used by a transaction to manage the state of previous response bodies on
     // the same connection and work around buggy servers.
     virtual bool LastTransactionExpectedNoContent() = 0;
     virtual void   SetLastTransactionExpectedNoContent(bool) = 0;
 
     // Transfer the base http connection object along with a
     // reference to it to the caller.
     virtual nsHttpConnection *TakeHttpConnection() = 0;
@@ -149,14 +154,15 @@ public:
     void GetConnectionInfo(nsHttpConnectionInfo **); \
     nsresult TakeTransport(nsISocketTransport **,    \
                            nsIAsyncInputStream **,   \
                            nsIAsyncOutputStream **); \
     void GetSecurityInfo(nsISupports **); \
     bool IsPersistent(); \
     bool IsReused(); \
     nsresult PushBack(const char *, PRUint32); \
+    bool IsProxyConnectInProgress(); \
     bool LastTransactionExpectedNoContent(); \
     void   SetLastTransactionExpectedNoContent(bool); \
     nsHttpConnection *TakeHttpConnection(); \
     nsISocketTransport *Transport();
 
 #endif // nsAHttpConnection_h__
--- a/netwerk/protocol/http/nsAHttpTransaction.h
+++ b/netwerk/protocol/http/nsAHttpTransaction.h
@@ -72,16 +72,17 @@ public:
 
     // called to report socket status (see nsITransportEventSink)
     virtual void OnTransportStatus(nsITransport* transport,
                                    nsresult status, PRUint64 progress) = 0;
 
     // called to check the transaction status.
     virtual bool     IsDone() = 0;
     virtual nsresult Status() = 0;
+    virtual PRUint8  Caps() = 0;
 
     // called to find out how much request data is available for writing.
     virtual PRUint32 Available() = 0;
 
     // called to read request data from the transaction.
     virtual nsresult ReadSegments(nsAHttpSegmentReader *reader,
                                   PRUint32 count, PRUint32 *countRead) = 0;
 
@@ -109,35 +110,46 @@ public:
     // Returns NS_ERROR_NOT_IMPLEMENTED if the object does not implement
     // sub-transactions.
     //
     // Returns NS_ERROR_ALREADY_OPENED if the subtransactions have been
     // at least partially written and cannot be moved.
     //
     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;
 };
 
 #define NS_DECL_NSAHTTPTRANSACTION \
     void SetConnection(nsAHttpConnection *); \
     nsAHttpConnection *Connection(); \
     void GetSecurityCallbacks(nsIInterfaceRequestor **, \
                               nsIEventTarget **);       \
     void OnTransportStatus(nsITransport* transport, \
                            nsresult status, PRUint64 progress); \
     bool     IsDone(); \
     nsresult Status(); \
+    PRUint8  Caps();   \
     PRUint32 Available(); \
     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 TakeSubTransactions(nsTArray<nsRefPtr<nsAHttpTransaction> > &outTransactions); \
+    nsresult AddTransaction(nsAHttpTransaction *);                      \
+    PRUint16 PipelineDepthAvailable();
 
 //-----------------------------------------------------------------------------
 // nsAHttpSegmentReader
 //-----------------------------------------------------------------------------
 
 class nsAHttpSegmentReader
 {
 public:
--- a/netwerk/protocol/http/nsHttpConnection.cpp
+++ b/netwerk/protocol/http/nsHttpConnection.cpp
@@ -81,16 +81,17 @@ nsHttpConnection::nsHttpConnection()
     , 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)
     , mNPNComplete(false)
     , mSetupNPNCalled(false)
     , mUsingSpdy(false)
     , mPriority(nsISupportsPriority::PRIORITY_NORMAL)
     , mReportedSpdy(false)
     , mEverUsedSpdy(false)
 {
@@ -148,16 +149,17 @@ nsHttpConnection::Init(nsHttpConnectionI
     LOG(("nsHttpConnection::Init [this=%p "
          "transport=%p instream=%p outstream=%p]\n",
          this, transport, instream, outstream));
 
     NS_ENSURE_ARG_POINTER(info);
     NS_ENSURE_TRUE(!mConnInfo, NS_ERROR_ALREADY_INITIALIZED);
 
     mConnInfo = info;
+    mSupportsPipelining = mConnInfo->SupportsPipelining();
     mMaxHangTime = PR_SecondsToInterval(maxHangTime);
     mLastReadTime = PR_IntervalNow();
 
     mSocketTransport = transport;
     mSocketIn = instream;
     mSocketOut = outstream;
     nsresult rv = mSocketTransport->SetEventSink(this, nsnull);
     NS_ENSURE_SUCCESS(rv, rv);
@@ -364,16 +366,17 @@ nsHttpConnection::Activate(nsAHttpTransa
 
     // need to handle HTTP CONNECT tunnels if this is the first time if
     // we are tunneling through a proxy
     if (((mConnInfo->UsingSSL() && mConnInfo->UsingHttpProxy()) ||
          mConnInfo->ShouldForceConnectMethod()) && !mCompletedProxyConnect) {
         rv = SetupProxyConnect();
         if (NS_FAILED(rv))
             goto failed_activation;
+        mProxyConnectInProgress = true;
     }
 
     // Clear the per activation counter
     mCurrentBytesRead = 0;
 
     // The overflow state is not needed between activations
     mInputOverflow = nsnull;
 
@@ -728,16 +731,17 @@ nsHttpConnection::OnHeadersAvailable(nsA
             // determination must be based on comunication with the
             // target server in this case. See bug 422016 for futher
             // details.
             if (!mProxyConnectStream)
               mSupportsPipelining = SupportsPipelining(responseHead);
         }
     }
     mKeepAliveMask = mKeepAlive;
+    mConnInfo->SetSupportsPipelining(mSupportsPipelining);
 
     // 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.
@@ -1089,16 +1093,17 @@ nsHttpConnection::OnSocketWritable()
         }
         else {
             if (!mReportedSpdy) {
                 mReportedSpdy = true;
                 gHttpHandler->ConnMgr()->ReportSpdyConnection(this, mUsingSpdy);
             }
 
             LOG(("  writing transaction request stream\n"));
+            mProxyConnectInProgress = false;
             rv = mTransaction->ReadSegments(this, nsIOService::gDefaultSegmentSize, &n);
         }
 
         LOG(("  ReadSegments returned [rv=%x read=%u sock-cond=%x]\n",
             rv, n, mSocketOutCondition));
 
         // XXX some streams return NS_BASE_STREAM_CLOSED to indicate EOF.
         if (rv == NS_BASE_STREAM_CLOSED) {
--- a/netwerk/protocol/http/nsHttpConnection.h
+++ b/netwerk/protocol/http/nsHttpConnection.h
@@ -36,31 +36,33 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef nsHttpConnection_h__
 #define nsHttpConnection_h__
 
 #include "nsHttp.h"
 #include "nsHttpConnectionInfo.h"
-#include "nsAHttpConnection.h"
 #include "nsAHttpTransaction.h"
 #include "nsXPIDLString.h"
 #include "nsCOMPtr.h"
 #include "nsAutoPtr.h"
 #include "prinrval.h"
 #include "SpdySession.h"
 
 #include "nsIStreamListener.h"
 #include "nsISocketTransport.h"
 #include "nsIAsyncInputStream.h"
 #include "nsIAsyncOutputStream.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIEventTarget.h"
 
+class nsHttpRequestHead;
+class nsHttpResponseHead;
+
 //-----------------------------------------------------------------------------
 // nsHttpConnection - represents a connection to a HTTP server (or proxy)
 //
 // NOTE: this objects lives on the socket thread only.  it should not be
 // accessed from any other thread.
 //-----------------------------------------------------------------------------
 
 class nsHttpConnection : public nsAHttpSegmentReader
@@ -110,16 +112,21 @@ public:
     bool     CanDirectlyActivate();
 
     // Returns time in seconds for how long connection can be reused.
     PRUint32 TimeToLive();
 
     void     DontReuse();
     void     DropTransport() { DontReuse(); mSocketTransport = 0; }
 
+    bool     IsProxyConnectInProgress()
+    {
+        return mProxyConnectInProgress;
+    }
+
     bool     LastTransactionExpectedNoContent()
     {
         return mLastTransactionExpectedNoContent;
     }
 
     void     SetLastTransactionExpectedNoContent(bool val)
     {
         mLastTransactionExpectedNoContent = val;
@@ -223,16 +230,17 @@ private:
 
     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;
 
     // SPDY related
     bool                            mNPNComplete;
     bool                            mSetupNPNCalled;
--- a/netwerk/protocol/http/nsHttpConnectionInfo.h
+++ b/netwerk/protocol/http/nsHttpConnectionInfo.h
@@ -55,16 +55,17 @@ class nsHttpConnectionInfo
 {
 public:
     nsHttpConnectionInfo(const nsACString &host, PRInt32 port,
                          nsProxyInfo* proxyInfo,
                          bool usingSSL=false)
         : mRef(0)
         , mProxyInfo(proxyInfo)
         , mUsingSSL(usingSSL)
+        , mSupportsPipelining(false)
     {
         LOG(("Creating nsHttpConnectionInfo @%x\n", this));
 
         mUsingHttpProxy = (proxyInfo && !nsCRT::strcmp(proxyInfo->Type(), "http"));
 
         SetOriginServer(host, port);
     }
     
@@ -120,22 +121,28 @@ public:
     PRInt32       Port() const           { return mPort; }
     nsProxyInfo  *ProxyInfo()            { return mProxyInfo; }
     bool          UsingHttpProxy() const { return mUsingHttpProxy; }
     bool          UsingSSL() const       { return mUsingSSL; }
     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          SupportsPipelining()   { return mSupportsPipelining; }
+    void          SetSupportsPipelining(bool support)
+                                         { mSupportsPipelining = support; }
+
     bool          ShouldForceConnectMethod();
     const nsCString &GetHost() { return mHost; }
 
 private:
     nsrefcnt               mRef;
     nsCString              mHashKey;
     nsCString              mHost;
     PRInt32                mPort;
     nsCOMPtr<nsProxyInfo>  mProxyInfo;
     bool                   mUsingHttpProxy;
     bool                   mUsingSSL;
+    bool                   mSupportsPipelining;
 };
 
 #endif // nsHttpConnectionInfo_h__
--- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp
@@ -347,36 +347,48 @@ nsHttpConnectionMgr::GetSocketThreadTarg
     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) {
+    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; ++i) {
+            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);
-                    break;
+                    if (--avail == 0)
+                        break;
+                }
+                else {
+                    ++i;
                 }
             }
         }
     }
 }
 
 nsresult
 nsHttpConnectionMgr::ReclaimConnection(nsHttpConnection *conn)
@@ -951,16 +963,79 @@ nsHttpConnectionMgr::ProcessPendingQForE
 
             NS_RELEASE(conn);
             return true;
         }
     }
     return false;
 }
 
+bool
+nsHttpConnectionMgr::ProcessPipelinePendingQForEntry(nsConnectionEntry *ent)
+{
+    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;
+}
+
+bool
+nsHttpConnectionMgr::ProcessPipelinePendingQForCI(nsHttpConnectionInfo *ci)
+{
+    NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+    
+    nsConnectionEntry *ent = mCT.Get(ci->HashKey());
+
+    return ent && ProcessPipelinePendingQForEntry(ent);
+}
+
 // 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)
 {
     nsHttpConnectionInfo *ci = ent->mConnInfo;
@@ -1225,22 +1300,23 @@ nsHttpConnectionMgr::DispatchTransaction
 
     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 (conn->SupportsPipelining() && (caps & NS_HTTP_ALLOW_PIPELINING)) {
-        LOG(("  looking to build pipeline...\n"));
-        if (BuildPipeline(ent, trans, &pipeline))
-            trans = pipeline;
-    }
+    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));
@@ -1263,50 +1339,58 @@ nsHttpConnectionMgr::DispatchTransaction
     return rv;
 }
 
 bool
 nsHttpConnectionMgr::BuildPipeline(nsConnectionEntry *ent,
                                    nsAHttpTransaction *firstTrans,
                                    nsHttpPipeline **result)
 {
+    NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+
     if (mMaxPipelinedRequests < 2)
         return false;
 
-    nsHttpPipeline *pipeline = nsnull;
-    nsHttpTransaction *trans;
+    /* 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;
 
-    PRUint32 i = 0, numAdded = 0;
-    while (i < ent->mPendingQ.Length()) {
-        trans = ent->mPendingQ[i];
-        if (trans->Caps() & NS_HTTP_ALLOW_PIPELINING) {
-            if (numAdded == 0) {
-                pipeline = new nsHttpPipeline;
-                if (!pipeline)
-                    return false;
-                pipeline->AddTransaction(firstTrans);
-                numAdded = 1;
+        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;
             }
-            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
+            }
         }
-        else
-            ++i; // skip to next pending transaction
     }
 
-    if (numAdded == 0)
-        return false;
-
-    LOG(("  pipelined %u transactions\n", numAdded));
+    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");
@@ -1370,16 +1454,21 @@ nsHttpConnectionMgr::ProcessNewTransacti
 
     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;
@@ -2293,16 +2382,22 @@ nsHttpConnectionMgr::nsConnectionHandle:
 
     NS_ASSERTION(mConn, "no connection");
     nsHttpConnection *conn = mConn;
     mConn = nsnull;
     return conn;
 }
 
 bool
+nsHttpConnectionMgr::nsConnectionHandle::IsProxyConnectInProgress()
+{
+    return mConn->IsProxyConnectInProgress();
+}
+
+bool
 nsHttpConnectionMgr::nsConnectionHandle::LastTransactionExpectedNoContent()
 {
     return mConn->LastTransactionExpectedNoContent();
 }
 
 void
 nsHttpConnectionMgr::
 nsConnectionHandle::SetLastTransactionExpectedNoContent(bool val)
--- a/netwerk/protocol/http/nsHttpConnectionMgr.h
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.h
@@ -137,17 +137,17 @@ public:
     bool GetSpdyAlternateProtocol(nsACString &key);
     void ReportSpdyAlternateProtocol(nsHttpConnection *);
     void RemoveSpdyAlternateProtocol(nsACString &key);
 
     //-------------------------------------------------------------------------
     // NOTE: functions below may be called only on the socket thread.
     //-------------------------------------------------------------------------
 
-    // removes the next transaction for the specified connection from the
+    // 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 *);
 
     // This is used to force an idle connection to be closed and removed from
@@ -155,16 +155,20 @@ public:
     // 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 *);
 private:
     virtual ~nsHttpConnectionMgr();
     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
@@ -306,16 +310,17 @@ private:
     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     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 ProcessNewTransaction(nsHttpTransaction *);
     nsresult EnsureSocketThreadTargetIfOnline();
     void     ClosePersistentConnections(nsConnectionEntry *ent);
--- a/netwerk/protocol/http/nsHttpPipeline.cpp
+++ b/netwerk/protocol/http/nsHttpPipeline.cpp
@@ -87,22 +87,24 @@ private:
     const char *mBuf;
     PRUint32    mBufLen;
 };
 
 //-----------------------------------------------------------------------------
 // nsHttpPipeline <public>
 //-----------------------------------------------------------------------------
 
-nsHttpPipeline::nsHttpPipeline()
-    : mConnection(nsnull)
+nsHttpPipeline::nsHttpPipeline(PRUint16 maxPipelineDepth)
+    : mMaxPipelineDepth(maxPipelineDepth)
+    , mConnection(nsnull)
     , mStatus(NS_OK)
     , mRequestIsPartial(false)
     , mResponseIsPartial(false)
     , mClosed(false)
+    , mUtilizedPipeline(false)
     , mPushBackBuf(nsnull)
     , mPushBackLen(0)
     , mPushBackMax(0)
     , mHttp1xTransactionCount(0)
     , mReceivingFromProgress(0)
     , mSendingToProgress(0)
     , mSuppressSendEvents(true)
 {
@@ -119,29 +121,53 @@ nsHttpPipeline::~nsHttpPipeline()
         free(mPushBackBuf);
 }
 
 nsresult
 nsHttpPipeline::AddTransaction(nsAHttpTransaction *trans)
 {
     LOG(("nsHttpPipeline::AddTransaction [this=%x trans=%x]\n", this, trans));
 
+    if (mRequestQ.Length() || mResponseQ.Length())
+        mUtilizedPipeline = true;
+
     NS_ADDREF(trans);
     mRequestQ.AppendElement(trans);
 
     if (mConnection && !mClosed) {
         trans->SetConnection(this);
 
         if (mRequestQ.Length() == 1)
             mConnection->ResumeSend();
     }
 
     return NS_OK;
 }
 
+PRUint16
+nsHttpPipeline::PipelineDepthAvailable()
+{
+    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;
+}
+
 //-----------------------------------------------------------------------------
 // nsHttpPipeline::nsISupports
 //-----------------------------------------------------------------------------
 
 NS_IMPL_THREADSAFE_ADDREF(nsHttpPipeline)
 NS_IMPL_THREADSAFE_RELEASE(nsHttpPipeline)
 
 // multiple inheritance fun :-)
@@ -159,19 +185,36 @@ nsHttpPipeline::OnHeadersAvailable(nsAHt
                                    nsHttpRequestHead *requestHead,
                                    nsHttpResponseHead *responseHead,
                                    bool *reset)
 {
     LOG(("nsHttpPipeline::OnHeadersAvailable [this=%x]\n", this));
 
     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();
+    
     // trans has now received its response headers; forward to the real connection
-    return mConnection->OnHeadersAvailable(trans, requestHead, responseHead, reset);
+    nsresult rv = mConnection->OnHeadersAvailable(trans,
+                                                  requestHead,
+                                                  responseHead,
+                                                  reset);
+    
+    if (!pipeliningBefore && ci->SupportsPipelining())
+        // The received headers have expanded the eligible
+        // pipeline depth for this connection
+        gHttpHandler->ConnMgr()->ProcessPipelinePendingQForCI(ci);
+
+    return rv;
 }
 
 nsresult
 nsHttpPipeline::ResumeSend()
 {
     if (mConnection)
         return mConnection->ResumeSend();
     return NS_ERROR_UNEXPECTED;
@@ -256,17 +299,19 @@ bool
 nsHttpPipeline::IsPersistent()
 {
     return true; // pipelining requires this
 }
 
 bool
 nsHttpPipeline::IsReused()
 {
-    return true; // pipelining requires this
+    if (!mUtilizedPipeline && mConnection)
+        return mConnection->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");
@@ -305,16 +350,23 @@ nsHttpPipeline::PushBack(const char *dat
  
     memcpy(mPushBackBuf, data, length);
     mPushBackLen = length;
 
     return NS_OK;
 }
 
 bool
+nsHttpPipeline::IsProxyConnectInProgress()
+{
+    NS_ABORT_IF_FALSE(mConnection, "no connection");
+    return mConnection->IsProxyConnectInProgress();
+}
+
+bool
 nsHttpPipeline::LastTransactionExpectedNoContent()
 {
     NS_ABORT_IF_FALSE(mConnection, "no connection");
     return mConnection->LastTransactionExpectedNoContent();
 }
 
 void
 nsHttpPipeline::SetLastTransactionExpectedNoContent(bool val)
@@ -420,18 +472,23 @@ nsHttpPipeline::Connection()
 }
 
 void
 nsHttpPipeline::GetSecurityCallbacks(nsIInterfaceRequestor **result,
                                      nsIEventTarget        **target)
 {
     NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
 
-    // return security callbacks from first request
+    // depending on timing this could be either the request or the response
+    // that is needed - but they both go to the same host. A request for these
+    // callbacks directly in nsHttpTransaction would not make a distinction
+    // over whether the the request had been transmitted yet.
     nsAHttpTransaction *trans = Request(0);
+    if (!trans)
+        trans = Response(0);
     if (trans)
         trans->GetSecurityCallbacks(result, target);
     else {
         *result = nsnull;
         if (target)
             *target = nsnull;
     }
 }
@@ -541,16 +598,26 @@ nsHttpPipeline::IsDone()
 }
 
 nsresult
 nsHttpPipeline::Status()
 {
     return mStatus;
 }
 
+PRUint8
+nsHttpPipeline::Caps()
+{
+    nsAHttpTransaction *trans = Request(0);
+    if (!trans)
+        trans = Response(0);
+
+    return trans ? trans->Caps() : 0;
+}
+
 PRUint32
 nsHttpPipeline::Available()
 {
     PRUint32 result = 0;
 
     PRInt32 i, count = mRequestQ.Length();
     for (i=0; i<count; ++i)
         result += Request(i)->Available();
@@ -627,16 +694,27 @@ nsHttpPipeline::WriteSegments(nsAHttpSeg
 
     if (mClosed)
         return NS_SUCCEEDED(mStatus) ? NS_BASE_STREAM_CLOSED : mStatus;
 
     nsAHttpTransaction *trans; 
     nsresult rv;
 
     trans = Response(0);
+    // This code deals with the establishment of a CONNECT tunnel through
+    // an HTTP proxy. It allows the connection to do the CONNECT/200
+    // HTTP transaction to establish an SSL tunnel as a precursor to the
+    // actual pipeline of regular HTTP transactions.
+    if (!trans && mRequestQ.Length() &&
+        mConnection->IsProxyConnectInProgress()) {
+        LOG(("nsHttpPipeline::WriteSegments [this=%p] Forced Delegation\n",
+             this));
+        trans = Request(0);
+    }
+
     if (!trans) {
         if (mRequestQ.Length() > 0)
             rv = NS_BASE_STREAM_WOULD_BLOCK;
         else
             rv = NS_BASE_STREAM_CLOSED;
     }
     else {
         // 
@@ -696,32 +774,42 @@ nsHttpPipeline::Close(nsresult reason)
     // the connection is going away!
     mStatus = reason;
     mClosed = true;
 
     PRUint32 i, count;
     nsAHttpTransaction *trans;
 
     // any pending requests can ignore this error and be restarted
+    // unless it is during a CONNECT tunnel request
     count = mRequestQ.Length();
-    for (i=0; i<count; ++i) {
+    for (i = 0; i < count; ++i) {
         trans = Request(i);
-        trans->Close(NS_ERROR_NET_RESET);
+        if (mConnection && mConnection->IsProxyConnectInProgress())
+            trans->Close(reason);
+        else
+            trans->Close(NS_ERROR_NET_RESET);
         NS_RELEASE(trans);
     }
     mRequestQ.Clear();
 
     trans = Response(0);
     if (trans) {
-        // if the current response is partially complete, then it cannot be
-        // restarted and will have to fail with the status of the connection.
-        if (mResponseIsPartial)
+        // 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)) {
+            trans->Close(NS_ERROR_NET_RESET);            
+        }
+        else {
             trans->Close(reason);
-        else
-            trans->Close(NS_ERROR_NET_RESET);
+        }
         NS_RELEASE(trans);
         
         // any remaining pending responses can be restarted
         count = mResponseQ.Length();
         for (i=1; i<count; ++i) {
             trans = Response(i);
             trans->Close(NS_ERROR_NET_RESET);
             NS_RELEASE(trans);
--- a/netwerk/protocol/http/nsHttpPipeline.h
+++ b/netwerk/protocol/http/nsHttpPipeline.h
@@ -52,21 +52,19 @@ class nsHttpPipeline : public nsAHttpCon
                      , public nsAHttpSegmentReader
 {
 public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSAHTTPCONNECTION
     NS_DECL_NSAHTTPTRANSACTION
     NS_DECL_NSAHTTPSEGMENTREADER
 
-    nsHttpPipeline();
+    nsHttpPipeline(PRUint16 maxPipelineDepth);
     virtual ~nsHttpPipeline();
 
-    nsresult AddTransaction(nsAHttpTransaction *);
-
 private:
     nsresult FillSendBuf();
     
     static NS_METHOD ReadFromPipe(nsIInputStream *, void *, const char *,
                                   PRUint32, PRUint32, PRUint32 *);
 
     // convenience functions
     nsAHttpTransaction *Request(PRInt32 i)
@@ -79,31 +77,36 @@ 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
     // that Response(0) has been partially read in from the socket.
     bool mRequestIsPartial;
     bool mResponseIsPartial;
 
     // indicates whether or not the pipeline has been explicitly closed.
     bool mClosed;
 
+    // indicates whether or not a true pipeline (more than 1 request without
+    // a synchronous response) has been formed.
+    bool mUtilizedPipeline;
+
     // used when calling ReadSegments/WriteSegments on a transaction.
     nsAHttpSegmentReader *mReader;
     nsAHttpSegmentWriter *mWriter;
 
     // send buffer
     nsCOMPtr<nsIInputStream>  mSendBufIn;
     nsCOMPtr<nsIOutputStream> mSendBufOut;
 
--- a/netwerk/protocol/http/nsHttpTransaction.cpp
+++ b/netwerk/protocol/http/nsHttpTransaction.cpp
@@ -451,16 +451,22 @@ nsHttpTransaction::IsDone()
 }
 
 nsresult
 nsHttpTransaction::Status()
 {
     return mStatus;
 }
 
+PRUint8
+nsHttpTransaction::Caps()
+{ 
+    return mCaps;
+}
+
 PRUint32
 nsHttpTransaction::Available()
 {
     PRUint32 size;
     if (NS_FAILED(mRequestStream->Available(&size)))
         size = 0;
     return size;
 }
@@ -699,16 +705,28 @@ nsHttpTransaction::Close(nsresult reason
         delete mChunkedDecoder;
         mChunkedDecoder = nsnull;
     }
 
     // closing this pipe triggers the channel's OnStopRequest method.
     mPipeOut->CloseWithStatus(reason);
 }
 
+nsresult
+nsHttpTransaction::AddTransaction(nsAHttpTransaction *trans)
+{
+    return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+PRUint16
+nsHttpTransaction::PipelineDepthAvailable()
+{
+    return 0;
+}
+
 //-----------------------------------------------------------------------------
 // nsHttpTransaction <private>
 //-----------------------------------------------------------------------------
 
 nsresult
 nsHttpTransaction::Restart()
 {
     NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
--- a/netwerk/protocol/http/nsHttpTransaction.h
+++ b/netwerk/protocol/http/nsHttpTransaction.h
@@ -108,17 +108,16 @@ public:
                   nsIInputStream        *reqBody,
                   bool                   reqBodyIncludesHeaders,
                   nsIEventTarget        *consumerTarget,
                   nsIInterfaceRequestor *callbacks,
                   nsITransportEventSink *eventsink,
                   nsIAsyncInputStream  **responseBody);
 
     // attributes
-    PRUint8                Caps()           { return mCaps; }
     nsHttpConnectionInfo  *ConnectionInfo() { return mConnInfo; }
     nsHttpResponseHead    *ResponseHead()   { return mHaveAllHeaders ? mResponseHead : nsnull; }
     nsISupports           *SecurityInfo()   { return mSecurityInfo; }
 
     nsIInterfaceRequestor *Callbacks()      { return mCallbacks; } 
     nsIEventTarget        *ConsumerTarget() { return mConsumerTarget; }
 
     // Called to take ownership of the response headers; the transaction