bug 603512 - large objects block pipelines r=honzab
authorPatrick McManus <mcmanus@ducksong.com>
Thu, 22 Mar 2012 19:39:31 -0400
changeset 90101 87ea59a6d1625dd810e9bf3985fceed8948437fe
parent 90100 e1e48e9ce79565e48f0b93ca75192976e92e379d
child 90102 52efc30fbfecff37fce7c3b15ad5a58b20b6dba9
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
bugs603512
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 603512 - large objects block pipelines r=honzab the type and state patch tries hard not to form pipelines behind resources that could become head of line blockers. But of course that requires the ability to predict the future, and won't be perfect. This patch reacts to a transaction that has a large response body (defined by either a large content-length header or actually reading a large number of chunked bytes) by cancelling any transactions that have been pipelined down the same connection and rescheduling them elsewhere. It also changes the type of the connection to "solo", which prevents new transactions from being pipelined onto this one and provides class-specific negative feedback to the pipeline manager so that near-future requests to the same host of the same type (e.g. general) will not be pipelined but other types (e.g. img or js/css) can still do that. Content-Length is ideal, because it allows us to identify the problem so early. But even actually reading the document for a fairly long time gives it a fairly high probability of not ending soon. (i.e. long document sizes are spread over a larger range than small ones. duh.) The pref network.http.pipelining.maxsize controls the threshold. I set the default at 300KB, which is roughly the bandwidth delay product of a 2mbit 120ms rtt connection and 1 rtt is mostly what you are giving up by canceling it on one connection and sending it on another. (modulo maybe needing a handshake).
modules/libpref/src/init/all.js
netwerk/protocol/http/SpdySession.cpp
netwerk/protocol/http/SpdySession.h
netwerk/protocol/http/nsAHttpConnection.h
netwerk/protocol/http/nsHttpConnection.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/nsHttpTransaction.cpp
netwerk/protocol/http/nsHttpTransaction.h
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -811,16 +811,17 @@ pref("network.http.proxy.pipelining", fa
 // Max number of requests in the pipeline
 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);
+pref("network.http.pipelining.maxsize" , 300000);
 
 // 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/protocol/http/SpdySession.cpp
+++ b/netwerk/protocol/http/SpdySession.cpp
@@ -2029,16 +2029,40 @@ SpdySession::TakeHttpConnection()
 nsISocketTransport *
 SpdySession::Transport()
 {
   if (!mConnection)
     return nsnull;
   return mConnection->Transport();
 }
 
+PRUint32
+SpdySession::CancelPipeline(nsresult reason)
+{
+  // we don't pipeline inside spdy, so this isn't an issue
+  return 0;
+}
+
+nsAHttpTransaction::Classifier
+SpdySession::Classification()
+{
+  if (!mConnection)
+    return nsAHttpTransaction::CLASS_GENERAL;
+  return mConnection->Classification();
+}
+
+void
+SpdySession::Classify(nsAHttpTransaction::Classifier newclass)
+{
+  if (!mConnection)
+    return;
+  
+  mConnection->Classify(newclass);
+}
+
 //-----------------------------------------------------------------------------
 // unused methods of nsAHttpTransaction
 // We can be sure of this because SpdySession is only constructed in
 // nsHttpConnection and is never passed out of that object
 //-----------------------------------------------------------------------------
 
 void
 SpdySession::SetConnection(nsAHttpConnection *)
--- a/netwerk/protocol/http/SpdySession.h
+++ b/netwerk/protocol/http/SpdySession.h
@@ -70,17 +70,16 @@ public:
   NS_DECL_NSAHTTPSEGMENTREADER
   NS_DECL_NSAHTTPSEGMENTWRITER
 
   SpdySession(nsAHttpTransaction *, nsISocketTransport *, PRInt32);
   ~SpdySession();
 
   bool AddStream(nsAHttpTransaction *, PRInt32);
   bool CanReuse() { return !mShouldGoAway && !mClosed; }
-  void DontReuse();
   bool RoomForMoreStreams();
 
   // When the connection is active this is called every 15 seconds
   void ReadTimeoutTick(PRIntervalTime now);
   
   // Idle time represents time since "goodput".. e.g. a data or header frame
   PRIntervalTime IdleTime();
 
--- a/netwerk/protocol/http/nsAHttpConnection.h
+++ b/netwerk/protocol/http/nsAHttpConnection.h
@@ -34,18 +34,18 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef nsAHttpConnection_h__
 #define nsAHttpConnection_h__
 
 #include "nsISupports.h"
+#include "nsAHttpTransaction.h"
 
-class nsAHttpTransaction;
 class nsHttpRequestHead;
 class nsHttpResponseHead;
 class nsHttpConnectionInfo;
 class nsHttpConnection;
 class nsISocketTransport;
 class nsIAsyncInputStream;
 class nsIAsyncOutputStream;
 
@@ -115,19 +115,20 @@ public:
 
     // called by a transaction to get the security info from the socket.
     virtual void GetSecurityInfo(nsISupports **) = 0;
 
     // called by a transaction to determine whether or not the connection is
     // persistent... important in determining the end of a response.
     virtual bool IsPersistent() = 0;
 
-    // called to determine if a connection has been reused.
+    // called to determine or set if a connection has been reused.
     virtual bool IsReused() = 0;
-    
+    virtual void   DontReuse() = 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;
@@ -139,30 +140,42 @@ public:
 
     // Transfer the base http connection object along with a
     // reference to it to the caller.
     virtual nsHttpConnection *TakeHttpConnection() = 0;
 
     // Get the nsISocketTransport used by the connection without changing
     //  references or ownership.
     virtual nsISocketTransport *Transport() = 0;
+
+    // Cancel and reschedule transactions deeper than the current response.
+    // Returns the number of canceled transactions.
+    virtual PRUint32 CancelPipeline(nsresult originalReason) = 0;
+
+    // Read and write class of transaction that is carried on this connection
+    virtual nsAHttpTransaction::Classifier Classification() = 0;
+    virtual void Classify(nsAHttpTransaction::Classifier newclass) = 0;
 };
 
 #define NS_DECL_NSAHTTPCONNECTION \
     nsresult OnHeadersAvailable(nsAHttpTransaction *, nsHttpRequestHead *, nsHttpResponseHead *, bool *reset); \
     nsresult ResumeSend(); \
     nsresult ResumeRecv(); \
     void CloseTransaction(nsAHttpTransaction *, nsresult); \
     void GetConnectionInfo(nsHttpConnectionInfo **); \
     nsresult TakeTransport(nsISocketTransport **,    \
                            nsIAsyncInputStream **,   \
                            nsIAsyncOutputStream **); \
     void GetSecurityInfo(nsISupports **); \
     bool IsPersistent(); \
     bool IsReused(); \
+    void DontReuse();  \
     nsresult PushBack(const char *, PRUint32); \
     bool IsProxyConnectInProgress(); \
     bool LastTransactionExpectedNoContent(); \
-    void   SetLastTransactionExpectedNoContent(bool); \
+    void SetLastTransactionExpectedNoContent(bool); \
     nsHttpConnection *TakeHttpConnection(); \
-    nsISocketTransport *Transport();
+    nsISocketTransport *Transport();        \
+    PRUint32 CancelPipeline(nsresult originalReason);   \
+    nsAHttpTransaction::Classifier Classification();    \
+    void Classify(nsAHttpTransaction::Classifier);
 
 #endif // nsAHttpConnection_h__
--- a/netwerk/protocol/http/nsHttpConnection.h
+++ b/netwerk/protocol/http/nsHttpConnection.h
@@ -37,16 +37,17 @@
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef nsHttpConnection_h__
 #define nsHttpConnection_h__
 
 #include "nsHttp.h"
 #include "nsHttpConnectionInfo.h"
 #include "nsAHttpTransaction.h"
+#include "nsHttpPipeline.h"
 #include "nsXPIDLString.h"
 #include "nsCOMPtr.h"
 #include "nsAutoPtr.h"
 #include "prinrval.h"
 #include "SpdySession.h"
 #include "mozilla/TimeStamp.h"
 
 #include "nsIStreamListener.h"
--- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp
@@ -2068,16 +2068,22 @@ nsHttpConnectionMgr::nsConnectionHandle:
 }
 
 bool
 nsHttpConnectionMgr::nsConnectionHandle::IsReused()
 {
     return mConn->IsReused();
 }
 
+void
+nsHttpConnectionMgr::nsConnectionHandle::DontReuse()
+{
+    mConn->DontReuse();
+}
+
 nsresult
 nsHttpConnectionMgr::nsConnectionHandle::PushBack(const char *buf, PRUint32 bufLen)
 {
     return mConn->PushBack(buf, bufLen);
 }
 
 
 //////////////////////// nsHalfOpenSocket
@@ -2512,16 +2518,42 @@ nsHttpConnectionMgr::nsConnectionHandle:
 }
 
 bool
 nsHttpConnectionMgr::nsConnectionHandle::IsProxyConnectInProgress()
 {
     return mConn->IsProxyConnectInProgress();
 }
 
+PRUint32
+nsHttpConnectionMgr::nsConnectionHandle::CancelPipeline(nsresult reason)
+{
+    // no pipeline to cancel
+    return 0;
+}
+
+nsAHttpTransaction::Classifier
+nsHttpConnectionMgr::nsConnectionHandle::Classification()
+{
+    if (mConn)
+        return mConn->Classification();
+
+    LOG(("nsConnectionHandle::Classification this=%p "
+         "has null mConn using CLASS_SOLO default", this));
+    return nsAHttpTransaction::CLASS_SOLO;
+}
+
+void
+nsHttpConnectionMgr::
+nsConnectionHandle::Classify(nsAHttpTransaction::Classifier newclass)
+{
+    if (mConn)
+        mConn->Classify(newclass);
+}
+
 // nsConnectionEntry
 
 nsHttpConnectionMgr::
 nsConnectionEntry::nsConnectionEntry(nsHttpConnectionInfo *ci)
     : mConnInfo(ci)
     , mPipelineState(PS_YELLOW)
     , mYellowGoodEvents(0)
     , mYellowBadEvents(0)
@@ -2576,17 +2608,18 @@ nsConnectionEntry::OnPipelineFeedbackInf
 
         if (depth >= 3)
             mGreenDepth = kPipelineUnlimited;
     }
 
     nsAHttpTransaction::Classifier classification;
     if (conn)
         classification = conn->Classification();
-    else if (info == BadInsufficientFraming)
+    else if (info == BadInsufficientFraming ||
+             info == BadUnexpectedLarge)
         classification = (nsAHttpTransaction::Classifier) data;
     else
         classification = nsAHttpTransaction::CLASS_SOLO;
 
     if (gHttpHandler->GetPipelineAggressive() &&
         info & kPipelineInfoTypeBad &&
         info != BadExplicitClose &&
         info != RedVersionTooLow &&
@@ -2635,16 +2668,19 @@ nsConnectionEntry::OnPipelineFeedbackInf
             mPipeliningClassPenalty[classification] += 5;
             break;
         case BadSlowReadMajor:
             mPipeliningClassPenalty[classification] += 25;
             break;
         case BadInsufficientFraming:
             mPipeliningClassPenalty[classification] += 7000;
             break;
+        case BadUnexpectedLarge:
+            mPipeliningClassPenalty[classification] += 120;
+            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);
             
--- a/netwerk/protocol/http/nsHttpConnectionMgr.h
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.h
@@ -50,17 +50,17 @@
 #include "mozilla/ReentrantMonitor.h"
 #include "nsISocketTransportService.h"
 #include "mozilla/TimeStamp.h"
 
 #include "nsIObserver.h"
 #include "nsITimer.h"
 #include "nsIX509Cert3.h"
 
-class nsHttpPipeline;
+#include "nsHttpPipeline.h"
 
 //-----------------------------------------------------------------------------
 
 class nsHttpConnectionMgr : public nsIObserver
 {
 public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSIOBSERVER
@@ -156,17 +156,17 @@ public:
         // 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
+        // integrity check such as assoc-req
         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
@@ -179,16 +179,20 @@ public:
 
         // 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 very large response is recevied in a potential pipelining
+        // context. Large responses cause head of line blocking.
+        BadUnexpectedLarge = kPipelineInfoTypeBad | 0x000B,
 
         // 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
     };
--- a/netwerk/protocol/http/nsHttpHandler.cpp
+++ b/netwerk/protocol/http/nsHttpHandler.cpp
@@ -183,16 +183,17 @@ nsHttpHandler::nsHttpHandler()
     , mIdleSynTimeout(250)
     , mMaxConnections(24)
     , mMaxConnectionsPerServer(8)
     , mMaxPersistentConnectionsPerServer(2)
     , mMaxPersistentConnectionsPerProxy(4)
     , mMaxPipelinedRequests(32)
     , mMaxOptimisticPipelinedRequests(4)
     , mPipelineAggressive(false)
+    , mMaxPipelineObjectSize(300000)
     , mRedirectionLimit(10)
     , mPhishyUserPassLength(1)
     , mQoSBits(0x00)
     , mPipeliningOverSSL(false)
     , mEnforceAssocReq(false)
     , mInPrivateBrowsingMode(PRIVATE_BROWSING_UNKNOWN)
     , mLastUniqueID(NowInSeconds())
     , mSessionStartTime(0)
@@ -1039,16 +1040,24 @@ nsHttpHandler::PrefsChanged(nsIPrefBranc
     }
 
     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.maxsize"))) {
+        rv = prefs->GetIntPref(HTTP_PREF("pipelining.maxsize"), &val);
+        if (NS_SUCCEEDED(rv)) {
+            mMaxPipelineObjectSize =
+                static_cast<PRInt64>(clamped(val, 1000, 100000000));
+        }
+    }
+
     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
@@ -228,16 +228,20 @@ public:
     }
 
     // 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; }
+    void GetMaxPipelineObjectSize(PRInt64 &outVal)
+    {
+        outVal = mMaxPipelineObjectSize;
+    }
 
 private:
 
     //
     // Useragent/prefs helper methods
     //
     void     BuildUserAgent();
     void     InitUserAgentComponents();
@@ -288,16 +292,17 @@ private:
 
     PRUint16 mMaxConnections;
     PRUint8  mMaxConnectionsPerServer;
     PRUint8  mMaxPersistentConnectionsPerServer;
     PRUint8  mMaxPersistentConnectionsPerProxy;
     PRUint16 mMaxPipelinedRequests;
     PRUint16 mMaxOptimisticPipelinedRequests;
     bool     mPipelineAggressive;
+    PRInt64  mMaxPipelineObjectSize;
 
     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
@@ -269,25 +269,27 @@ nsHttpPipeline::CloseTransaction(nsAHttp
             mResponseQ.RemoveElementAt(index);
         // while we could avoid killing the pipeline if this transaction is the
         // last transaction in the pipeline, there doesn't seem to be that much
         // value in doing so.  most likely if this transaction is going away,
         // the others will be shortly as well.
         killPipeline = true;
     }
 
+    // Marking this connection as non-reusable prevents other items from being
+    // added to it and causes it to be torn down soon. Don't tear it down yet
+    // as that would prevent Response(0) from being processed.
+    DontReuse();
+
     trans->Close(reason);
     NS_RELEASE(trans);
 
-    if (killPipeline) {
-        if (mConnection)
-            mConnection->CloseTransaction(this, reason);
-        else
-            Close(reason);
-    }
+    if (killPipeline)
+        // reschedule anything from this pipeline onto a different connection
+        CancelPipeline(reason);
 }
 
 void
 nsHttpPipeline::GetConnectionInfo(nsHttpConnectionInfo **result)
 {
     if (!mConnection) {
         *result = nsnull;
         return;
@@ -320,16 +322,23 @@ nsHttpPipeline::IsPersistent()
 bool
 nsHttpPipeline::IsReused()
 {
     if (!mUtilizedPipeline && mConnection)
         return mConnection->IsReused();
     return true;
 }
 
+void
+nsHttpPipeline::DontReuse()
+{
+    if (mConnection)
+        mConnection->DontReuse();
+}
+
 nsresult
 nsHttpPipeline::PushBack(const char *data, PRUint32 length)
 {
     LOG(("nsHttpPipeline::PushBack [this=%x len=%u]\n", this, length));
     
     NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
     NS_ASSERTION(mPushBackLen == 0, "push back buffer already has data!");
 
@@ -402,16 +411,34 @@ nsHttpPipeline::TakeHttpConnection()
 nsISocketTransport *
 nsHttpPipeline::Transport()
 {
     if (!mConnection)
         return nsnull;
     return mConnection->Transport();
 }
 
+nsAHttpTransaction::Classifier
+nsHttpPipeline::Classification()
+{
+    if (mConnection)
+        return mConnection->Classification();
+
+    LOG(("nsHttpPipeline::Classification this=%p "
+         "has null mConnection using CLASS_SOLO default", this));
+    return nsAHttpTransaction::CLASS_SOLO;
+}
+
+void
+nsHttpPipeline::Classify(nsAHttpTransaction::Classifier newclass)
+{
+    if (mConnection)
+        mConnection->Classify(newclass);
+}
+
 void
 nsHttpPipeline::SetSSLConnectFailed()
 {
     nsAHttpTransaction *trans = Request(0);
 
     if (trans)
         trans->SetSSLConnectFailed();
 }
@@ -765,79 +792,107 @@ nsHttpPipeline::WriteSegments(nsAHttpSeg
         // so we are guaranteed that the next response will eat the entire
         // push back buffer (even though it might again call PushBack).
         rv = WriteSegments(&writer, len, &n);
     }
 
     return rv;
 }
 
+PRUint32
+nsHttpPipeline::CancelPipeline(nsresult originalReason)
+{
+    PRUint32 i, reqLen, respLen, total;
+    nsAHttpTransaction *trans;
+
+    reqLen = mRequestQ.Length();
+    respLen = mResponseQ.Length();
+    total = reqLen + respLen;
+
+    // don't count the first response, if presnet
+    if (respLen)
+        total--;
+
+    if (!total)
+        return 0;
+
+    // any pending requests can ignore this error and be restarted
+    // unless it is during a CONNECT tunnel request
+    for (i = 0; i < reqLen; ++i) {
+        trans = Request(i);
+        if (mConnection && mConnection->IsProxyConnectInProgress())
+            trans->Close(originalReason);
+        else
+            trans->Close(NS_ERROR_NET_RESET);
+        NS_RELEASE(trans);
+    }
+    mRequestQ.Clear();
+
+    // any pending responses can be restarted except for the first one,
+    // that we might want to finish on this pipeline or cancel individually
+    for (i = 1; i < respLen; ++i) {
+        trans = Response(i);
+        trans->Close(NS_ERROR_NET_RESET);
+        NS_RELEASE(trans);
+    }
+
+    if (respLen > 1)
+        mResponseQ.TruncateLength(1);
+
+    DontReuse();
+    Classify(nsAHttpTransaction::CLASS_SOLO);
+
+    return total;
+}
+
 void
 nsHttpPipeline::Close(nsresult reason)
 {
     LOG(("nsHttpPipeline::Close [this=%x reason=%x]\n", this, reason));
 
     if (mClosed) {
         LOG(("  already closed\n"));
         return;
     }
 
     // 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) {
-        trans = Request(i);
-        if (mConnection && mConnection->IsProxyConnectInProgress())
-            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))
+    PRUint32 numRescheduled = CancelPipeline(reason);
+
+    // numRescheduled can be 0 if there is just a single response in the
+    // pipeline object. That isn't really a meaningful pipeline that
+    // has been forced to be rescheduled so it does not need to generate
+    // negative feedback.
+    if (ci && numRescheduled)
         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)) {
-            trans->Close(NS_ERROR_NET_RESET);            
-        }
-        else {
-            trans->Close(reason);
-        }
-        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);
-        }
-        mResponseQ.Clear();
+    nsAHttpTransaction *trans = Response(0);
+    if (!trans)
+        return;
+
+    // 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);
     }
 
+    NS_RELEASE(trans);
+    mResponseQ.Clear();
 }
 
 nsresult
 nsHttpPipeline::OnReadSegment(const char *segment,
                               PRUint32 count,
                               PRUint32 *countRead)
 {
     return mSendBufOut->Write(segment, count, countRead);
--- a/netwerk/protocol/http/nsHttpTransaction.cpp
+++ b/netwerk/protocol/http/nsHttpTransaction.cpp
@@ -133,16 +133,17 @@ nsHttpTransaction::nsHttpTransaction()
     , mReceivedData(false)
     , mStatusEventPending(false)
     , mHasRequestBody(false)
     , mSSLConnectFailed(false)
     , mHttpResponseMatched(false)
     , mPreserveStream(false)
 {
     LOG(("Creating nsHttpTransaction @%x\n", this));
+    gHttpHandler->GetMaxPipelineObjectSize(mMaxPipelineObjectSize);
 }
 
 nsHttpTransaction::~nsHttpTransaction()
 {
     LOG(("Destroying nsHttpTransaction @%x\n", this));
 
     NS_IF_RELEASE(mConnection);
     NS_IF_RELEASE(mConnInfo);
@@ -1132,16 +1133,20 @@ nsHttpTransaction::HandleContentStart()
                 nsnull, mClassification);
 
         if (mNoContent)
             mContentLength = 0;
         else {
             // grab the content-length from the response headers
             mContentLength = mResponseHead->ContentLength();
 
+            if ((mClassification != CLASS_SOLO) &&
+                (mContentLength > mMaxPipelineObjectSize))
+                CancelPipeline(nsHttpConnectionMgr::BadUnexpectedLarge);
+            
             // handle chunked encoding here, so we'll know immediately when
             // we're done with the socket.  please note that _all_ other
             // decoding is done when the channel receives the content data
             // so as not to block the socket transport thread too much.
             // ignore chunked responses from HTTP/1.0 servers and proxies.
             if (mResponseHead->Version() >= NS_HTTP_VERSION_1_1 &&
                 mResponseHead->HasHeaderValue(nsHttp::Transfer_Encoding, "chunked")) {
                 // we only support the "chunked" transfer encoding right now.
@@ -1226,16 +1231,23 @@ nsHttpTransaction::HandleContent(char *b
         if (mProgressSink)
             mProgressSink->OnProgress(nsnull, nsnull, mContentRead, NS_MAX(0, mContentLength));
         */
     }
 
     LOG(("nsHttpTransaction::HandleContent [this=%x count=%u read=%u mContentRead=%lld mContentLength=%lld]\n",
         this, count, *contentRead, mContentRead, mContentLength));
 
+    // Check the size of chunked responses. If we exceed the max pipeline size
+    // for this response reschedule the pipeline
+    if ((mClassification != CLASS_SOLO) &&
+        mChunkedDecoder &&
+        (mContentRead > mMaxPipelineObjectSize))
+        CancelPipeline(nsHttpConnectionMgr::BadUnexpectedLarge);
+
     // check for end-of-file
     if ((mContentRead == mContentLength) ||
         (mChunkedDecoder && mChunkedDecoder->ReachedEOF())) {
         // the transaction is done with a complete response.
         mTransactionDone = true;
         mResponseIsComplete = true;
 
         if (TimingEnabled())
@@ -1325,16 +1337,33 @@ nsHttpTransaction::ProcessData(char *buf
             NS_ASSERTION(mConnection, "no connection");
             mConnection->PushBack(buf + *countRead, countRemaining);
         }
     }
 
     return NS_OK;
 }
 
+void
+nsHttpTransaction::CancelPipeline(PRUint32 reason)
+{
+    // reason is casted through a uint to avoid compiler header deps
+    gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
+        mConnInfo,
+        static_cast<nsHttpConnectionMgr::PipelineFeedbackInfoType>(reason),
+        nsnull, mClassification);
+
+    mConnection->CancelPipeline(NS_ERROR_CORRUPTED_CONTENT);
+
+    // Avoid pipelining this transaction on restart by classifying it as solo.
+    // This also prevents BadUnexpectedLarge from being reported more
+    // than one time per transaction.
+    mClassification = CLASS_SOLO;
+}
+
 //-----------------------------------------------------------------------------
 // nsHttpTransaction deletion event
 //-----------------------------------------------------------------------------
 
 class nsDeleteHttpTransaction : public nsRunnable {
 public:
     nsDeleteHttpTransaction(nsHttpTransaction *trans)
         : mTrans(trans)
--- a/netwerk/protocol/http/nsHttpTransaction.h
+++ b/netwerk/protocol/http/nsHttpTransaction.h
@@ -144,16 +144,17 @@ private:
     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();
+    void       CancelPipeline(PRUint32 reason);
 
     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; }
 
@@ -199,16 +200,17 @@ private:
     nsresult                        mStatus;
 
     PRInt16                         mPriority;
 
     PRUint16                        mRestartCount;        // the number of times this transaction has been restarted
     PRUint8                         mCaps;
     enum Classifier                 mClassification;
     PRInt32                         mPipelinePosition;
+    PRInt64                         mMaxPipelineObjectSize;
 
     // 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;
     bool                            mTransactionDone;