diff --git a/content/base/src/nsXMLHttpRequest.cpp b/content/base/src/nsXMLHttpRequest.cpp index fc1d0cf876f..39eec8236bf 100644 --- a/content/base/src/nsXMLHttpRequest.cpp +++ b/content/base/src/nsXMLHttpRequest.cpp @@ -2779,6 +2779,10 @@ nsXMLHttpRequest::Send(nsIVariant *aBody) } } + // Blocking gets are common enough out of XHR that we should mark + // the channel slow by default for pipeline purposes + AddLoadFlags(mChannel, nsIRequest::INHIBIT_PIPELINE); + if (!IsSystemXHR()) { // Always create a nsCORSListenerProxy here even if it's // a same-origin request right now, since it could be redirected. diff --git a/content/html/content/src/nsHTMLMediaElement.cpp b/content/html/content/src/nsHTMLMediaElement.cpp index a3d92bca874..ca91cddcdda 100644 --- a/content/html/content/src/nsHTMLMediaElement.cpp +++ b/content/html/content/src/nsHTMLMediaElement.cpp @@ -57,6 +57,8 @@ #include "nsThreadUtils.h" #include "nsIThreadInternal.h" #include "nsContentUtils.h" +#include "nsIRequest.h" + #include "nsFrameManager.h" #include "nsIScriptSecurityManager.h" #include "nsIXPConnect.h" @@ -2850,6 +2852,13 @@ void nsHTMLMediaElement::SetRequestHeaders(nsIHttpChannel* aChannel) // Send Accept header for video and audio types only (Bug 489071) SetAcceptHeader(aChannel); + // Media elements are likely candidates for HTTP Pipeline head of line + // blocking problems, so disable pipelines. + nsLoadFlags loadflags; + aChannel->GetLoadFlags(&loadflags); + loadflags |= nsIRequest::INHIBIT_PIPELINE; + aChannel->SetLoadFlags(loadflags); + // Apache doesn't send Content-Length when gzip transfer encoding is used, // which prevents us from estimating the video length (if explicit Content-Duration // and a length spec in the container are not present either) and from seeking. diff --git a/modules/libpref/src/init/all.js b/modules/libpref/src/init/all.js index 2e49c17d7bd..d743200320c 100644 --- a/modules/libpref/src/init/all.js +++ b/modules/libpref/src/init/all.js @@ -806,7 +806,13 @@ pref("network.http.pipelining.ssl" , false); // disable pipelining over SSL pref("network.http.proxy.pipelining", false); // Max number of requests in the pipeline -pref("network.http.pipelining.maxrequests" , 4); +pref("network.http.pipelining.maxrequests" , 32); + +// An optimistic request is one pipelined when policy might allow a new +// connection instead +pref("network.http.pipelining.max-optimistic-requests" , 4); + +pref("network.http.pipelining.aggressive", false); // Prompt for 307 redirects pref("network.http.prompt-temp-redirect", true); diff --git a/netwerk/base/public/nsIRequest.idl b/netwerk/base/public/nsIRequest.idl index f04d9d2b329..455d16b4fda 100644 --- a/netwerk/base/public/nsIRequest.idl +++ b/netwerk/base/public/nsIRequest.idl @@ -159,6 +159,13 @@ interface nsIRequest : nsISupports * The following flags control the flow of data into the cache. */ + /** + * This flag prevents loading of the request with an HTTP pipeline. + * Generally this is because the resource is expected to take a + * while to load and may cause head of line blocking problems. + */ + const unsigned long INHIBIT_PIPELINE = 1 << 6; + /** * This flag prevents caching of any kind. It does not, however, prevent * cached content from being used to satisfy this request. diff --git a/netwerk/protocol/http/SpdySession.cpp b/netwerk/protocol/http/SpdySession.cpp index 1c0bbe4b59e..8cd11a1c75f 100644 --- a/netwerk/protocol/http/SpdySession.cpp +++ b/netwerk/protocol/http/SpdySession.cpp @@ -2064,8 +2064,7 @@ SpdySession::SetSSLConnectFailed() bool SpdySession::IsDone() { - NS_ABORT_IF_FALSE(false, "SpdySession::IsDone()"); - return false; + return !mStreamTransactionHash.Count(); } nsresult @@ -2151,12 +2150,10 @@ SpdySession::AddTransaction(nsAHttpTransaction *) return NS_ERROR_NOT_IMPLEMENTED; } -PRUint16 -SpdySession::PipelineDepthAvailable() +PRUint32 +SpdySession::PipelineDepth() { - // any attempt at pipelining will be turned into parallelism - - return 0; + return IsDone() ? 0 : 1; } nsresult diff --git a/netwerk/protocol/http/nsAHttpTransaction.h b/netwerk/protocol/http/nsAHttpTransaction.h index 10a1a1389d7..f7c68dd6ff9 100644 --- a/netwerk/protocol/http/nsAHttpTransaction.h +++ b/netwerk/protocol/http/nsAHttpTransaction.h @@ -63,6 +63,8 @@ class nsAHttpTransaction : public nsISupports public: // called by the connection when it takes ownership of the transaction. virtual void SetConnection(nsAHttpConnection *) = 0; + + // used to obtain the connection associated with this transaction virtual nsAHttpConnection *Connection() = 0; // called by the connection to get security callbacks to set on the @@ -121,14 +123,38 @@ public: // return NS_ERROR_NOT_IMPLEMENTED virtual nsresult AddTransaction(nsAHttpTransaction *transaction) = 0; - // called to count the number of sub transactions that can be added - virtual PRUint16 PipelineDepthAvailable() = 0; + // The total length of the outstanding pipeline comprised of transacations + // and sub-transactions. + virtual PRUint32 PipelineDepth() = 0; // Used to inform the connection that it is being used in a pipelined // context. That may influence the handling of some errors. - // The value is the pipeline position. + // The value is the pipeline position (> 1). virtual nsresult SetPipelinePosition(PRInt32) = 0; virtual PRInt32 PipelinePosition() = 0; + + // Every transaction is classified into one of the types below. When using + // HTTP pipelines, only transactions with the same type appear on the same + // pipeline. + enum Classifier { + // Transactions that expect a short 304 (no-content) response + CLASS_REVALIDATION, + + // Transactions for content expected to be CSS or JS + CLASS_SCRIPT, + + // Transactions for content expected to be an image + CLASS_IMAGE, + + // Transactions that cannot involve a pipeline + CLASS_SOLO, + + // Transactions that do not fit any of the other categories. HTML + // is normally GENERAL. + CLASS_GENERAL, + + CLASS_MAX + }; }; #define NS_DECL_NSAHTTPTRANSACTION \ @@ -150,7 +176,7 @@ public: PRUint32 Http1xTransactionCount(); \ nsresult TakeSubTransactions(nsTArray > &outTransactions); \ nsresult AddTransaction(nsAHttpTransaction *); \ - PRUint16 PipelineDepthAvailable(); \ + PRUint32 PipelineDepth(); \ nsresult SetPipelinePosition(PRInt32); \ PRInt32 PipelinePosition(); diff --git a/netwerk/protocol/http/nsHttp.h b/netwerk/protocol/http/nsHttp.h index 551f6e9ba9a..51ff68e4424 100644 --- a/netwerk/protocol/http/nsHttp.h +++ b/netwerk/protocol/http/nsHttp.h @@ -140,9 +140,6 @@ typedef PRUint8 nsHttpVersion; // some default values //----------------------------------------------------------------------------- -// hard upper limit on the number of requests that can be pipelined -#define NS_HTTP_MAX_PIPELINED_REQUESTS 8 - #define NS_HTTP_DEFAULT_PORT 80 #define NS_HTTPS_DEFAULT_PORT 443 diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp index 1d92195eb02..5b22eb8561d 100644 --- a/netwerk/protocol/http/nsHttpChannel.cpp +++ b/netwerk/protocol/http/nsHttpChannel.cpp @@ -502,16 +502,17 @@ nsHttpChannel::SetupTransaction() if (mCaps & NS_HTTP_ALLOW_PIPELINING) { // // disable pipelining if: - // (1) pipelining has been explicitly disabled - // (2) request corresponds to a top-level document load (link click) - // (3) request method is non-idempotent + // (1) pipelining has been disabled by config + // (2) pipelining has been disabled by connection mgr info + // (3) request corresponds to a top-level document load (link click) + // (4) request method is non-idempotent + // (5) request is marked slow (e.g XHR) // - // XXX does the toplevel document check really belong here? or, should - // we push it out entirely to necko consumers? - // - if (!mAllowPipelining || (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) || + if (!mAllowPipelining || + (mLoadFlags & (LOAD_INITIAL_DOCUMENT_URI | INHIBIT_PIPELINE)) || !(mRequestHead.Method() == nsHttp::Get || mRequestHead.Method() == nsHttp::Head || + mRequestHead.Method() == nsHttp::Options || mRequestHead.Method() == nsHttp::Propfind || mRequestHead.Method() == nsHttp::Proppatch)) { LOG((" pipelining disallowed\n")); @@ -1772,7 +1773,10 @@ nsHttpChannel::EnsureAssocReq() endofmethod - method)) { LOG((" Assoc-Req failure Method %s", method)); if (mConnectionInfo) - mConnectionInfo->BanPipelining(); + gHttpHandler->ConnMgr()-> + PipelineFeedbackInfo(mConnectionInfo, + nsHttpConnectionMgr::RedCorruptedContent, + nsnull, 0); nsCOMPtr consoleService = do_GetService(NS_CONSOLESERVICE_CONTRACTID); @@ -1802,7 +1806,10 @@ nsHttpChannel::EnsureAssocReq() if (!equals) { LOG((" Assoc-Req failure URL %s", assoc_val)); if (mConnectionInfo) - mConnectionInfo->BanPipelining(); + gHttpHandler->ConnMgr()-> + PipelineFeedbackInfo(mConnectionInfo, + nsHttpConnectionMgr::RedCorruptedContent, + nsnull, 0); nsCOMPtr consoleService = do_GetService(NS_CONSOLESERVICE_CONTRACTID); diff --git a/netwerk/protocol/http/nsHttpConnection.cpp b/netwerk/protocol/http/nsHttpConnection.cpp index ecbe8deab0a..bf6d2bbb288 100644 --- a/netwerk/protocol/http/nsHttpConnection.cpp +++ b/netwerk/protocol/http/nsHttpConnection.cpp @@ -72,8 +72,6 @@ using namespace mozilla::net; nsHttpConnection::nsHttpConnection() : mTransaction(nsnull) - , mLastReadTime(0) - , mIdleTimeout(0) , mConsiderReusedAfterInterval(0) , mConsiderReusedAfterEpoch(0) , mCurrentBytesRead(0) @@ -88,6 +86,7 @@ nsHttpConnection::nsHttpConnection() , mIdleMonitoring(false) , mProxyConnectInProgress(false) , mHttp1xTransactionCount(0) + , mClassification(nsAHttpTransaction::CLASS_GENERAL) , mNPNComplete(false) , mSetupNPNCalled(false) , mUsingSpdy(false) @@ -142,21 +141,25 @@ nsHttpConnection::Init(nsHttpConnectionInfo *info, nsIAsyncInputStream *instream, nsIAsyncOutputStream *outstream, nsIInterfaceRequestor *callbacks, - nsIEventTarget *callbackTarget) + nsIEventTarget *callbackTarget, + PRIntervalTime rtt) { NS_ABORT_IF_FALSE(transport && instream && outstream, "invalid socket information"); LOG(("nsHttpConnection::Init [this=%p " - "transport=%p instream=%p outstream=%p]\n", - this, transport, instream, outstream)); + "transport=%p instream=%p outstream=%p rtt=%d]\n", + this, transport, instream, outstream, + PR_IntervalToMilliseconds(rtt))); NS_ENSURE_ARG_POINTER(info); NS_ENSURE_TRUE(!mConnInfo, NS_ERROR_ALREADY_INITIALIZED); mConnInfo = info; - mSupportsPipelining = mConnInfo->SupportsPipelining(); - mMaxHangTime = PR_SecondsToInterval(maxHangTime); mLastReadTime = PR_IntervalNow(); + mSupportsPipelining = + gHttpHandler->ConnMgr()->SupportsPipelining(mConnInfo); + mRtt = rtt; + mMaxHangTime = PR_SecondsToInterval(maxHangTime); mSocketTransport = transport; mSocketIn = instream; @@ -340,6 +343,9 @@ nsHttpConnection::Activate(nsAHttpTransaction *trans, PRUint8 caps, PRInt32 pri) NS_ENSURE_ARG_POINTER(trans); NS_ENSURE_TRUE(!mTransaction, NS_ERROR_IN_PROGRESS); + // reset the read timers to wash away any idle time + mLastReadTime = PR_IntervalNow(); + // Update security callbacks nsCOMPtr callbacks; nsCOMPtr callbackTarget; @@ -537,7 +543,7 @@ nsHttpConnection::CanReuse() canReuse = mSpdySession->CanReuse(); else canReuse = IsKeepAlive(); - + canReuse = canReuse && (IdleTime() < mIdleTimeout) && IsAlive(); // An idle persistent connection should not have data waiting to be read @@ -622,21 +628,19 @@ nsHttpConnection::SupportsPipelining(nsHttpResponseHead *responseHead) if (mUsingSpdy) return false; - // XXX there should be a strict mode available that disables this - // blacklisting. - // assuming connection is HTTP/1.1 with keep-alive enabled if (mConnInfo->UsingHttpProxy() && !mConnInfo->UsingSSL()) { // XXX check for bad proxy servers... return true; } - // XXX what about checking for a Via header? (transparent proxies) - // check for bad origin servers const char *val = responseHead->PeekHeader(nsHttp::Server); + + // If there is no server header we will assume it should not be banned + // as facebook and some other prominent sites do this if (!val) - return false; // no header, no love + return true; // The blacklist is indexed by the first character. All of these servers are // known to return their identifier as the first thing in the server string, @@ -663,6 +667,8 @@ nsHttpConnection::SupportsPipelining(nsHttpResponseHead *responseHead) for (int i = 0; bad_servers[index][i] != nsnull; i++) { if (!PL_strncmp (val, bad_servers[index][i], strlen (bad_servers[index][i]))) { LOG(("looks like this server does not support pipelining")); + gHttpHandler->ConnMgr()->PipelineFeedbackInfo( + mConnInfo, nsHttpConnectionMgr::RedBannedServer, this , 0); return false; } } @@ -718,11 +724,19 @@ nsHttpConnection::OnHeadersAvailable(nsAHttpTransaction *trans, mKeepAlive = true; else mKeepAlive = false; + + // We need at least version 1.1 to use pipelines + gHttpHandler->ConnMgr()->PipelineFeedbackInfo( + mConnInfo, nsHttpConnectionMgr::RedVersionTooLow, this, 0); } else { // HTTP/1.1 connections are by default persistent - if (val && !PL_strcasecmp(val, "close")) + if (val && !PL_strcasecmp(val, "close")) { mKeepAlive = false; + // persistent connections are required for pipelining to work + gHttpHandler->ConnMgr()->PipelineFeedbackInfo( + mConnInfo, nsHttpConnectionMgr::BadExplicitClose, this, 0); + } else { mKeepAlive = true; @@ -741,7 +755,26 @@ nsHttpConnection::OnHeadersAvailable(nsAHttpTransaction *trans, // and also read it back. It is possible the ci status is // locked to false if pipelining has been banned on this ci due to // some kind of observed flaky behavior - mSupportsPipelining = mConnInfo->SetSupportsPipelining(mSupportsPipelining); + if (mSupportsPipelining) { + // report the pipelining-compatible header to the connection manager + // as positive feedback. This will undo 1 penalty point the host + // may have accumulated in the past. + + gHttpHandler->ConnMgr()->PipelineFeedbackInfo( + mConnInfo, nsHttpConnectionMgr::NeutralExpectedOK, this, 0); + + mSupportsPipelining = + gHttpHandler->ConnMgr()->SupportsPipelining(mConnInfo); + } + + // If this connection is reserved for revalidations and we are + // receiving a document that failed revalidation then switch the + // classification to general to avoid pipelining more revalidations behind + // it. + if (mClassification == nsAHttpTransaction::CLASS_REVALIDATION && + responseHead->Status() != 304) { + mClassification = nsAHttpTransaction::CLASS_GENERAL; + } // if this connection is persistent, then the server may send a "Keep-Alive" // header specifying the maximum number of times the connection can be @@ -758,13 +791,13 @@ nsHttpConnection::OnHeadersAvailable(nsAHttpTransaction *trans, if (cp) mIdleTimeout = PR_SecondsToInterval((PRUint32) atoi(cp + 8)); else - mIdleTimeout = gHttpHandler->IdleTimeout(); + mIdleTimeout = gHttpHandler->SpdyTimeout(); } else { mIdleTimeout = gHttpHandler->SpdyTimeout(); } - LOG(("Connection can be reused [this=%x idle-timeout=%usec]\n", + LOG(("Connection can be reused [this=%p idle-timeout=%usec]\n", this, PR_IntervalToSeconds(mIdleTimeout))); } @@ -939,6 +972,14 @@ nsHttpConnection::ResumeRecv() NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + // the mLastReadTime timestamp is used for finding slowish readers + // and can be pretty sensitive. For that reason we actually reset it + // when we ask to read (resume recv()) so that when we get called back + // with actual read data in OnSocketReadable() we are only measuring + // the latency between those two acts and not all the processing that + // may get done before the ResumeRecv() call + mLastReadTime = PR_IntervalNow(); + if (mSocketIn) return mSocketIn->AsyncWait(this, 0, 0, nsnull); @@ -1061,7 +1102,8 @@ nsHttpConnection::OnReadSegment(const char *buf, nsresult nsHttpConnection::OnSocketWritable() { - LOG(("nsHttpConnection::OnSocketWritable [this=%x]\n", this)); + LOG(("nsHttpConnection::OnSocketWritable [this=%p] host=%s\n", + this, mConnInfo->Host())); nsresult rv; PRUint32 n; @@ -1141,7 +1183,7 @@ nsHttpConnection::OnSocketWritable() nsISocketTransport::STATUS_WAITING_FOR, LL_ZERO); - rv = mSocketIn->AsyncWait(this, 0, 0, nsnull); // start reading + rv = ResumeRecv(); // start reading again = false; } // write more to the socket until error or end-of-request... @@ -1180,14 +1222,41 @@ nsHttpConnection::OnSocketReadable() LOG(("nsHttpConnection::OnSocketReadable [this=%x]\n", this)); PRIntervalTime now = PR_IntervalNow(); + PRIntervalTime delta = now - mLastReadTime; - if (mKeepAliveMask && ((now - mLastReadTime) >= mMaxHangTime)) { + if (mKeepAliveMask && (delta >= mMaxHangTime)) { LOG(("max hang time exceeded!\n")); // give the handler a chance to create a new persistent connection to // this host if we've been busy for too long. mKeepAliveMask = false; gHttpHandler->ProcessPendingQ(mConnInfo); } + + // Look for data being sent in bursts with large pauses. If the pauses + // are caused by server bottlenecks such as think-time, disk i/o, or + // cpu exhaustion (as opposed to network latency) then we generate negative + // pipelining feedback to prevent head of line problems + + // Reduce the estimate of the time since last read by up to 1 RTT to + // accommodate exhausted sender TCP congestion windows or minor I/O delays. + + if (delta > mRtt) + delta -= mRtt; + else + delta = 0; + + const PRIntervalTime k400ms = PR_MillisecondsToInterval(400); + const PRIntervalTime k1200ms = PR_MillisecondsToInterval(1200); + + if (delta > k1200ms) { + gHttpHandler->ConnMgr()->PipelineFeedbackInfo( + mConnInfo, nsHttpConnectionMgr::BadSlowReadMajor, this, 0); + } + else if (delta > k400ms) { + gHttpHandler->ConnMgr()->PipelineFeedbackInfo( + mConnInfo, nsHttpConnectionMgr::BadSlowReadMinor, this, 0); + } + mLastReadTime = now; nsresult rv; @@ -1209,7 +1278,7 @@ nsHttpConnection::OnSocketReadable() if (NS_FAILED(mSocketInCondition)) { // continue waiting for the socket if necessary... if (mSocketInCondition == NS_BASE_STREAM_WOULD_BLOCK) - rv = mSocketIn->AsyncWait(this, 0, 0, nsnull); + rv = ResumeRecv(); else rv = mSocketInCondition; again = false; diff --git a/netwerk/protocol/http/nsHttpConnection.h b/netwerk/protocol/http/nsHttpConnection.h index 762f4ac221d..6239ef0f717 100644 --- a/netwerk/protocol/http/nsHttpConnection.h +++ b/netwerk/protocol/http/nsHttpConnection.h @@ -47,6 +47,7 @@ #include "nsAutoPtr.h" #include "prinrval.h" #include "SpdySession.h" +#include "mozilla/TimeStamp.h" #include "nsIStreamListener.h" #include "nsISocketTransport.h" @@ -92,7 +93,7 @@ public: nsresult Init(nsHttpConnectionInfo *info, PRUint16 maxHangTime, nsISocketTransport *, nsIAsyncInputStream *, nsIAsyncOutputStream *, nsIInterfaceRequestor *, - nsIEventTarget *); + nsIEventTarget *, PRIntervalTime); // Activate causes the given transaction to be processed on this // connection. It fails if there is already an existing transaction unless @@ -105,7 +106,7 @@ public: //------------------------------------------------------------------------- // XXX document when these are ok to call - bool SupportsPipelining() { return mSupportsPipelining; } + bool SupportsPipelining() { return mSupportsPipelining && IsKeepAlive(); } bool IsKeepAlive() { return mUsingSpdy || (mKeepAliveMask && mKeepAlive); } bool CanReuse(); // can this connection be reused? @@ -168,6 +169,12 @@ public: // When the connection is active this is called every 15 seconds void ReadTimeoutTick(PRIntervalTime now); + nsAHttpTransaction::Classifier Classification() { return mClassification; } + void Classify(nsAHttpTransaction::Classifier newclass) + { + mClassification = newclass; + } + private: // called to cause the underlying socket to start speaking SSL nsresult ProxyStartSSL(); @@ -217,7 +224,7 @@ private: nsRefPtr mConnInfo; - PRUint32 mLastReadTime; + PRIntervalTime mLastReadTime; PRIntervalTime mMaxHangTime; // max download time before dropping keep-alive status PRIntervalTime mIdleTimeout; // value of keep-alive: timeout= PRIntervalTime mConsiderReusedAfterInterval; @@ -228,6 +235,8 @@ private: nsRefPtr mInputOverflow; + PRIntervalTime mRtt; + bool mKeepAlive; bool mKeepAliveMask; bool mSupportsPipelining; @@ -241,6 +250,8 @@ private: // excludes spdy transactions. PRUint32 mHttp1xTransactionCount; + nsAHttpTransaction::Classifier mClassification; + // SPDY related bool mNPNComplete; bool mSetupNPNCalled; diff --git a/netwerk/protocol/http/nsHttpConnectionInfo.cpp b/netwerk/protocol/http/nsHttpConnectionInfo.cpp index 3747122370d..d7442047319 100644 --- a/netwerk/protocol/http/nsHttpConnectionInfo.cpp +++ b/netwerk/protocol/http/nsHttpConnectionInfo.cpp @@ -100,27 +100,6 @@ nsHttpConnectionInfo::Clone() const return clone; } -bool -nsHttpConnectionInfo::SupportsPipelining() -{ - return mSupportsPipelining; -} - -bool -nsHttpConnectionInfo::SetSupportsPipelining(bool support) -{ - if (!mBannedPipelining) - mSupportsPipelining = support; - return mSupportsPipelining; -} - -void -nsHttpConnectionInfo::BanPipelining() -{ - mBannedPipelining = true; - mSupportsPipelining = false; -} - bool nsHttpConnectionInfo::ShouldForceConnectMethod() { diff --git a/netwerk/protocol/http/nsHttpConnectionInfo.h b/netwerk/protocol/http/nsHttpConnectionInfo.h index 8daab44b8bf..ce469f8a38e 100644 --- a/netwerk/protocol/http/nsHttpConnectionInfo.h +++ b/netwerk/protocol/http/nsHttpConnectionInfo.h @@ -60,8 +60,6 @@ public: : mRef(0) , mProxyInfo(proxyInfo) , mUsingSSL(usingSSL) - , mSupportsPipelining(false) - , mBannedPipelining(false) { LOG(("Creating nsHttpConnectionInfo @%x\n", this)); @@ -131,10 +129,6 @@ public: bool ShouldForceConnectMethod(); const nsCString &GetHost() { return mHost; } - bool SupportsPipelining(); - bool SetSupportsPipelining(bool support); - void BanPipelining(); - private: nsrefcnt mRef; nsCString mHashKey; @@ -143,8 +137,6 @@ private: nsCOMPtr mProxyInfo; bool mUsingHttpProxy; bool mUsingSSL; - bool mSupportsPipelining; - bool mBannedPipelining; }; #endif // nsHttpConnectionInfo_h__ diff --git a/netwerk/protocol/http/nsHttpConnectionMgr.cpp b/netwerk/protocol/http/nsHttpConnectionMgr.cpp index 292850c8db3..9c48557ed7c 100644 --- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp +++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp @@ -143,7 +143,8 @@ nsHttpConnectionMgr::Init(PRUint16 maxConns, PRUint16 maxPersistConnsPerHost, PRUint16 maxPersistConnsPerProxy, PRUint16 maxRequestDelay, - PRUint16 maxPipelinedRequests) + PRUint16 maxPipelinedRequests, + PRUint16 maxOptimisticPipelinedRequests) { LOG(("nsHttpConnectionMgr::Init\n")); @@ -157,6 +158,7 @@ nsHttpConnectionMgr::Init(PRUint16 maxConns, mMaxPersistConnsPerProxy = maxPersistConnsPerProxy; mMaxRequestDelay = maxRequestDelay; mMaxPipelinedRequests = maxPipelinedRequests; + mMaxOptimisticPipelinedRequests = maxOptimisticPipelinedRequests; mIsShuttingDown = false; } @@ -211,10 +213,7 @@ nsHttpConnectionMgr::PostEvent(nsConnEventHandler handler, PRInt32 iparam, void } else { nsRefPtr event = new nsConnEvent(this, handler, iparam, vparam); - if (!event) - rv = NS_ERROR_OUT_OF_MEMORY; - else - rv = mSocketThreadTarget->Dispatch(event, NS_DISPATCH_NORMAL); + rv = mSocketThreadTarget->Dispatch(event, NS_DISPATCH_NORMAL); } return rv; } @@ -349,47 +348,6 @@ nsHttpConnectionMgr::GetSocketThreadTarget(nsIEventTarget **target) 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 ci; - pipeline->GetConnectionInfo(getter_AddRefs(ci)); - if (ci && avail && ci->SupportsPipelining()) { - nsConnectionEntry *ent = mCT.Get(ci->HashKey()); - if (ent) { - // search for another request to pipeline... - PRInt32 i, count = ent->mPendingQ.Length(); - for (i = 0; i < count; ) { - nsHttpTransaction *trans = ent->mPendingQ[i]; - if (trans->Caps() & NS_HTTP_ALLOW_PIPELINING) { - pipeline->AddTransaction(trans); - - // remove transaction from pending queue - ent->mPendingQ.RemoveElementAt(i); - --count; - - NS_RELEASE(trans); - if (--avail == 0) - break; - } - else { - ++i; - } - } - } - } -} - nsresult nsHttpConnectionMgr::ReclaimConnection(nsHttpConnection *conn) { @@ -822,18 +780,14 @@ nsHttpConnectionMgr::PruneDeadConnectionsCB(const nsACString &key, } else { self->ConditionallyStopPruneDeadConnectionsTimer(); } -#ifdef DEBUG - count = ent->mActiveConns.Length(); - if (count > 0) { - for (PRInt32 i=count-1; i>=0; --i) { - nsHttpConnection *conn = ent->mActiveConns[i]; - LOG((" active conn [%x] with trans [%x]\n", conn, conn->Transaction())); - } - } -#endif - // if this entry is empty, then we can remove it. - if (ent->mIdleConns.Length() == 0 && + // if this entry is empty, we have too many entries, + // and this doesn't represent some painfully determined + // red condition, then we can clean it up and restart from + // yellow + if (ent->PipelineState() != PS_RED && + self->mCT.Count() > 125 && + ent->mIdleConns.Length() == 0 && ent->mActiveConns.Length() == 0 && ent->mHalfOpens.Length() == 0 && ent->mPendingQ.Length() == 0 && @@ -844,7 +798,7 @@ nsHttpConnectionMgr::PruneDeadConnectionsCB(const nsACString &key, return PL_DHASH_REMOVE; } - // else, use this opportunity to compact our arrays... + // otherwise use this opportunity to compact our arrays... ent->mIdleConns.Compact(); ent->mActiveConns.Compact(); ent->mPendingQ.Compact(); @@ -910,125 +864,119 @@ bool nsHttpConnectionMgr::ProcessPendingQForEntry(nsConnectionEntry *ent) { NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + LOG(("nsHttpConnectionMgr::ProcessPendingQForEntry [ci=%s]\n", - ent->mConnInfo->HashKey().get())); + ent->mConnInfo->HashKey().get())); ProcessSpdyPendingQ(ent); - PRUint32 i, count = ent->mPendingQ.Length(); - if (count > 0) { - LOG((" pending-count=%u\n", count)); - nsHttpTransaction *trans = nsnull; - nsHttpConnection *conn = nsnull; - for (i = 0; i < count; ++i) { - trans = ent->mPendingQ[i]; + PRInt32 count = ent->mPendingQ.Length(); + nsHttpTransaction *trans; + nsresult rv; + + for (PRInt32 i = 0; i < count; ++i) { + trans = ent->mPendingQ[i]; - // When this transaction has already established a half-open - // connection, we want to prevent any duplicate half-open - // connections from being established and bound to this - // transaction. Allow only use of an idle persistent connection - // (if found) for transactions referred by a half-open connection. - bool alreadyHalfOpen = false; - for (PRInt32 j = 0; j < ((PRInt32) ent->mHalfOpens.Length()); j++) { - if (ent->mHalfOpens[j]->Transaction() == trans) { - alreadyHalfOpen = true; - break; - } - } - - GetConnection(ent, trans, alreadyHalfOpen, &conn); - if (conn) + // When this transaction has already established a half-open + // connection, we want to prevent any duplicate half-open + // connections from being established and bound to this + // transaction. Allow only use of an idle persistent connection + // (if found) for transactions referred by a half-open connection. + bool alreadyHalfOpen = false; + for (PRInt32 j = 0; j < ((PRInt32) ent->mHalfOpens.Length()); ++j) { + if (ent->mHalfOpens[j]->Transaction() == trans) { + alreadyHalfOpen = true; break; - - NS_ABORT_IF_FALSE(count == ent->mPendingQ.Length(), - "something mutated pending queue from " - "GetConnection()"); - } - if (conn) { - LOG((" dispatching pending transaction...\n")); - - // remove pending transaction - ent->mPendingQ.RemoveElementAt(i); - - nsresult rv = DispatchTransaction(ent, trans, trans->Caps(), conn); - if (NS_SUCCEEDED(rv)) - NS_RELEASE(trans); - else { - LOG((" DispatchTransaction failed [rv=%x]\n", rv)); - // on failure, just put the transaction back - ent->mPendingQ.InsertElementAt(i, trans); - // might be something wrong with the connection... close it. - conn->Close(rv); } + } - NS_RELEASE(conn); + rv = TryDispatchTransaction(ent, alreadyHalfOpen, trans); + if (NS_SUCCEEDED(rv)) { + LOG((" dispatching pending transaction...\n")); + ent->mPendingQ.RemoveElementAt(i); + NS_RELEASE(trans); return true; } + + NS_ABORT_IF_FALSE(count == ((PRInt32) ent->mPendingQ.Length()), + "something mutated pending queue from " + "GetConnection()"); } return false; } bool -nsHttpConnectionMgr::ProcessPipelinePendingQForEntry(nsConnectionEntry *ent) +nsHttpConnectionMgr::ProcessPendingQForEntry(nsHttpConnectionInfo *ci) { - LOG(("nsHttpConnectionMgr::ProcessPipelinePendingQForEntry [ci=%s]\n", - ent->mConnInfo->HashKey().get())); + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); - 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; + nsConnectionEntry *ent = mCT.Get(ci->HashKey()); + if (ent) + return ProcessPendingQForEntry(ent); + return false; } bool -nsHttpConnectionMgr::ProcessPipelinePendingQForCI(nsHttpConnectionInfo *ci) +nsHttpConnectionMgr::SupportsPipelining(nsHttpConnectionInfo *ci) { - NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + nsConnectionEntry *ent = mCT.Get(ci->HashKey()); + if (ent) + return ent->SupportsPipelining(); + return false; +} + +// nsHttpPipelineFeedback used to hold references across events + +class nsHttpPipelineFeedback +{ +public: + nsHttpPipelineFeedback(nsHttpConnectionInfo *ci, + nsHttpConnectionMgr::PipelineFeedbackInfoType info, + nsHttpConnection *conn, PRUint32 data) + : mConnInfo(ci) + , mConn(conn) + , mInfo(info) + , mData(data) + { + } + ~nsHttpPipelineFeedback() + { + } + + nsRefPtr mConnInfo; + nsRefPtr mConn; + nsHttpConnectionMgr::PipelineFeedbackInfoType mInfo; + PRUint32 mData; +}; + +void +nsHttpConnectionMgr::PipelineFeedbackInfo(nsHttpConnectionInfo *ci, + PipelineFeedbackInfoType info, + nsHttpConnection *conn, + PRUint32 data) +{ + if (!ci) + return; + + // Post this to the socket thread if we are not running there already + if (PR_GetCurrentThread() != gSocketThread) { + nsHttpPipelineFeedback *fb = new nsHttpPipelineFeedback(ci, info, + conn, data); + + nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgProcessFeedback, + 0, fb); + if (NS_FAILED(rv)) + delete fb; + return; + } + nsConnectionEntry *ent = mCT.Get(ci->HashKey()); - return ent && ProcessPipelinePendingQForEntry(ent); + if (ent) + ent->OnPipelineFeedbackInfo(info, conn, data); } // we're at the active connection limit if any one of the following conditions is true: @@ -1124,122 +1072,458 @@ nsHttpConnectionMgr::ClosePersistentConnectionsCB(const nsACString &key, return PL_DHASH_NEXT; } -void -nsHttpConnectionMgr::GetConnection(nsConnectionEntry *ent, - nsHttpTransaction *trans, - bool onlyReusedConnection, - nsHttpConnection **result) +bool +nsHttpConnectionMgr::MakeNewConnection(nsConnectionEntry *ent, + nsHttpTransaction *trans) { - LOG(("nsHttpConnectionMgr::GetConnection [ci=%s caps=%x]\n", - ent->mConnInfo->HashKey().get(), PRUint32(trans->Caps()))); - - // First, see if an existing connection can be used - either an idle - // persistent connection or an active spdy session may be reused instead of - // establishing a new socket. We do not need to check the connection limits - // yet as they govern the maximum number of open connections and reusing - // an old connection never increases that. - - *result = nsnull; - - nsHttpConnection *conn = nsnull; - bool addConnToActiveList = true; - - if (trans->Caps() & NS_HTTP_ALLOW_KEEPALIVE) { - - // if willing to use spdy look for an active spdy connections - // before considering idle http ones. - if (gHttpHandler->IsSpdyEnabled()) { - conn = GetSpdyPreferredConn(ent); - if (conn) - addConnToActiveList = false; - } + LOG(("nsHttpConnectionMgr::MakeNewConnection %p ent=%p trans=%p", + this, ent, trans)); + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); - // search the idle connection list. Each element in the list - // has a reference, so if we remove it from the list into a local - // ptr, that ptr now owns the reference + // If this host is trying to negotiate a SPDY session right now, + // don't create any new connections until the result of the + // negotiation is known. + + if (gHttpHandler->IsSpdyEnabled() && + ent->mConnInfo->UsingSSL() && + !ent->mConnInfo->UsingHttpProxy() && + !(trans->Caps() & NS_HTTP_DISALLOW_SPDY) && + (!ent->mTestedSpdy || ent->mUsingSpdy) && + (ent->mHalfOpens.Length() || ent->mActiveConns.Length())) { + return false; + } + + // We need to make a new connection. If that is going to exceed the + // global connection limit then try and free up some room by closing + // an idle connection to another host. We know it won't select "ent" + // beacuse we have already determined there are no idle connections + // to our destination + + if ((mNumIdleConns + mNumActiveConns + 1 >= mMaxConns) && mNumIdleConns) + mCT.Enumerate(PurgeExcessIdleConnectionsCB, this); + + if (AtActiveConnectionLimit(ent, trans->Caps())) + return false; + + nsresult rv = CreateTransport(ent, trans); + if (NS_FAILED(rv)) /* hard failure */ + trans->Close(rv); + + return true; +} + +bool +nsHttpConnectionMgr::AddToShortestPipeline(nsConnectionEntry *ent, + nsHttpTransaction *trans, + nsHttpTransaction::Classifier classification, + PRUint16 depthLimit) +{ + if (classification == nsAHttpTransaction::CLASS_SOLO) + return false; + + PRUint32 maxdepth = ent->MaxPipelineDepth(classification); + if (maxdepth == 0) { + ent->CreditPenalty(); + maxdepth = ent->MaxPipelineDepth(classification); + } + + if (ent->PipelineState() == PS_RED) + return false; + + if (ent->PipelineState() == PS_YELLOW && ent->mYellowConnection) + return false; + + // The maximum depth of a pipeline in yellow is 1 pipeline of + // depth 2 for entire CI. When that transaction completes successfully + // we transition to green and that expands the allowed depth + // to any number of pipelines of up to depth 4. When a transaction + // queued at position 3 or deeper succeeds we open it all the way + // up to depths limited only by configuration. The staggered start + // in green is simply because a successful yellow test of depth=2 + // might really just be a race condition (i.e. depth=1 from the + // server's point of view), while depth=3 is a stronger indicator - + // keeping the pipelines to a modest depth during that period limits + // the damage if something is going to go wrong. + + maxdepth = PR_MIN(maxdepth, depthLimit); + + if (maxdepth < 2) + return false; + + nsAHttpTransaction *activeTrans; + + nsHttpConnection *bestConn = nsnull; + PRUint32 activeCount = ent->mActiveConns.Length(); + PRUint32 bestConnLength = 0; + PRUint32 connLength; + + for (PRUint32 i = 0; i < activeCount; ++i) { + nsHttpConnection *conn = ent->mActiveConns[i]; + if (!conn->SupportsPipelining()) + continue; + + if (conn->Classification() != classification) + continue; + + activeTrans = conn->Transaction(); + if (!activeTrans || + activeTrans->IsDone() || + NS_FAILED(activeTrans->Status())) + continue; + + connLength = activeTrans->PipelineDepth(); + + if (maxdepth <= connLength) + continue; + + if (!bestConn || (connLength < bestConnLength)) { + bestConn = conn; + bestConnLength = connLength; + } + } + + if (!bestConn) + return false; + + activeTrans = bestConn->Transaction(); + nsresult rv = activeTrans->AddTransaction(trans); + if (NS_FAILED(rv)) + return false; + + LOG((" scheduling trans %p on pipeline at position %d\n", + trans, trans->PipelinePosition())); + + if ((ent->PipelineState() == PS_YELLOW) && (trans->PipelinePosition() > 1)) + ent->SetYellowConnection(bestConn); + return true; +} + +bool +nsHttpConnectionMgr::IsUnderPressure(nsConnectionEntry *ent, + nsHttpTransaction::Classifier classification) +{ + // A connection entry is declared to be "under pressure" if most of the + // allowed parallel connections are already used up. In that case we want to + // favor existing pipelines over more parallelism so as to reserve any + // unused parallel connections for types that don't have existing pipelines. + // + // The defintion of connection pressure is a pretty liberal one here - that + // is why we are using the more restrictive maxPersist* counters. + // + // Pipelines are also favored when the requested classification is already + // using 3 or more of the connections. Failure to do this could result in + // one class (e.g. images) establishing self replenishing queues on all the + // connections that would starve the other transaction types. + + PRInt32 currentConns = ent->mActiveConns.Length(); + PRInt32 maxConns = + (ent->mConnInfo->UsingHttpProxy() && !ent->mConnInfo->UsingSSL()) ? + mMaxPersistConnsPerProxy : mMaxPersistConnsPerHost; + + // Leave room for at least 3 distinct types to operate concurrently, + // this satisfies the typical {html, js/css, img} page. + if (currentConns >= (maxConns - 2)) + return true; /* prefer pipeline */ + + PRInt32 sameClass = 0; + for (PRInt32 i = 0; i < currentConns; ++i) + if (classification == ent->mActiveConns[i]->Classification()) + if (++sameClass == 3) + return true; /* prefer pipeline */ + + return false; /* normal behavior */ +} + +// returns OK if a connection is found for the transaction +// and the transaction is started. +// returns ERROR_NOT_AVAILABLE if no connection can be found and it +// should be queued +nsresult +nsHttpConnectionMgr::TryDispatchTransaction(nsConnectionEntry *ent, + bool onlyReusedConnection, + nsHttpTransaction *trans) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + LOG(("nsHttpConnectionMgr::TryDispatchTransaction without conn " + "[ci=%s caps=%x]\n", + ent->mConnInfo->HashKey().get(), PRUint32(trans->Caps()))); + + nsHttpTransaction::Classifier classification = trans->Classification(); + PRUint8 caps = trans->Caps(); + + // no keep-alive means no pipelines either + if (!(caps & NS_HTTP_ALLOW_KEEPALIVE)) + caps = caps & ~NS_HTTP_ALLOW_PIPELINING; + + // 0 - If this should use spdy then dispatch it post haste. + // 1 - If there is connection pressure then see if we can pipeline this on + // a connection of a matching type instead of using a new conn + // 2 - If there is an idle connection, use it! + // 3 - if class == reval or script and there is an open conn of that type + // then pipeline onto shortest pipeline of that class if limits allow + // 4 - If we aren't up against our connection limit, + // then open a new one + // 5 - Try a pipeline if we haven't already - this will be unusual because + // it implies a low connection pressure situation where + // MakeNewConnection() failed.. that is possible, but unlikely, due to + // global limits + // 6 - no connection is available - queue it + + bool attemptedOptimisticPipeline = !(caps & NS_HTTP_ALLOW_PIPELINING); + + // step 0 + // look for existing spdy connection - that's always best because it is + // essentially pipelining without head of line blocking + + if (!(caps & NS_HTTP_DISALLOW_SPDY) && gHttpHandler->IsSpdyEnabled()) { + nsRefPtr conn = GetSpdyPreferredConn(ent); + if (conn) { + LOG((" dispatch to spdy: [conn=%x]\n", conn.get())); + DispatchTransaction(ent, trans, conn); + return NS_OK; + } + } + + // step 1 + // If connection pressure, then we want to favor pipelining of any kind + if (IsUnderPressure(ent, classification) && !attemptedOptimisticPipeline) { + attemptedOptimisticPipeline = true; + if (AddToShortestPipeline(ent, trans, + classification, + mMaxOptimisticPipelinedRequests)) { + return NS_OK; + } + } + + // step 2 + // consider an idle persistent connection + if (caps & NS_HTTP_ALLOW_KEEPALIVE) { + nsRefPtr conn; while (!conn && (ent->mIdleConns.Length() > 0)) { conn = ent->mIdleConns[0]; + ent->mIdleConns.RemoveElementAt(0); + mNumIdleConns--; + nsHttpConnection *temp = conn; + NS_RELEASE(temp); + // we check if the connection can be reused before even checking if // it is a "matching" connection. if (!conn->CanReuse()) { - LOG((" dropping stale connection: [conn=%x]\n", conn)); + LOG((" dropping stale connection: [conn=%x]\n", conn.get())); conn->Close(NS_ERROR_ABORT); - NS_RELEASE(conn); + conn = nsnull; } else { - LOG((" reusing connection [conn=%x]\n", conn)); + LOG((" reusing connection [conn=%x]\n", conn.get())); conn->EndIdleMonitoring(); } - ent->mIdleConns.RemoveElementAt(0); - mNumIdleConns--; - // If there are no idle connections left at all, we need to make // sure that we are not pruning dead connections anymore. ConditionallyStopPruneDeadConnectionsTimer(); } + if (conn) { + // This will update the class of the connection to be the class of + // the transaction dispatched on it. + AddActiveConn(conn, ent); + DispatchTransaction(ent, trans, conn); + return NS_OK; + } } - if (!conn) { - - // If the onlyReusedConnection parameter is TRUE, then GetConnection() - // does not create new transports under any circumstances. - if (onlyReusedConnection) - return; - - if (gHttpHandler->IsSpdyEnabled() && - ent->mConnInfo->UsingSSL() && - !ent->mConnInfo->UsingHttpProxy()) - { - // If this host is trying to negotiate a SPDY session right now, - // don't create any new connections until the result of the - // negotiation is known. - - if ((!ent->mTestedSpdy || ent->mUsingSpdy) && - (ent->mHalfOpens.Length() || ent->mActiveConns.Length())) - return; + // step 3 + // consider pipelining scripts and revalidations + if (!attemptedOptimisticPipeline && + (classification == nsHttpTransaction::CLASS_REVALIDATION || + classification == nsHttpTransaction::CLASS_SCRIPT)) { + attemptedOptimisticPipeline = true; + if (AddToShortestPipeline(ent, trans, + classification, + mMaxOptimisticPipelinedRequests)) { + return NS_OK; } - - // Check if we need to purge an idle connection. Note that we may have - // removed one above; if so, this will be a no-op. We do this before - // checking the active connection limit to catch the case where we do - // have an idle connection, but the purge timer hasn't fired yet. - // XXX this just purges a random idle connection. we should instead - // enumerate the entire hash table to find the eldest idle connection. - if (mNumIdleConns && mNumIdleConns + mNumActiveConns + 1 >= mMaxConns) - mCT.Enumerate(PurgeExcessIdleConnectionsCB, this); - - // Need to make a new TCP connection. First, we check if we've hit - // either the maximum connection limit globally or for this particular - // host or proxy. If we have, we're done. - if (AtActiveConnectionLimit(ent, trans->Caps())) { - LOG(("nsHttpConnectionMgr::GetConnection [ci = %s]" - "at active connection limit - will queue\n", - ent->mConnInfo->HashKey().get())); - return; - } - - LOG(("nsHttpConnectionMgr::GetConnection Open Connection " - "%s %s ent=%p spdy=%d", - ent->mConnInfo->Host(), ent->mCoalescingKey.get(), - ent, ent->mUsingSpdy)); - - nsresult rv = CreateTransport(ent, trans); - if (NS_FAILED(rv)) - trans->Close(rv); - return; } - if (addConnToActiveList) { - // hold an owning ref to this connection - ent->mActiveConns.AppendElement(conn); - mNumActiveConns++; + // step 4 + if (!onlyReusedConnection && MakeNewConnection(ent, trans)) { + return NS_ERROR_IN_PROGRESS; } - NS_ADDREF(conn); - *result = conn; + // step 5 + if (caps & NS_HTTP_ALLOW_PIPELINING) { + if (AddToShortestPipeline(ent, trans, + classification, + mMaxPipelinedRequests)) { + return NS_OK; + } + } + + // step 6 + return NS_ERROR_NOT_AVAILABLE; /* queue it */ } +nsresult +nsHttpConnectionMgr::DispatchTransaction(nsConnectionEntry *ent, + nsHttpTransaction *trans, + nsHttpConnection *conn) +{ + PRUint8 caps = trans->Caps(); + PRInt32 priority = trans->Priority(); + + LOG(("nsHttpConnectionMgr::DispatchTransaction " + "[ci=%s trans=%x caps=%x conn=%x priority=%d]\n", + ent->mConnInfo->HashKey().get(), trans, caps, conn, priority)); + + if (conn->UsingSpdy()) { + LOG(("Spdy Dispatch Transaction via Activate(). Transaction host = %s," + "Connection host = %s\n", + trans->ConnectionInfo()->Host(), + conn->ConnectionInfo()->Host())); + nsresult rv = conn->Activate(trans, caps, priority); + NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv), "SPDY Cannot Fail Dispatch"); + return rv; + } + + NS_ABORT_IF_FALSE(conn && !conn->Transaction(), + "DispatchTranaction() on non spdy active connection"); + + /* Use pipeline datastructure even if connection does not currently qualify + to pipeline this transaction because a different pipeline-eligible + transaction might be placed on the active connection */ + + nsRefPtr pipeline; + nsresult rv = BuildPipeline(ent, trans, getter_AddRefs(pipeline)); + if (!NS_SUCCEEDED(rv)) + return rv; + + nsRefPtr handle = new nsConnectionHandle(conn); + + // give the transaction the indirect reference to the connection. + pipeline->SetConnection(handle); + + if (!(caps & NS_HTTP_ALLOW_PIPELINING)) + conn->Classify(nsAHttpTransaction::CLASS_SOLO); + else + conn->Classify(trans->Classification()); + + rv = conn->Activate(pipeline, caps, priority); + if (NS_FAILED(rv)) { + LOG((" conn->Activate failed [rv=%x]\n", rv)); + ent->mActiveConns.RemoveElement(conn); + if (conn == ent->mYellowConnection) + ent->OnYellowComplete(); + mNumActiveConns--; + // sever back references to connection, and do so without triggering + // a call to ReclaimConnection ;-) + pipeline->SetConnection(nsnull); + NS_RELEASE(handle->mConn); + // destroy the connection + NS_RELEASE(conn); + } + + // As pipeline goes out of scope it will drop the last refernece to the + // pipeline if activation failed, in which case this will destroy + // the pipeline, which will cause each the transactions owned by the + // pipeline to be restarted. + + return rv; +} + +nsresult +nsHttpConnectionMgr::BuildPipeline(nsConnectionEntry *ent, + nsAHttpTransaction *firstTrans, + nsHttpPipeline **result) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + /* form a pipeline here even if nothing is pending so that we + can stream-feed it as new transactions arrive */ + + /* the first transaction can go in unconditionally - 1 transaction + on a nsHttpPipeline object is not a real HTTP pipeline */ + + nsRefPtr pipeline = new nsHttpPipeline(); + pipeline->AddTransaction(firstTrans); + NS_ADDREF(*result = pipeline); + return NS_OK; +} + +nsresult +nsHttpConnectionMgr::ProcessNewTransaction(nsHttpTransaction *trans) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + // since "adds" and "cancels" are processed asynchronously and because + // various events might trigger an "add" directly on the socket thread, + // we must take care to avoid dispatching a transaction that has already + // been canceled (see bug 190001). + if (NS_FAILED(trans->Status())) { + LOG((" transaction was canceled... dropping event!\n")); + return NS_OK; + } + + nsresult rv = NS_OK; + nsHttpConnectionInfo *ci = trans->ConnectionInfo(); + NS_ASSERTION(ci, "no connection info"); + + nsConnectionEntry *ent = mCT.Get(ci->HashKey()); + if (!ent) { + nsHttpConnectionInfo *clone = ci->Clone(); + if (!clone) + return NS_ERROR_OUT_OF_MEMORY; + ent = new nsConnectionEntry(clone); + mCT.Put(ci->HashKey(), ent); + } + + // SPDY coalescing of hostnames means we might redirect from this + // connection entry onto the preferred one. + nsConnectionEntry *preferredEntry = GetSpdyPreferredEnt(ent); + if (preferredEntry && (preferredEntry != ent)) { + LOG(("nsHttpConnectionMgr::ProcessNewTransaction trans=%p " + "redirected via coalescing from %s to %s\n", trans, + ent->mConnInfo->Host(), preferredEntry->mConnInfo->Host())); + + ent = preferredEntry; + } + + // If we are doing a force reload then close out any existing conns + // to this host so that changes in DNS, LBs, etc.. are reflected + if (trans->Caps() & NS_HTTP_CLEAR_KEEPALIVES) + ClosePersistentConnections(ent); + + // Check if the transaction already has a sticky reference to a connection. + // If so, then we can just use it directly by transferring its reference + // to the new connection variable instead of searching for a new one + + nsAHttpConnection *wrappedConnection = trans->Connection(); + nsRefPtr conn; + if (wrappedConnection) + conn = dont_AddRef(wrappedConnection->TakeHttpConnection()); + + if (conn) { + NS_ASSERTION(trans->Caps() & NS_HTTP_STICKY_CONNECTION, + "unexpected caps"); + NS_ABORT_IF_FALSE(((PRInt32)ent->mActiveConns.IndexOf(conn)) != -1, + "Sticky Connection Not In Active List"); + trans->SetConnection(nsnull); + rv = DispatchTransaction(ent, trans, conn); + } + else + rv = TryDispatchTransaction(ent, false, trans); + + if (NS_FAILED(rv)) { + LOG((" adding transaction to pending queue " + "[trans=%p pending-count=%u]\n", + trans, ent->mPendingQ.Length()+1)); + // put this transaction on the pending queue... + InsertTransactionSorted(ent->mPendingQ, trans); + NS_ADDREF(trans); + } + + return NS_OK; +} + + void nsHttpConnectionMgr::AddActiveConn(nsHttpConnection *conn, nsConnectionEntry *ent) @@ -1276,204 +1560,6 @@ nsHttpConnectionMgr::CreateTransport(nsConnectionEntry *ent, return NS_OK; } -nsresult -nsHttpConnectionMgr::DispatchTransaction(nsConnectionEntry *ent, - nsHttpTransaction *aTrans, - PRUint8 caps, - nsHttpConnection *conn) -{ - LOG(("nsHttpConnectionMgr::DispatchTransaction [ci=%s trans=%x caps=%x conn=%x]\n", - ent->mConnInfo->HashKey().get(), aTrans, caps, conn)); - nsresult rv; - - PRInt32 priority = aTrans->Priority(); - - if (conn->UsingSpdy()) { - LOG(("Spdy Dispatch Transaction via Activate(). Transaction host = %s," - "Connection host = %s\n", - aTrans->ConnectionInfo()->Host(), - conn->ConnectionInfo()->Host())); - rv = conn->Activate(aTrans, caps, priority); - NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv), "SPDY Cannot Fail Dispatch"); - return rv; - } - - nsConnectionHandle *handle = new nsConnectionHandle(conn); - if (!handle) - return NS_ERROR_OUT_OF_MEMORY; - NS_ADDREF(handle); - - nsHttpPipeline *pipeline = nsnull; - nsAHttpTransaction *trans = aTrans; - - /* Use pipeline datastructure even if connection does not currently qualify - to pipeline this transaction because a different pipeline-eligible - transaction might be placed on the active connection */ - - if (BuildPipeline(ent, trans, &pipeline)) - trans = pipeline; - - // give the transaction the indirect reference to the connection. - trans->SetConnection(handle); - - rv = conn->Activate(trans, caps, priority); - - if (NS_FAILED(rv)) { - LOG((" conn->Activate failed [rv=%x]\n", rv)); - ent->mActiveConns.RemoveElement(conn); - mNumActiveConns--; - // sever back references to connection, and do so without triggering - // a call to ReclaimConnection ;-) - trans->SetConnection(nsnull); - NS_RELEASE(handle->mConn); - // destroy the connection - NS_RELEASE(conn); - } - - // if we were unable to activate the pipeline, then this will destroy - // the pipeline, which will cause each the transactions owned by the - // pipeline to be restarted. - NS_IF_RELEASE(pipeline); - - NS_RELEASE(handle); - return rv; -} - -bool -nsHttpConnectionMgr::BuildPipeline(nsConnectionEntry *ent, - nsAHttpTransaction *firstTrans, - nsHttpPipeline **result) -{ - NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); - - if (mMaxPipelinedRequests < 2) - return false; - - /* form a pipeline here even if nothing is pending so that we - can stream-feed it as new transactions arrive */ - - nsHttpPipeline *pipeline = new nsHttpPipeline(mMaxPipelinedRequests); - - /* the first transaction can go in unconditionally - 1 transaction - on a nsHttpPipeline object is not a real HTTP pipeline */ - - PRUint16 numAdded = 1; - pipeline->AddTransaction(firstTrans); - - if (ent->mConnInfo->SupportsPipelining() && - firstTrans->Caps() & NS_HTTP_ALLOW_PIPELINING) { - PRUint32 i = 0; - nsHttpTransaction *trans; - - while (i < ent->mPendingQ.Length()) { - trans = ent->mPendingQ[i]; - if (trans->Caps() & NS_HTTP_ALLOW_PIPELINING) { - pipeline->AddTransaction(trans); - - // remove transaction from pending queue - ent->mPendingQ.RemoveElementAt(i); - NS_RELEASE(trans); - - if (++numAdded == mMaxPipelinedRequests) - break; - } - else { - ++i; // skip to next pending transaction - } - } - } - - if (numAdded > 1) - LOG((" pipelined %u transactions\n", numAdded)); - - NS_ADDREF(*result = pipeline); - return true; -} - -nsresult -nsHttpConnectionMgr::ProcessNewTransaction(nsHttpTransaction *trans) -{ - NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); - - // since "adds" and "cancels" are processed asynchronously and because - // various events might trigger an "add" directly on the socket thread, - // we must take care to avoid dispatching a transaction that has already - // been canceled (see bug 190001). - if (NS_FAILED(trans->Status())) { - LOG((" transaction was canceled... dropping event!\n")); - return NS_OK; - } - - PRUint8 caps = trans->Caps(); - nsHttpConnectionInfo *ci = trans->ConnectionInfo(); - NS_ASSERTION(ci, "no connection info"); - - nsConnectionEntry *ent = mCT.Get(ci->HashKey()); - if (!ent) { - nsHttpConnectionInfo *clone = ci->Clone(); - if (!clone) - return NS_ERROR_OUT_OF_MEMORY; - ent = new nsConnectionEntry(clone); - if (!ent) - return NS_ERROR_OUT_OF_MEMORY; - mCT.Put(ci->HashKey(), ent); - } - - // SPDY coalescing of hostnames means we might redirect from this - // connection entry onto the preferred one. - nsConnectionEntry *preferredEntry = GetSpdyPreferredEnt(ent); - if (preferredEntry && (preferredEntry != ent)) { - LOG(("nsHttpConnectionMgr::ProcessNewTransaction trans=%p " - "redirected via coalescing from %s to %s\n", trans, - ent->mConnInfo->Host(), preferredEntry->mConnInfo->Host())); - - ent = preferredEntry; - } - - // If we are doing a force reload then close out any existing conns - // to this host so that changes in DNS, LBs, etc.. are reflected - if (caps & NS_HTTP_CLEAR_KEEPALIVES) - ClosePersistentConnections(ent); - - // Check if the transaction already has a sticky reference to a connection. - // If so, then we can just use it directly by transferring its reference - // to the new connection var instead of calling GetConnection() to search - // for an available one. - - nsAHttpConnection *wrappedConnection = trans->Connection(); - nsHttpConnection *conn; - conn = wrappedConnection ? wrappedConnection->TakeHttpConnection() : nsnull; - - if (conn) { - NS_ASSERTION(caps & NS_HTTP_STICKY_CONNECTION, "unexpected caps"); - - trans->SetConnection(nsnull); - } - else - GetConnection(ent, trans, false, &conn); - - nsresult rv; - if (!conn) { - LOG((" adding transaction to pending queue [trans=%x pending-count=%u]\n", - trans, ent->mPendingQ.Length()+1)); - // put this transaction on the pending queue... - InsertTransactionSorted(ent->mPendingQ, trans); - NS_ADDREF(trans); - - /* there still remains the possibility that the transaction we just - queued could go out right away as a pipelined request on an existing - connection */ - ProcessPipelinePendingQForEntry(ent); - rv = NS_OK; - } - else { - rv = DispatchTransaction(ent, trans, caps, conn); - NS_RELEASE(conn); - } - - return rv; -} - // This function tries to dispatch the pending spdy transactions on // the connection entry sent in as an argument. It will do so on the // active spdy connection either in that same entry or in the @@ -1498,7 +1584,7 @@ nsHttpConnectionMgr::ProcessSpdyPendingQ(nsConnectionEntry *ent) ent->mPendingQ.RemoveElementAt(index); - nsresult rv = DispatchTransaction(ent, trans, trans->Caps(), conn); + nsresult rv = DispatchTransaction(ent, trans, conn); if (NS_FAILED(rv)) { // this cannot happen, but if due to some bug it does then // close the transaction @@ -1563,6 +1649,7 @@ nsHttpConnectionMgr::GetSpdyPreferredConn(nsConnectionEntry *ent) void nsHttpConnectionMgr::OnMsgShutdown(PRInt32, void *) { + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); LOG(("nsHttpConnectionMgr::OnMsgShutdown\n")); mCT.Enumerate(ShutdownPassCB, this); @@ -1594,7 +1681,8 @@ nsHttpConnectionMgr::OnMsgNewTransaction(PRInt32 priority, void *param) void nsHttpConnectionMgr::OnMsgReschedTransaction(PRInt32 priority, void *param) { - LOG(("nsHttpConnectionMgr::OnMsgNewTransaction [trans=%p]\n", param)); + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + LOG(("nsHttpConnectionMgr::OnMsgReschedTransaction [trans=%p]\n", param)); nsHttpTransaction *trans = (nsHttpTransaction *) param; trans->SetPriority(priority); @@ -1616,6 +1704,7 @@ nsHttpConnectionMgr::OnMsgReschedTransaction(PRInt32 priority, void *param) void nsHttpConnectionMgr::OnMsgCancelTransaction(PRInt32 reason, void *param) { + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); LOG(("nsHttpConnectionMgr::OnMsgCancelTransaction [trans=%p]\n", param)); nsHttpTransaction *trans = (nsHttpTransaction *) param; @@ -1647,6 +1736,7 @@ nsHttpConnectionMgr::OnMsgCancelTransaction(PRInt32 reason, void *param) void nsHttpConnectionMgr::OnMsgProcessPendingQ(PRInt32, void *param) { + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); nsHttpConnectionInfo *ci = (nsHttpConnectionInfo *) param; LOG(("nsHttpConnectionMgr::OnMsgProcessPendingQ [ci=%s]\n", ci->HashKey().get())); @@ -1665,6 +1755,7 @@ nsHttpConnectionMgr::OnMsgProcessPendingQ(PRInt32, void *param) void nsHttpConnectionMgr::OnMsgPruneDeadConnections(PRInt32, void *) { + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); LOG(("nsHttpConnectionMgr::OnMsgPruneDeadConnections\n")); // Reset mTimeOfNextWakeUp so that we can find a new shortest value. @@ -1687,6 +1778,7 @@ nsHttpConnectionMgr::OnMsgClosePersistentConnections(PRInt32, void *) void nsHttpConnectionMgr::OnMsgReclaimConnection(PRInt32, void *param) { + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); LOG(("nsHttpConnectionMgr::OnMsgReclaimConnection [conn=%p]\n", param)); nsHttpConnection *conn = (nsHttpConnection *) param; @@ -1726,6 +1818,8 @@ nsHttpConnectionMgr::OnMsgReclaimConnection(PRInt32, void *param) } if (ent->mActiveConns.RemoveElement(conn)) { + if (conn == ent->mYellowConnection) + ent->OnYellowComplete(); nsHttpConnection *temp = conn; NS_RELEASE(temp); mNumActiveConns--; @@ -1761,7 +1855,6 @@ nsHttpConnectionMgr::OnMsgReclaimConnection(PRInt32, void *param) } else { LOG((" connection cannot be reused; closing connection\n")); - // make sure the connection is closed and release our reference. conn->Close(NS_ERROR_ABORT); } } @@ -1798,6 +1891,9 @@ nsHttpConnectionMgr::OnMsgUpdateParam(PRInt32, void *param) case MAX_PIPELINED_REQUESTS: mMaxPipelinedRequests = value; break; + case MAX_OPTIMISTIC_PIPELINED_REQUESTS: + mMaxOptimisticPipelinedRequests = value; + break; default: NS_NOTREACHED("unexpected parameter name"); } @@ -1812,6 +1908,16 @@ nsHttpConnectionMgr::nsConnectionEntry::~nsConnectionEntry() NS_RELEASE(mConnInfo); } +void +nsHttpConnectionMgr::OnMsgProcessFeedback(PRInt32, void *param) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + nsHttpPipelineFeedback *fb = (nsHttpPipelineFeedback *)param; + + PipelineFeedbackInfo(fb->mConnInfo, fb->mInfo, fb->mConn, fb->mData); + delete fb; +} + // Read Timeout Tick handlers void @@ -2080,6 +2186,7 @@ nsHttpConnectionMgr::nsHalfOpenSocket::SetupPrimaryStreams() nsresult rv; + mPrimarySynStarted = mozilla::TimeStamp::Now(); rv = SetupStreams(getter_AddRefs(mSocketTransport), getter_AddRefs(mStreamIn), getter_AddRefs(mStreamOut), @@ -2099,6 +2206,7 @@ nsHttpConnectionMgr::nsHalfOpenSocket::SetupPrimaryStreams() nsresult nsHttpConnectionMgr::nsHalfOpenSocket::SetupBackupStreams() { + mBackupSynStarted = mozilla::TimeStamp::Now(); nsresult rv = SetupStreams(getter_AddRefs(mBackupTransport), getter_AddRefs(mBackupStreamIn), getter_AddRefs(mBackupStreamOut), @@ -2220,10 +2328,13 @@ nsHalfOpenSocket::OnOutputStreamReady(nsIAsyncOutputStream *out) mTransaction->GetSecurityCallbacks(getter_AddRefs(callbacks), getter_AddRefs(callbackTarget)); if (out == mStreamOut) { + mozilla::TimeDuration rtt = + mozilla::TimeStamp::Now() - mPrimarySynStarted; rv = conn->Init(mEnt->mConnInfo, gHttpHandler->ConnMgr()->mMaxRequestDelay, mSocketTransport, mStreamIn, mStreamOut, - callbacks, callbackTarget); + callbacks, callbackTarget, + PR_MillisecondsToInterval(rtt.ToMilliseconds())); // The nsHttpConnection object now owns these streams and sockets mStreamOut = nsnull; @@ -2231,10 +2342,14 @@ nsHalfOpenSocket::OnOutputStreamReady(nsIAsyncOutputStream *out) mSocketTransport = nsnull; } else { + mozilla::TimeDuration rtt = + mozilla::TimeStamp::Now() - mBackupSynStarted; + rv = conn->Init(mEnt->mConnInfo, gHttpHandler->ConnMgr()->mMaxRequestDelay, mBackupTransport, mBackupStreamIn, mBackupStreamOut, - callbacks, callbackTarget); + callbacks, callbackTarget, + PR_MillisecondsToInterval(rtt.ToMilliseconds())); // The nsHttpConnection object now owns these streams and sockets mBackupStreamOut = nsnull; @@ -2256,7 +2371,6 @@ nsHalfOpenSocket::OnOutputStreamReady(nsIAsyncOutputStream *out) NS_RELEASE(temp); gHttpHandler->ConnMgr()->AddActiveConn(conn, mEnt); rv = gHttpHandler->ConnMgr()->DispatchTransaction(mEnt, mTransaction, - mTransaction->Caps(), conn); } else { @@ -2392,6 +2506,255 @@ nsHttpConnectionMgr::nsConnectionHandle::IsProxyConnectInProgress() return mConn->IsProxyConnectInProgress(); } +// nsConnectionEntry + +nsHttpConnectionMgr:: +nsConnectionEntry::nsConnectionEntry(nsHttpConnectionInfo *ci) + : mConnInfo(ci) + , mPipelineState(PS_YELLOW) + , mYellowGoodEvents(0) + , mYellowBadEvents(0) + , mYellowConnection(nsnull) + , mGreenDepth(kPipelineOpen) + , mPipeliningPenalty(0) + , mUsingSpdy(false) + , mTestedSpdy(false) + , mSpdyPreferred(false) +{ + NS_ADDREF(mConnInfo); + if (gHttpHandler->GetPipelineAggressive()) { + mGreenDepth = kPipelineUnlimited; + mPipelineState = PS_GREEN; + } + mInitialGreenDepth = mGreenDepth; + memset(mPipeliningClassPenalty, 0, sizeof(PRInt16) * nsAHttpTransaction::CLASS_MAX); +} + +bool +nsHttpConnectionMgr::nsConnectionEntry::SupportsPipelining() +{ + return mPipelineState != nsHttpConnectionMgr::PS_RED; +} + +nsHttpConnectionMgr::PipeliningState +nsHttpConnectionMgr::nsConnectionEntry::PipelineState() +{ + return mPipelineState; +} + +void +nsHttpConnectionMgr:: +nsConnectionEntry::OnPipelineFeedbackInfo( + nsHttpConnectionMgr::PipelineFeedbackInfoType info, + nsHttpConnection *conn, + PRUint32 data) +{ + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + + if (mPipelineState == PS_YELLOW) { + if (info & kPipelineInfoTypeBad) + mYellowBadEvents++; + else if (info & (kPipelineInfoTypeNeutral | kPipelineInfoTypeGood)) + mYellowGoodEvents++; + } + + if (mPipelineState == PS_GREEN && info == GoodCompletedOK) { + PRInt32 depth = data; + LOG(("Transaction completed at pipeline depty of %d. Host = %s\n", + depth, mConnInfo->Host())); + + if (depth >= 3) + mGreenDepth = kPipelineUnlimited; + } + + nsAHttpTransaction::Classifier classification; + if (conn) + classification = conn->Classification(); + else if (info == BadInsufficientFraming) + classification = (nsAHttpTransaction::Classifier) data; + else + classification = nsAHttpTransaction::CLASS_SOLO; + + if (gHttpHandler->GetPipelineAggressive() && + info & kPipelineInfoTypeBad && + info != BadExplicitClose && + info != RedVersionTooLow && + info != RedBannedServer && + info != RedCorruptedContent && + info != BadInsufficientFraming) { + LOG(("minor negative feedback ignored " + "because of pipeline aggressive mode")); + } + else if (info & kPipelineInfoTypeBad) { + if ((info & kPipelineInfoTypeRed) && (mPipelineState != PS_RED)) { + LOG(("transition to red from %d. Host = %s.\n", + mPipelineState, mConnInfo->Host())); + mPipelineState = PS_RED; + mPipeliningPenalty = 0; + } + + if (mLastCreditTime.IsNull()) + mLastCreditTime = mozilla::TimeStamp::Now(); + + // Red* events impact the host globally via mPipeliningPenalty, while + // Bad* events impact the per class penalty. + + // The individual penalties should be < 16bit-signed-maxint - 25000 + // (approx 7500). Penalties are paid-off either when something promising + // happens (a successful transaction, or promising headers) or when + // time goes by at a rate of 1 penalty point every 16 seconds. + + switch (info) { + case RedVersionTooLow: + mPipeliningPenalty += 1000; + break; + case RedBannedServer: + mPipeliningPenalty += 7000; + break; + case RedCorruptedContent: + mPipeliningPenalty += 7000; + break; + case RedCanceledPipeline: + mPipeliningPenalty += 60; + break; + case BadExplicitClose: + mPipeliningClassPenalty[classification] += 250; + break; + case BadSlowReadMinor: + mPipeliningClassPenalty[classification] += 5; + break; + case BadSlowReadMajor: + mPipeliningClassPenalty[classification] += 25; + break; + case BadInsufficientFraming: + mPipeliningClassPenalty[classification] += 7000; + break; + + default: + NS_ABORT_IF_FALSE(0, "Unknown Bad/Red Pipeline Feedback Event"); + } + + mPipeliningPenalty = PR_MIN(mPipeliningPenalty, 25000); + mPipeliningClassPenalty[classification] = PR_MIN(mPipeliningClassPenalty[classification], 25000); + + LOG(("Assessing red penalty to %s class %d for event %d. " + "Penalty now %d, throttle[%d] = %d\n", mConnInfo->Host(), + classification, info, mPipeliningPenalty, classification, + mPipeliningClassPenalty[classification])); + } + else { + // hand out credits for neutral and good events such as + // "headers look ok" events + + mPipeliningPenalty = PR_MAX(mPipeliningPenalty - 1, 0); + mPipeliningClassPenalty[classification] = PR_MAX(mPipeliningClassPenalty[classification] - 1, 0); + } + + if (mPipelineState == PS_RED && !mPipeliningPenalty) + { + LOG(("transition %s to yellow\n", mConnInfo->Host())); + mPipelineState = PS_YELLOW; + mYellowConnection = nsnull; + } +} + +void +nsHttpConnectionMgr:: +nsConnectionEntry::SetYellowConnection(nsHttpConnection *conn) +{ + NS_ABORT_IF_FALSE(!mYellowConnection && mPipelineState == PS_YELLOW, + "yellow connection already set or state is not yellow"); + mYellowConnection = conn; + mYellowGoodEvents = mYellowBadEvents = 0; +} + +void +nsHttpConnectionMgr::nsConnectionEntry::OnYellowComplete() +{ + if (mPipelineState == PS_YELLOW) { + if (mYellowGoodEvents && !mYellowBadEvents) { + LOG(("transition %s to green\n", mConnInfo->Host())); + mPipelineState = PS_GREEN; + mGreenDepth = mInitialGreenDepth; + } + else { + // The purpose of the yellow state is to witness at least + // one successful pipelined transaction without seeing any + // kind of negative feedback before opening the flood gates. + // If we haven't confirmed that, then transfer back to red. + LOG(("transition %s to red from yellow return\n", + mConnInfo->Host())); + mPipelineState = PS_RED; + } + } + + mYellowConnection = nsnull; +} + +void +nsHttpConnectionMgr::nsConnectionEntry::CreditPenalty() +{ + if (mLastCreditTime.IsNull()) + return; + + // Decrease penalty values by 1 for every 16 seconds + // (i.e 3.7 per minute, or 1000 every 4h20m) + + mozilla::TimeStamp now = mozilla::TimeStamp::Now(); + mozilla::TimeDuration elapsedTime = now - mLastCreditTime; + PRUint32 creditsEarned = + static_cast(elapsedTime.ToSeconds()) >> 4; + + bool failed = false; + if (creditsEarned > 0) { + mPipeliningPenalty = + PR_MAX(PRInt32(mPipeliningPenalty - creditsEarned), 0); + if (mPipeliningPenalty > 0) + failed = true; + + for (PRInt32 i = 0; i < nsAHttpTransaction::CLASS_MAX; ++i) { + mPipeliningClassPenalty[i] = + PR_MAX(PRInt32(mPipeliningClassPenalty[i] - creditsEarned), 0); + failed = failed || (mPipeliningClassPenalty[i] > 0); + } + + // update last credit mark to reflect elapsed time + mLastCreditTime += + mozilla::TimeDuration::FromSeconds(creditsEarned << 4); + } + else { + failed = true; /* just assume this */ + } + + // If we are no longer red then clear the credit counter - you only + // get credits for time spent in the red state + if (!failed) + mLastCreditTime = mozilla::TimeStamp(); /* reset to null timestamp */ + + if (mPipelineState == PS_RED && !mPipeliningPenalty) + { + LOG(("transition %s to yellow based on time credit\n", + mConnInfo->Host())); + mPipelineState = PS_YELLOW; + mYellowConnection = nsnull; + } +} + +PRUint32 +nsHttpConnectionMgr:: +nsConnectionEntry::MaxPipelineDepth(nsAHttpTransaction::Classifier aClass) +{ + // Still subject to configuration limit no matter return value + + if ((mPipelineState == PS_RED) || (mPipeliningClassPenalty[aClass] > 0)) + return 0; + + if (mPipelineState == PS_YELLOW) + return kPipelineRestricted; + + return mGreenDepth; +} + bool nsHttpConnectionMgr::nsConnectionHandle::LastTransactionExpectedNoContent() { diff --git a/netwerk/protocol/http/nsHttpConnectionMgr.h b/netwerk/protocol/http/nsHttpConnectionMgr.h index ac0a98eac63..02232192bde 100644 --- a/netwerk/protocol/http/nsHttpConnectionMgr.h +++ b/netwerk/protocol/http/nsHttpConnectionMgr.h @@ -49,6 +49,7 @@ #include "nsAutoPtr.h" #include "mozilla/ReentrantMonitor.h" #include "nsISocketTransportService.h" +#include "mozilla/TimeStamp.h" #include "nsIObserver.h" #include "nsITimer.h" @@ -72,7 +73,8 @@ public: MAX_PERSISTENT_CONNECTIONS_PER_HOST, MAX_PERSISTENT_CONNECTIONS_PER_PROXY, MAX_REQUEST_DELAY, - MAX_PIPELINED_REQUESTS + MAX_PIPELINED_REQUESTS, + MAX_OPTIMISTIC_PIPELINED_REQUESTS }; //------------------------------------------------------------------------- @@ -87,7 +89,8 @@ public: PRUint16 maxPersistentConnectionsPerHost, PRUint16 maxPersistentConnectionsPerProxy, PRUint16 maxRequestDelay, - PRUint16 maxPipelinedRequests); + PRUint16 maxPipelinedRequests, + PRUint16 maxOptimisticPipelinedRequests); nsresult Shutdown(); //------------------------------------------------------------------------- @@ -138,17 +141,73 @@ public: void ReportSpdyAlternateProtocol(nsHttpConnection *); void RemoveSpdyAlternateProtocol(nsACString &key); + // Pipielining Interfaces and Datatypes + + const static PRUint32 kPipelineInfoTypeMask = 0xffff0000; + const static PRUint32 kPipelineInfoIDMask = ~kPipelineInfoTypeMask; + + const static PRUint32 kPipelineInfoTypeRed = 0x00010000; + const static PRUint32 kPipelineInfoTypeBad = 0x00020000; + const static PRUint32 kPipelineInfoTypeNeutral = 0x00040000; + const static PRUint32 kPipelineInfoTypeGood = 0x00080000; + + enum PipelineFeedbackInfoType + { + // Used when an HTTP response less than 1.1 is received + RedVersionTooLow = kPipelineInfoTypeRed | kPipelineInfoTypeBad | 0x0001, + + // Used when a HTTP Server response header that is on the banned from + // pipelining list is received + RedBannedServer = kPipelineInfoTypeRed | kPipelineInfoTypeBad | 0x0002, + + // Used when a response is terminated early, or when it fails an + // integrity check such as assoc-req or md5 + RedCorruptedContent = kPipelineInfoTypeRed | kPipelineInfoTypeBad | 0x0004, + + // Used when a pipeline is only partly satisfied - for instance if the + // server closed the connection after responding to the first + // request but left some requests unprocessed. + RedCanceledPipeline = kPipelineInfoTypeRed | kPipelineInfoTypeBad | 0x0005, + + // Used when a connection that we expected to stay persistently open + // was closed by the server. Not used when simply timed out. + BadExplicitClose = kPipelineInfoTypeBad | 0x0003, + + // Used when there is a gap of around 400 - 1200ms in between data being + // read from the server + BadSlowReadMinor = kPipelineInfoTypeBad | 0x0006, + + // Used when there is a gap of > 1200ms in between data being + // read from the server + BadSlowReadMajor = kPipelineInfoTypeBad | 0x0007, + + // Used when a response is received that is not framed with either chunked + // encoding or a complete content length. + BadInsufficientFraming = kPipelineInfoTypeBad | 0x0008, + + // Used when a response is received that has headers that appear to support + // pipelining. + NeutralExpectedOK = kPipelineInfoTypeNeutral | 0x0009, + + // Used when a response is received successfully to a pipelined request. + GoodCompletedOK = kPipelineInfoTypeGood | 0x000A + }; + + // called to provide information relevant to the pipelining manager + // may be called from any thread + void PipelineFeedbackInfo(nsHttpConnectionInfo *, + PipelineFeedbackInfoType info, + nsHttpConnection *, + PRUint32); + //------------------------------------------------------------------------- // NOTE: functions below may be called only on the socket thread. //------------------------------------------------------------------------- - // removes the next transactions for the specified connection from the - // pending transaction queue. - void AddTransactionToPipeline(nsHttpPipeline *); - // called to force the transaction queue to be processed once more, giving // preference to the specified connection. nsresult ProcessPendingQ(nsHttpConnectionInfo *); + bool ProcessPendingQForEntry(nsHttpConnectionInfo *); // This is used to force an idle connection to be closed and removed from // the idle connection list. It is called when the idle connection detects @@ -161,29 +220,40 @@ public: void ReportSpdyConnection(nsHttpConnection *, bool usingSpdy); - // Similar to ProcessPendingQ, but only considers adding transactions to - // existing connections - bool ProcessPipelinePendingQForCI(nsHttpConnectionInfo *); + bool SupportsPipelining(nsHttpConnectionInfo *); + private: virtual ~nsHttpConnectionMgr(); - class nsHalfOpenSocket; + + enum PipeliningState { + // Host has proven itself pipeline capable through past experience and + // large pipeline depths are allowed on multiple connections. + PS_GREEN, + + // Not enough information is available yet with this host to be certain + // of pipeline capability. Small pipelines on a single connection are + // allowed in order to decide whether or not to proceed to green. + PS_YELLOW, + + // One or more bad events has happened that indicate that pipelining + // to this host (or a particular type of transaction with this host) + // is a bad idea. Pipelining is not currently allowed, but time and + // other positive experiences will eventually allow it to try again. + PS_RED + }; + class nsHalfOpenSocket; + // nsConnectionEntry // // mCT maps connection info hash key to nsConnectionEntry object, which // contains list of active and idle connections as well as the list of // pending transactions. // - struct nsConnectionEntry + class nsConnectionEntry { - nsConnectionEntry(nsHttpConnectionInfo *ci) - : mConnInfo(ci), - mUsingSpdy(false), - mTestedSpdy(false), - mSpdyPreferred(false) - { - NS_ADDREF(mConnInfo); - } + public: + nsConnectionEntry(nsHttpConnectionInfo *ci); ~nsConnectionEntry(); nsHttpConnectionInfo *mConnInfo; @@ -192,6 +262,54 @@ private: nsTArray mIdleConns; // idle persistent connections nsTArray mHalfOpens; + // Pipeline depths for various states + const static PRUint32 kPipelineUnlimited = 1024; // fully open - extended green + const static PRUint32 kPipelineOpen = 6; // 6 on each conn - normal green + const static PRUint32 kPipelineRestricted = 2; // 2 on just 1 conn in yellow + + nsHttpConnectionMgr::PipeliningState PipelineState(); + void OnPipelineFeedbackInfo( + nsHttpConnectionMgr::PipelineFeedbackInfoType info, + nsHttpConnection *, PRUint32); + bool SupportsPipelining(); + PRUint32 MaxPipelineDepth(nsAHttpTransaction::Classifier classification); + void CreditPenalty(); + + nsHttpConnectionMgr::PipeliningState mPipelineState; + + void SetYellowConnection(nsHttpConnection *); + void OnYellowComplete(); + PRUint32 mYellowGoodEvents; + PRUint32 mYellowBadEvents; + nsHttpConnection *mYellowConnection; + + // initialGreenDepth is the max depth of a pipeline when you first + // transition to green. Normally this is kPipelineOpen, but it can + // be kPipelineUnlimited in aggressive mode. + PRUint32 mInitialGreenDepth; + + // greenDepth is the current max allowed depth of a pipeline when + // in the green state. Normally this starts as kPipelineOpen and + // grows to kPipelineUnlimited after a pipeline of depth 3 has been + // successfully transacted. + PRUint32 mGreenDepth; + + // pipeliningPenalty is the current amount of penalty points this host + // entry has earned for participating in events that are not conducive + // to good pipelines - such as head of line blocking, canceled pipelines, + // etc.. penalties are paid back either through elapsed time or simply + // healthy transactions. Having penalty points means that this host is + // not currently eligible for pipelines. + PRInt16 mPipeliningPenalty; + + // some penalty points only apply to particular classifications of + // transactions - this allows a server that perhaps has head of line + // blocking problems on CGI queries to still serve JS pipelined. + PRInt16 mPipeliningClassPenalty[nsAHttpTransaction::CLASS_MAX]; + + // for calculating penalty repair credits + mozilla::TimeStamp mLastCreditTime; + // Spdy sometimes resolves the address in the socket manager in order // to re-coalesce sharded HTTP hosts. The dotted decimal address is // combined with the Anonymous flag from the connection information @@ -276,6 +394,9 @@ private: nsCOMPtr mStreamOut; nsCOMPtr mStreamIn; + mozilla::TimeStamp mPrimarySynStarted; + mozilla::TimeStamp mBackupSynStarted; + // for syn retry nsCOMPtr mSynTimer; nsCOMPtr mBackupTransport; @@ -300,7 +421,7 @@ private: PRUint16 mMaxPersistConnsPerProxy; PRUint16 mMaxRequestDelay; // in seconds PRUint16 mMaxPipelinedRequests; - + PRUint16 mMaxOptimisticPipelinedRequests; bool mIsShuttingDown; //------------------------------------------------------------------------- @@ -314,13 +435,18 @@ private: static PLDHashOperator PurgeExcessIdleConnectionsCB(const nsACString &, nsAutoPtr &, void *); static PLDHashOperator ClosePersistentConnectionsCB(const nsACString &, nsAutoPtr &, void *); bool ProcessPendingQForEntry(nsConnectionEntry *); + bool IsUnderPressure(nsConnectionEntry *ent, + nsHttpTransaction::Classifier classification); bool AtActiveConnectionLimit(nsConnectionEntry *, PRUint8 caps); - bool ProcessPipelinePendingQForEntry(nsConnectionEntry *); - void GetConnection(nsConnectionEntry *, nsHttpTransaction *, - bool, nsHttpConnection **); - nsresult DispatchTransaction(nsConnectionEntry *, nsHttpTransaction *, - PRUint8 caps, nsHttpConnection *); - bool BuildPipeline(nsConnectionEntry *, nsAHttpTransaction *, nsHttpPipeline **); + nsresult TryDispatchTransaction(nsConnectionEntry *ent, + bool onlyReusedConnection, + nsHttpTransaction *trans); + nsresult DispatchTransaction(nsConnectionEntry *, + nsHttpTransaction *, + nsHttpConnection *); + nsresult BuildPipeline(nsConnectionEntry *, + nsAHttpTransaction *, + nsHttpPipeline **); nsresult ProcessNewTransaction(nsHttpTransaction *); nsresult EnsureSocketThreadTargetIfOnline(); void ClosePersistentConnections(nsConnectionEntry *ent); @@ -329,6 +455,13 @@ private: void StartedConnect(); void RecvdConnect(); + bool MakeNewConnection(nsConnectionEntry *ent, + nsHttpTransaction *trans); + bool AddToShortestPipeline(nsConnectionEntry *ent, + nsHttpTransaction *trans, + nsHttpTransaction::Classifier classification, + PRUint16 depthLimit); + // Manage the preferred spdy connection entry for this address nsConnectionEntry *GetSpdyPreferredEnt(nsConnectionEntry *aOriginalEntry); void RemoveSpdyPreferredEnt(nsACString &aDottedDecimal); @@ -401,6 +534,7 @@ private: void OnMsgReclaimConnection (PRInt32, void *); void OnMsgUpdateParam (PRInt32, void *); void OnMsgClosePersistentConnections (PRInt32, void *); + void OnMsgProcessFeedback (PRInt32, void *); // Total number of active connections in all of the ConnectionEntry objects // that are accessed from mCT connection table. diff --git a/netwerk/protocol/http/nsHttpHandler.cpp b/netwerk/protocol/http/nsHttpHandler.cpp index cd569859f74..99db873eca3 100644 --- a/netwerk/protocol/http/nsHttpHandler.cpp +++ b/netwerk/protocol/http/nsHttpHandler.cpp @@ -185,7 +185,9 @@ nsHttpHandler::nsHttpHandler() , mMaxConnectionsPerServer(8) , mMaxPersistentConnectionsPerServer(2) , mMaxPersistentConnectionsPerProxy(4) - , mMaxPipelinedRequests(2) + , mMaxPipelinedRequests(32) + , mMaxOptimisticPipelinedRequests(4) + , mPipelineAggressive(false) , mRedirectionLimit(10) , mPhishyUserPassLength(1) , mQoSBits(0x00) @@ -364,7 +366,8 @@ nsHttpHandler::InitConnectionMgr() mMaxPersistentConnectionsPerServer, mMaxPersistentConnectionsPerProxy, mMaxRequestDelay, - mMaxPipelinedRequests); + mMaxPipelinedRequests, + mMaxOptimisticPipelinedRequests); return rv; } @@ -1016,13 +1019,31 @@ nsHttpHandler::PrefsChanged(nsIPrefBranch *prefs, const char *pref) if (PREF_CHANGED(HTTP_PREF("pipelining.maxrequests"))) { rv = prefs->GetIntPref(HTTP_PREF("pipelining.maxrequests"), &val); if (NS_SUCCEEDED(rv)) { - mMaxPipelinedRequests = clamped(val, 1, NS_HTTP_MAX_PIPELINED_REQUESTS); + mMaxPipelinedRequests = clamped(val, 1, 0xffff); if (mConnMgr) mConnMgr->UpdateParam(nsHttpConnectionMgr::MAX_PIPELINED_REQUESTS, mMaxPipelinedRequests); } } + if (PREF_CHANGED(HTTP_PREF("pipelining.max-optimistic-requests"))) { + rv = prefs-> + GetIntPref(HTTP_PREF("pipelining.max-optimistic-requests"), &val); + if (NS_SUCCEEDED(rv)) { + mMaxOptimisticPipelinedRequests = clamped(val, 1, 0xffff); + if (mConnMgr) + mConnMgr->UpdateParam + (nsHttpConnectionMgr::MAX_OPTIMISTIC_PIPELINED_REQUESTS, + mMaxOptimisticPipelinedRequests); + } + } + + if (PREF_CHANGED(HTTP_PREF("pipelining.aggressive"))) { + rv = prefs->GetBoolPref(HTTP_PREF("pipelining.aggressive"), &cVar); + if (NS_SUCCEEDED(rv)) + mPipelineAggressive = cVar; + } + if (PREF_CHANGED(HTTP_PREF("pipelining.ssl"))) { rv = prefs->GetBoolPref(HTTP_PREF("pipelining.ssl"), &cVar); if (NS_SUCCEEDED(rv)) diff --git a/netwerk/protocol/http/nsHttpHandler.h b/netwerk/protocol/http/nsHttpHandler.h index 8c18d78f9ea..f0213cb4b69 100644 --- a/netwerk/protocol/http/nsHttpHandler.h +++ b/netwerk/protocol/http/nsHttpHandler.h @@ -232,6 +232,8 @@ public: static nsresult GenerateHostPort(const nsCString& host, PRInt32 port, nsCString& hostLine); + bool GetPipelineAggressive() { return mPipelineAggressive; } + private: // @@ -288,7 +290,9 @@ private: PRUint8 mMaxConnectionsPerServer; PRUint8 mMaxPersistentConnectionsPerServer; PRUint8 mMaxPersistentConnectionsPerProxy; - PRUint8 mMaxPipelinedRequests; + PRUint16 mMaxPipelinedRequests; + PRUint16 mMaxOptimisticPipelinedRequests; + bool mPipelineAggressive; PRUint8 mRedirectionLimit; diff --git a/netwerk/protocol/http/nsHttpPipeline.cpp b/netwerk/protocol/http/nsHttpPipeline.cpp index 245675e11e4..bf6fc2f891c 100644 --- a/netwerk/protocol/http/nsHttpPipeline.cpp +++ b/netwerk/protocol/http/nsHttpPipeline.cpp @@ -92,9 +92,8 @@ private: // nsHttpPipeline //----------------------------------------------------------------------------- -nsHttpPipeline::nsHttpPipeline(PRUint16 maxPipelineDepth) - : mMaxPipelineDepth(maxPipelineDepth) - , mConnection(nsnull) +nsHttpPipeline::nsHttpPipeline() + : mConnection(nsnull) , mStatus(NS_OK) , mRequestIsPartial(false) , mResponseIsPartial(false) @@ -131,7 +130,7 @@ nsHttpPipeline::AddTransaction(nsAHttpTransaction *trans) NS_ADDREF(trans); mRequestQ.AppendElement(trans); - PRInt32 qlen = mRequestQ.Length(); + PRUint32 qlen = PipelineDepth(); if (qlen != 1) { trans->SetPipelinePosition(qlen); @@ -142,34 +141,20 @@ nsHttpPipeline::AddTransaction(nsAHttpTransaction *trans) trans->SetPipelinePosition(0); } - if (mConnection && !mClosed) { - trans->SetConnection(this); - if (qlen == 1) - mConnection->ResumeSend(); - } + // trans->SetConnection() needs to be updated to point back at + // the pipeline object. + trans->SetConnection(this); + + if (mConnection && !mClosed && mRequestQ.Length() == 1) + mConnection->ResumeSend(); return NS_OK; } -PRUint16 -nsHttpPipeline::PipelineDepthAvailable() +PRUint32 +nsHttpPipeline::PipelineDepth() { - PRUint16 currentTransactions = mRequestQ.Length() + mResponseQ.Length(); - - // Check to see if there are too many transactions currently in use. - if (currentTransactions >= mMaxPipelineDepth) - return 0; - - // Check to see if this connection is being used by a non-pipelineable - // transaction already. - nsAHttpTransaction *trans = Request(0); - if (!trans) - trans = Response(0); - if (trans && !(trans->Caps() & NS_HTTP_ALLOW_PIPELINING)) - return 0; - - // There is still some room available. - return mMaxPipelineDepth - currentTransactions; + return mRequestQ.Length() + mResponseQ.Length(); } nsresult @@ -223,7 +208,7 @@ nsHttpPipeline::OnHeadersAvailable(nsAHttpTransaction *trans, NS_ABORT_IF_FALSE(ci, "no connection info"); - bool pipeliningBefore = ci->SupportsPipelining(); + bool pipeliningBefore = gHttpHandler->ConnMgr()->SupportsPipelining(ci); // trans has now received its response headers; forward to the real connection nsresult rv = mConnection->OnHeadersAvailable(trans, @@ -231,10 +216,10 @@ nsHttpPipeline::OnHeadersAvailable(nsAHttpTransaction *trans, responseHead, reset); - if (!pipeliningBefore && ci->SupportsPipelining()) + if (!pipeliningBefore && gHttpHandler->ConnMgr()->SupportsPipelining(ci)) // The received headers have expanded the eligible // pipeline depth for this connection - gHttpHandler->ConnMgr()->ProcessPipelinePendingQForCI(ci); + gHttpHandler->ConnMgr()->ProcessPendingQForEntry(ci); return rv; } @@ -261,7 +246,7 @@ nsHttpPipeline::CloseTransaction(nsAHttpTransaction *trans, nsresult reason) LOG(("nsHttpPipeline::CloseTransaction [this=%x trans=%x reason=%x]\n", this, trans, reason)); - NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); NS_ASSERTION(NS_FAILED(reason), "expecting failure code"); // the specified transaction is to be closed with the given "reason" @@ -303,7 +288,11 @@ nsHttpPipeline::CloseTransaction(nsAHttpTransaction *trans, nsresult reason) void nsHttpPipeline::GetConnectionInfo(nsHttpConnectionInfo **result) { - NS_ASSERTION(mConnection, "no connection"); + if (!mConnection) { + *result = nsnull; + return; + } + mConnection->GetConnectionInfo(result); } @@ -341,7 +330,7 @@ nsHttpPipeline::PushBack(const char *data, PRUint32 length) { LOG(("nsHttpPipeline::PushBack [this=%x len=%u]\n", this, length)); - NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread"); + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread"); NS_ASSERTION(mPushBackLen == 0, "push back buffer already has data!"); // If we have no chance for a pipeline (e.g. due to an Upgrade) @@ -471,7 +460,7 @@ nsHttpPipeline::TakeSubTransactions( } //----------------------------------------------------------------------------- -// nsHttpPipeline::nsAHttpConnection +// nsHttpPipeline::nsAHttpTransaction //----------------------------------------------------------------------------- void @@ -483,10 +472,6 @@ nsHttpPipeline::SetConnection(nsAHttpConnection *conn) NS_ASSERTION(!mConnection, "already have a connection"); NS_IF_ADDREF(mConnection = conn); - - PRInt32 i, count = mRequestQ.Length(); - for (i=0; iSetConnection(this); } nsAHttpConnection * @@ -759,7 +744,10 @@ nsHttpPipeline::WriteSegments(nsAHttpSegmentWriter *writer, // ask the connection manager to add additional transactions // to our pipeline. - gHttpHandler->ConnMgr()->AddTransactionToPipeline(this); + nsRefPtr ci; + GetConnectionInfo(getter_AddRefs(ci)); + if (ci) + gHttpHandler->ConnMgr()->ProcessPendingQForEntry(ci); } else mResponseIsPartial = true; @@ -819,6 +807,13 @@ nsHttpPipeline::Close(nsresult reason) mRequestQ.Clear(); trans = Response(0); + + nsRefPtr ci; + GetConnectionInfo(getter_AddRefs(ci)); + if (ci && (trans || count)) + gHttpHandler->ConnMgr()->PipelineFeedbackInfo( + ci, nsHttpConnectionMgr::RedCanceledPipeline, nsnull, 0); + if (trans) { // The current transaction can be restarted via reset // if the response has not started to arrive and the reason @@ -879,6 +874,13 @@ nsHttpPipeline::FillSendBuf() while ((trans = Request(0)) != nsnull) { avail = trans->Available(); if (avail) { + // if there is already a response in the responseq then this + // new data comprises a pipeline. Update the transaction in the + // response queue to reflect that if necessary. We are now sending + // out a request while we haven't received all responses. + nsAHttpTransaction *response = Response(0); + if (response && !response->PipelinePosition()) + response->SetPipelinePosition(1); rv = trans->ReadSegments(this, avail, &n); if (NS_FAILED(rv)) return rv; @@ -909,6 +911,10 @@ nsHttpPipeline::FillSendBuf() NS_NET_STATUS_WAITING_FOR, mSendingToProgress); } + + // It would be good to re-enable data read handlers via ResumeRecv() + // except the read handler code can be synchronously dispatched on + // the stack. } else mRequestIsPartial = true; diff --git a/netwerk/protocol/http/nsHttpPipeline.h b/netwerk/protocol/http/nsHttpPipeline.h index 831ce6548ce..dbc5b2b3fbc 100644 --- a/netwerk/protocol/http/nsHttpPipeline.h +++ b/netwerk/protocol/http/nsHttpPipeline.h @@ -57,7 +57,7 @@ public: NS_DECL_NSAHTTPTRANSACTION NS_DECL_NSAHTTPSEGMENTREADER - nsHttpPipeline(PRUint16 maxPipelineDepth); + nsHttpPipeline(); virtual ~nsHttpPipeline(); private: @@ -82,7 +82,6 @@ private: return mResponseQ[i]; } - PRUint16 mMaxPipelineDepth; nsAHttpConnection *mConnection; nsTArray mRequestQ; // array of transactions nsTArray mResponseQ; // array of transactions diff --git a/netwerk/protocol/http/nsHttpTransaction.cpp b/netwerk/protocol/http/nsHttpTransaction.cpp index 22a03ebaf39..3bb5e8ef359 100644 --- a/netwerk/protocol/http/nsHttpTransaction.cpp +++ b/netwerk/protocol/http/nsHttpTransaction.cpp @@ -119,6 +119,7 @@ nsHttpTransaction::nsHttpTransaction() , mPriority(0) , mRestartCount(0) , mCaps(0) + , mClassification(CLASS_GENERAL) , mPipelinePosition(0) , mClosed(false) , mConnected(false) @@ -150,6 +151,40 @@ nsHttpTransaction::~nsHttpTransaction() delete mChunkedDecoder; } +nsHttpTransaction::Classifier +nsHttpTransaction::Classify() +{ + if (!(mCaps & NS_HTTP_ALLOW_PIPELINING)) + return (mClassification = CLASS_SOLO); + + if (mRequestHead->PeekHeader(nsHttp::If_Modified_Since) || + mRequestHead->PeekHeader(nsHttp::If_None_Match)) + return (mClassification = CLASS_REVALIDATION); + + const char *accept = mRequestHead->PeekHeader(nsHttp::Accept); + if (accept && !PL_strncmp(accept, "image/", 6)) + return (mClassification = CLASS_IMAGE); + + if (accept && !PL_strncmp(accept, "text/css", 8)) + return (mClassification = CLASS_SCRIPT); + + mClassification = CLASS_GENERAL; + + PRInt32 queryPos = mRequestHead->RequestURI().FindChar('?'); + if (queryPos == kNotFound) { + if (StringEndsWith(mRequestHead->RequestURI(), + NS_LITERAL_CSTRING(".js"))) + mClassification = CLASS_SCRIPT; + } + else if (queryPos >= 3 && + Substring(mRequestHead->RequestURI(), queryPos - 3, 3). + EqualsLiteral(".js")) { + mClassification = CLASS_SCRIPT; + } + + return mClassification; +} + nsresult nsHttpTransaction::Init(PRUint8 caps, nsHttpConnectionInfo *cinfo, @@ -301,6 +336,8 @@ nsHttpTransaction::Init(PRUint8 caps, nsIOService::gDefaultSegmentCount); if (NS_FAILED(rv)) return rv; + Classify(); + NS_ADDREF(*responseBody = mPipeIn); return NS_OK; } @@ -661,9 +698,15 @@ nsHttpTransaction::Close(nsresult reason) // mReceivedData == FALSE. (see bug 203057 for more info.) // if (reason == NS_ERROR_NET_RESET || reason == NS_OK) { - if (!mReceivedData && (!mSentData || connReused)) { + if (!mReceivedData && (!mSentData || connReused || mPipelinePosition)) { // if restarting fails, then we must proceed to close the pipe, // which will notify the channel that the transaction failed. + + if (mPipelinePosition) { + gHttpHandler->ConnMgr()->PipelineFeedbackInfo( + mConnInfo, nsHttpConnectionMgr::RedCanceledPipeline, + nsnull, 0); + } if (NS_SUCCEEDED(Restart())) return; } @@ -671,6 +714,21 @@ nsHttpTransaction::Close(nsresult reason) bool relConn = true; if (NS_SUCCEEDED(reason)) { + if (!mResponseIsComplete) { + // The response has not been delimited with a high-confidence + // algorithm like Content-Length or Chunked Encoding. We + // need to use a strong framing mechanism to pipeline. + gHttpHandler->ConnMgr()->PipelineFeedbackInfo( + mConnInfo, nsHttpConnectionMgr::BadInsufficientFraming, + nsnull, mClassification); + } + else if (mPipelinePosition) { + // report this success as feedback + gHttpHandler->ConnMgr()->PipelineFeedbackInfo( + mConnInfo, nsHttpConnectionMgr::GoodCompletedOK, + nsnull, mPipelinePosition); + } + // the server has not sent the final \r\n terminating the header // section, and there may still be a header line unparsed. let's make // sure we parse the remaining header line, and then hopefully, the @@ -717,10 +775,10 @@ nsHttpTransaction::AddTransaction(nsAHttpTransaction *trans) return NS_ERROR_NOT_IMPLEMENTED; } -PRUint16 -nsHttpTransaction::PipelineDepthAvailable() +PRUint32 +nsHttpTransaction::PipelineDepth() { - return 0; + return IsDone() ? 0 : 1; } nsresult @@ -871,6 +929,9 @@ nsHttpTransaction::ParseLineSegment(char *segment, PRUint32 len) nsresult rv = ParseLine(mLineBuf.BeginWriting()); mLineBuf.Truncate(); if (NS_FAILED(rv)) { + gHttpHandler->ConnMgr()->PipelineFeedbackInfo( + mConnInfo, nsHttpConnectionMgr::RedCorruptedContent, + nsnull, 0); return rv; } } @@ -1065,6 +1126,10 @@ nsHttpTransaction::HandleContentStart() break; } mConnection->SetLastTransactionExpectedNoContent(mNoContent); + if (mInvalidResponseBytesRead) + gHttpHandler->ConnMgr()->PipelineFeedbackInfo( + mConnInfo, nsHttpConnectionMgr::BadInsufficientFraming, + nsnull, mClassification); if (mNoContent) mContentLength = 0; diff --git a/netwerk/protocol/http/nsHttpTransaction.h b/netwerk/protocol/http/nsHttpTransaction.h index f071b7dc2a3..11a8c69a60e 100644 --- a/netwerk/protocol/http/nsHttpTransaction.h +++ b/netwerk/protocol/http/nsHttpTransaction.h @@ -134,6 +134,7 @@ public: PRInt32 Priority() { return mPriority; } const TimingStruct& Timings() const { return mTimings; } + enum Classifier Classification() { return mClassification; } private: nsresult Restart(); @@ -147,6 +148,8 @@ private: nsresult ProcessData(char *, PRUint32, PRUint32 *); void DeleteSelfOnConsumerThread(); + Classifier Classify(); + static NS_METHOD ReadRequestSegment(nsIInputStream *, void *, const char *, PRUint32, PRUint32, PRUint32 *); static NS_METHOD WritePipeSegment(nsIOutputStream *, void *, char *, @@ -199,6 +202,7 @@ private: PRUint16 mRestartCount; // the number of times this transaction has been restarted PRUint8 mCaps; + enum Classifier mClassification; PRInt32 mPipelinePosition; // state flags, all logically boolean, but not packed together into a