bug 447866 http pipelining is bursty r=honzab
authorPatrick McManus <mcmanus@ducksong.com>
Thu, 22 Mar 2012 19:39:31 -0400
changeset 91908 3dd62d76cc6d73fcfc47f6d5677099cf6aad4c1c
parent 91907 f8656bbf0818126745f41119529b8be3a95bc4ad
child 91909 fd6f1cca200c3b750198ec03cbd82e8fa44b59ec
push idunknown
push userunknown
push dateunknown
reviewershonzab
bugs447866
milestone14.0a1
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);
@@ -367,16 +369,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;
 
@@ -732,16 +735,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.
@@ -1093,16 +1097,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)
@@ -414,18 +466,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;
     }
 }
@@ -535,16 +592,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();
@@ -621,16 +688,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 {
         // 
@@ -690,32 +768,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