From 0ad27d7443570051d51428277ab6b3281c23f452 Mon Sep 17 00:00:00 2001 From: Mike Perry Date: Mon, 14 Oct 2013 20:30:06 -0700 Subject: [PATCH] Bug 5282: Randomize HTTP request order and pipeline depth. This is an experimental defense against Website Traffic Fingerprinting: http://lorre.uni.lu/~andriy/papers/acmccs-wpes11-fingerprinting.pdf See: https://blog.torproject.org/blog/experimental-defense-website-traffic-fingerprinting This patch is different from the approach described in that post, as well as the 10.x ESR patch, as the pipelining code has changed significantly between the time of writing of that post and Firefox 17. The main control nob for this patch is now the about:config pref network.http.pipelining.max-optimistic-requests. The value of that pref represents the minimum number of pipelined requests we will attempt to batch together. The total outstanding pipeline size is randomized between that value and network.http.pipelining.maxrequests on a per-host basis. Care must be taken when evaluating this defense, as pipeline behavior is extremely sensitive to browser performance. In fact, a debug build alone is enough to significantly impair request availability to the pipeline (due slower document parsing and rendering). For this reason, we provide two separate debug log defines. For most evaluation circumstances, you want to define only WTF_TEST in an optimized build to only log request order, combination behavior, and cases where the pipeline is forcibly disabled. This patch may also have some minor impact on SPDY request order, but the SPDY implementation has not been altered directly. It has several stream queues that may also benefit from reordering and batching, as well as a more compact request representation that will allow more requests to be packed inside Tor cells. If you have interest in evaluating SPDY in a study of Website Traffic Fingerprinting, please contact me. --- netwerk/protocol/http/nsHttpConnectionMgr.cpp | 377 ++++++++++++------ netwerk/protocol/http/nsHttpConnectionMgr.h | 15 +- netwerk/protocol/http/nsHttpHandler.h | 2 + netwerk/protocol/http/nsHttpPipeline.cpp | 69 +++- netwerk/protocol/http/nsHttpPipeline.h | 4 + 5 files changed, 347 insertions(+), 120 deletions(-) diff --git a/netwerk/protocol/http/nsHttpConnectionMgr.cpp b/netwerk/protocol/http/nsHttpConnectionMgr.cpp index ba9db2c930131..72e63eed7c5d6 100644 --- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp +++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp @@ -51,25 +51,25 @@ InsertTransactionSorted(nsTArray > &pendingQ, nsHttpTr // insert into queue with smallest valued number first. search in reverse // order under the assumption that many of the existing transactions will // have the same priority (usually 0). + uint32_t len = pendingQ.Length(); - for (int32_t i = pendingQ.Length() - 1; i >= 0; --i) { - nsHttpTransaction *t = pendingQ[i]; - if (trans->Priority() >= t->Priority()) { - if (ChaosMode::isActive(ChaosFeature::NetworkScheduling)) { - int32_t samePriorityCount; - for (samePriorityCount = 0; i - samePriorityCount >= 0; ++samePriorityCount) { - if (pendingQ[i - samePriorityCount]->Priority() != trans->Priority()) { - break; - } - } - // skip over 0...all of the elements with the same priority. - i -= ChaosMode::randomUint32LessThan(samePriorityCount + 1); - } - pendingQ.InsertElementAt(i+1, trans); - return; - } + if (pendingQ.IsEmpty()) { + pendingQ.InsertElementAt(0, trans); + return; } pendingQ.InsertElementAt(0, trans); + + // FIXME: Refactor into standalone helper (for nsHttpPipeline) + // Or at least simplify this function if this shuffle ends up + // being an improvement. + uint32_t i = 0; + for (i=0; i < len; ++i) { + uint32_t ridx = rand() % len; + + nsHttpTransaction *tmp = pendingQ[i]; + pendingQ[i] = pendingQ[ridx]; + pendingQ[ridx] = tmp; + } } //----------------------------------------------------------------------------- @@ -878,6 +878,10 @@ nsHttpConnectionMgr::ProcessPendingQForEntry(nsConnectionEntry *ent, bool consid nsHttpTransaction *trans; nsresult rv; bool dispatchedSuccessfully = false; + int dispatchCount = 0; +#ifdef WTF_DEBUG + uint32_t total = ent->mPendingQ.Length(); +#endif // if !considerAll iterate the pending list until one is dispatched successfully. // Keep iterating afterwards only until a transaction fails to dispatch. @@ -885,16 +889,19 @@ nsHttpConnectionMgr::ProcessPendingQForEntry(nsConnectionEntry *ent, bool consid for (uint32_t i = 0; i < ent->mPendingQ.Length(); ) { trans = ent->mPendingQ[i]; - // When this transaction has already established a half-open + // When this entry 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. + // transaction. bool alreadyHalfOpen = false; - for (int32_t j = 0; j < ((int32_t) ent->mHalfOpens.Length()); ++j) { - if (ent->mHalfOpens[j]->Transaction() == trans) { - alreadyHalfOpen = true; - break; + if (ent->SupportsPipelining()) { + alreadyHalfOpen = (ent->UnconnectedHalfOpens() > 0); + } else { + for (int32_t j = 0; j < ((int32_t) ent->mHalfOpens.Length()); ++j) { + if (ent->mHalfOpens[j]->Transaction() == trans) { + alreadyHalfOpen = true; + break; + } } } @@ -911,17 +918,29 @@ nsHttpConnectionMgr::ProcessPendingQForEntry(nsConnectionEntry *ent, bool consid if (ent->mPendingQ.RemoveElement(trans)) { // trans is now potentially destroyed dispatchedSuccessfully = true; + dispatchCount++; continue; // dont ++i as we just made the array shorter } LOG((" transaction not found in pending queue\n")); } - if (dispatchedSuccessfully && !considerAll) - break; + // We want to keep walking the dispatch table to ensure requests + // get combined properly. + //if (dispatchedSuccessfully && !considerAll) + // break; ++i; } + +#ifdef WTF_DEBUG + if (dispatchedSuccessfully) { + fprintf(stderr, "WTF-queue: Dispatched %d/%d pending transactions for %s\n", + dispatchCount, total, ent->mConnInfo->Origin()); + return true; + } +#endif + return dispatchedSuccessfully; } @@ -1290,6 +1309,11 @@ nsHttpConnectionMgr::MakeNewConnection(nsConnectionEntry *ent, if (AtActiveConnectionLimit(ent, trans->Caps())) return NS_ERROR_NOT_AVAILABLE; +#ifdef WTF_DEBUG + fprintf(stderr, "WTF: MakeNewConnection() is creating a transport (pipelines %d) for host %s\n", + ent->SupportsPipelining(), ent->mConnInfo->Origin()); +#endif + nsresult rv = CreateTransport(ent, trans, trans->Caps(), false, false, true); if (NS_FAILED(rv)) { /* hard failure */ @@ -1306,7 +1330,7 @@ nsHttpConnectionMgr::MakeNewConnection(nsConnectionEntry *ent, } bool -nsHttpConnectionMgr::AddToShortestPipeline(nsConnectionEntry *ent, +nsHttpConnectionMgr::AddToBestPipeline(nsConnectionEntry *ent, nsHttpTransaction *trans, nsHttpTransaction::Classifier classification, uint16_t depthLimit) @@ -1343,48 +1367,117 @@ nsHttpConnectionMgr::AddToShortestPipeline(nsConnectionEntry *ent, if (maxdepth < 2) return false; - nsAHttpTransaction *activeTrans; + // Find out how many requests of this class we have + uint32_t sameClass = 0; + uint32_t allClasses = ent->mPendingQ.Length(); + for (uint32_t i = 0; i < allClasses; ++i) { + if (trans != ent->mPendingQ[i] && + classification == ent->mPendingQ[i]->Classification()) { + sameClass++; + } + } + nsAHttpTransaction *activeTrans; + nsHttpPipeline *pipeline; nsHttpConnection *bestConn = nullptr; uint32_t activeCount = ent->mActiveConns.Length(); - uint32_t bestConnLength = 0; - uint32_t connLength; + uint32_t pipelineDepth; + uint32_t requestLen; + uint32_t totalDepth = 0; + + // Now, try to find the best pipeline + nsTArray validConns; + nsTArray betterConns; + nsTArray bestConns; + uint32_t numPipelines = 0; for (uint32_t i = 0; i < activeCount; ++i) { nsHttpConnection *conn = ent->mActiveConns[i]; - if (!conn->SupportsPipelining()) - continue; - if (conn->Classification() != classification) + if (!conn->SupportsPipelining()) continue; activeTrans = conn->Transaction(); + if (!activeTrans || activeTrans->IsDone() || NS_FAILED(activeTrans->Status())) continue; - connLength = activeTrans->PipelineDepth(); + pipeline = activeTrans->QueryPipeline(); + if (!pipeline) + continue; + + numPipelines++; + + pipelineDepth = activeTrans->PipelineDepth(); + requestLen = pipeline->RequestDepth(); - if (maxdepth <= connLength) + totalDepth += pipelineDepth; + + // If we're within striking distance of our pipeline + // packaging goal, give a little slack on the depth + // limit to allow us to try to get there. Don't give + // too much slack, though, or we'll tend to have + // request packages of the same size when we have + // many content elements appear at once. + if (maxdepth + + PR_MIN(mMaxOptimisticPipelinedRequests, + requestLen + allClasses) + <= pipelineDepth) continue; - if (!bestConn || (connLength < bestConnLength)) { - bestConn = conn; - bestConnLength = connLength; - } - } + validConns.AppendElement(conn); + + // Prefer a pipeline that either has at least two requests + // queued already, or for which we can add multiple requests + if (requestLen + allClasses < mMaxOptimisticPipelinedRequests) + continue; + + betterConns.AppendElement(conn); - if (!bestConn) + // Prefer a pipeline with the same classification if + // our current classes will put it over the line + if (conn->Classification() != classification) + continue; + if (requestLen + sameClass < mMaxOptimisticPipelinedRequests) + continue; + + bestConns.AppendElement(conn); + } + + const char *type; + if (bestConns.Length()) { + type = "best"; + bestConn = bestConns[rand()%bestConns.Length()]; + } else if (betterConns.Length()) { + type = "better"; + bestConn = betterConns[rand()%betterConns.Length()]; + } else if (validConns.Length() && totalDepth == 0) { + // We only use valid conns if it's a last resort + // (No other requests are pending or in flight) + type = "valid"; + bestConn = validConns[rand()%validConns.Length()]; + } else { 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())); + LOG((" scheduling trans %p on pipeline at position %d, type %s\n", + trans, trans->PipelinePosition(), type)); + +#ifdef WTF_DEBUG + pipeline = activeTrans->QueryPipeline(); + fprintf(stderr, + "WTF-depth: Added trans to %s of %zu/%zu/%zu/%u pipelines. Request len %d/%d/%d for %s\n", + type, bestConns.Length(), betterConns.Length(), validConns.Length(), + numPipelines, pipeline->RequestDepth(), activeTrans->PipelineDepth(), + maxdepth, ent->mConnInfo->Origin()); +#endif if ((ent->PipelineState() == PS_YELLOW) && (trans->PipelinePosition() > 1)) ent->SetYellowConnection(bestConn); @@ -1462,25 +1555,12 @@ nsHttpConnectionMgr::TryDispatchTransaction(nsConnectionEntry *ent, nsHttpTransaction::Classifier classification = trans->Classification(); uint32_t caps = trans->Caps(); + bool allowNewPipelines = true; + // 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); RefPtr unusedSpdyPersistentConnection; // step 0 @@ -1524,18 +1604,14 @@ nsHttpConnectionMgr::TryDispatchTransaction(nsConnectionEntry *ent, trans->DispatchedAsBlocking(); } - // 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)) { - LOG((" dispatched step 1 trans=%p\n", trans)); - return NS_OK; - } + // step 1: Try a pipeline + if (caps & NS_HTTP_ALLOW_PIPELINING && + AddToBestPipeline(ent, trans, classification, + mMaxPipelinedRequests)) { + return NS_OK; } + // XXX: Kill this block? It's new.. but it may be needed for SPDY // Subject most transactions at high parallelism to rate pacing. // It will only be actually submitted to the // token bucket once, and if possible it is granted admission synchronously. @@ -1561,9 +1637,20 @@ nsHttpConnectionMgr::TryDispatchTransaction(nsConnectionEntry *ent, } } - // step 2 - // consider an idle persistent connection - if (caps & NS_HTTP_ALLOW_KEEPALIVE) { + // Step 2: Decide if we should forbid new pipeline creation. + // + // FIXME: We repurposed mMaxOptimisticPipelinedRequests here to mean: + // "Don't make a new pipeline until you have this many requests pending and + // no potential connections to put them on". It might be nice to give this + // its own pref.. + if (HasPipelines(ent) && + ent->mPendingQ.Length() < mMaxOptimisticPipelinedRequests && + trans->Classification() != nsAHttpTransaction::CLASS_SOLO && + caps & NS_HTTP_ALLOW_PIPELINING) + allowNewPipelines = false; + + // step 3: consider an idle persistent connection + if (allowNewPipelines && (caps & NS_HTTP_ALLOW_KEEPALIVE)) { RefPtr conn; while (!conn && (ent->mIdleConns.Length() > 0)) { conn = ent->mIdleConns[0]; @@ -1596,23 +1683,8 @@ nsHttpConnectionMgr::TryDispatchTransaction(nsConnectionEntry *ent, } } - // step 3 - // consider pipelining scripts and revalidations - if (!attemptedOptimisticPipeline && - (classification == nsHttpTransaction::CLASS_REVALIDATION || - classification == nsHttpTransaction::CLASS_SCRIPT)) { - // Assignation kept here for documentation purpose; Never read after - attemptedOptimisticPipeline = true; - if (AddToShortestPipeline(ent, trans, - classification, - mMaxOptimisticPipelinedRequests)) { - LOG((" dispatched step 3 (pipeline) trans=%p\n", trans)); - return NS_OK; - } - } - - // step 4 - if (!onlyReusedConnection) { + // step 4: Maybe make a connection? + if (!onlyReusedConnection && allowNewPipelines) { nsresult rv = MakeNewConnection(ent, trans); if (NS_SUCCEEDED(rv)) { // this function returns NOT_AVAILABLE for asynchronous connects @@ -1633,16 +1705,6 @@ nsHttpConnectionMgr::TryDispatchTransaction(nsConnectionEntry *ent, } // step 5 - if (caps & NS_HTTP_ALLOW_PIPELINING) { - if (AddToShortestPipeline(ent, trans, - classification, - mMaxPipelinedRequests)) { - LOG((" dispatched step 5 trans=%p\n", trans)); - return NS_OK; - } - } - - // step 6 if (unusedSpdyPersistentConnection) { // to avoid deadlocks, we need to throw away this perfectly valid SPDY // connection to make room for a new one that can service a no KEEPALIVE @@ -1650,6 +1712,20 @@ nsHttpConnectionMgr::TryDispatchTransaction(nsConnectionEntry *ent, unusedSpdyPersistentConnection->DontReuse(); } + // XXX: We dequeue and queue the same url here sometimes.. +#ifdef WTF_DEBUG + nsHttpRequestHead *head = trans->RequestHead(); + nsAutoCString requestURI; + if (head) { + head->RequestURI(requestURI); + } else { + requestURI = ""; + } + fprintf(stderr, "WTF: Queuing url %s%s\n", + ent->mConnInfo->Origin(), requestURI.get()); +#endif + + LOG((" not dispatched (queued) trans=%p\n", trans)); return NS_ERROR_NOT_AVAILABLE; /* queue it */ } @@ -1779,10 +1855,33 @@ nsHttpConnectionMgr::DispatchAbstractTransaction(nsConnectionEntry *ent, if (!NS_SUCCEEDED(rv)) return rv; transaction = pipeline; +#ifdef WTF_DEBUG + if (HasPipelines(ent) && + ent->mPendingQ.Length()+1 < mMaxOptimisticPipelinedRequests) { + fprintf(stderr, "WTF-new-bug: New pipeline created from %zu idle conns for host %s with %zu/%u pending\n", + ent->mIdleConns.Length(), ent->mConnInfo->Origin(), ent->mPendingQ.Length(), + mMaxOptimisticPipelinedRequests); + } else { + fprintf(stderr, "WTF-new: New pipeline created from %zu idle conns for host %s with %zu/%u pending\n", + ent->mIdleConns.Length(), ent->mConnInfo->Origin(), ent->mPendingQ.Length(), + mMaxOptimisticPipelinedRequests); + } +#endif } else { LOG((" not using pipeline datastructure due to class solo.\n")); transaction = aTrans; +#ifdef WTF_TEST + nsHttpRequestHead *head = transaction->RequestHead(); + nsAutoCString requestURI; + if (head) { + head->RequestURI(requestURI); + } else { + requestURI = ""; + } + fprintf(stderr, "WTF-order: Pipeline forbidden for url %s%s\n", + ent->mConnInfo->Origin(), requestURI.get()); +#endif } RefPtr handle = new ConnectionHandle(conn); @@ -1919,27 +2018,18 @@ nsHttpConnectionMgr::ProcessNewTransaction(nsHttpTransaction *trans) } trans->SetConnection(nullptr); +#ifdef WTF_TEST + fprintf(stderr, "WTF-bad: Sticky connection status on 1 transaction to host %s\n", + ent->mConnInfo->Origin()); +#endif rv = DispatchTransaction(ent, trans, conn); - } else { - rv = TryDispatchTransaction(ent, !!trans->TunnelProvider(), trans); - } - - if (NS_SUCCEEDED(rv)) { - LOG((" ProcessNewTransaction Dispatch Immediately trans=%p\n", trans)); return rv; - } - - if (rv == NS_ERROR_NOT_AVAILABLE) { - LOG((" adding transaction to pending queue " - "[trans=%p pending-count=%u]\n", - trans, ent->mPendingQ.Length()+1)); - // put this transaction on the pending queue... + } else { + // XXX: maybe check the queue first and directly call TryDispatch? InsertTransactionSorted(ent->mPendingQ, trans); + ProcessPendingQForEntry(ent, true); return NS_OK; } - - LOG((" ProcessNewTransaction Hard Error trans=%p rv=%x\n", trans, rv)); - return rv; } @@ -2922,11 +3012,25 @@ nsHttpConnectionMgr::OnMsgSpeculativeConnect(int32_t, ARefBase *param) } bool keepAlive = args->mTrans->Caps() & NS_HTTP_ALLOW_KEEPALIVE; - if (mNumHalfOpenConns < parallelSpeculativeConnectLimit && + if (ent->SupportsPipelining()) { + /* Only speculative connect if we're not pipelining and have no other pending + * unconnected half-opens.. */ + if (ent->UnconnectedHalfOpens() == 0 && ent->mIdleConns.Length() == 0 + && !RestrictConnections(ent) && !HasPipelines(ent) + && !AtActiveConnectionLimit(ent, args->mTrans->Caps())) { +#ifdef WTF_DEBUG + fprintf(stderr, "WTF: Creating speculative connection because we have no pipelines\n"); +#endif + CreateTransport(ent, args->mTrans, args->mTrans->Caps(), true, isFromPredictor, allow1918); + } + } else if (mNumHalfOpenConns < parallelSpeculativeConnectLimit && ((ignoreIdle && (ent->mIdleConns.Length() < parallelSpeculativeConnectLimit)) || !ent->mIdleConns.Length()) && !(keepAlive && RestrictConnections(ent)) && !AtActiveConnectionLimit(ent, args->mTrans->Caps())) { +#ifdef WTF_DEBUG + fprintf(stderr, "WTF: Creating speculative connection because we can't pipeline\n"); +#endif CreateTransport(ent, args->mTrans, args->mTrans->Caps(), true, isFromPredictor, allow1918); } else { LOG(("OnMsgSpeculativeConnect Transport " @@ -2934,6 +3038,29 @@ nsHttpConnectionMgr::OnMsgSpeculativeConnect(int32_t, ARefBase *param) } } +bool +nsHttpConnectionMgr::HasPipelines(nsConnectionEntry *ent) +{ + uint32_t activeCount = ent->mActiveConns.Length(); + + if (!ent->SupportsPipelining()) { + return false; + } + + for (uint32_t i = 0; i < activeCount; ++i) { + nsHttpConnection *conn = ent->mActiveConns[i]; + if (!conn->SupportsPipelining()) + continue; + + nsAHttpTransaction *activeTrans = conn->Transaction(); + + if (activeTrans && !activeTrans->IsDone() && + !NS_FAILED(activeTrans->Status())) + return true; + } + return false; +} + bool ConnectionHandle::IsPersistent() { @@ -3378,6 +3505,10 @@ nsHalfOpenSocket::OnOutputStreamReady(nsIAsyncOutputStream *out) RefPtr temp = mEnt->mPendingQ[index]; mEnt->mPendingQ.RemoveElementAt(index); gHttpHandler->ConnMgr()->AddActiveConn(conn, mEnt); +#ifdef WTF_DEBUG + fprintf(stderr, "WTF: Speculative half-opened connection is now ready for %s (pipelines %d)\n", + mEnt->mConnInfo->Origin(), mEnt->SupportsPipelining()); +#endif rv = gHttpHandler->ConnMgr()->DispatchTransaction(mEnt, temp, conn); } else { // this transaction was dispatched off the pending q before all the @@ -3576,10 +3707,16 @@ nsConnectionEntry::nsConnectionEntry(nsHttpConnectionInfo *ci) , mUsedForConnection(false) { MOZ_COUNT_CTOR(nsConnectionEntry); + + // Randomize the pipeline depth (3..12) + mGreenDepth = gHttpHandler->GetMaxOptimisticPipelinedRequests() + + rand() % (gHttpHandler->GetMaxPipelinedRequests() + - gHttpHandler->GetMaxOptimisticPipelinedRequests()); + if (gHttpHandler->GetPipelineAggressive()) { - mGreenDepth = kPipelineUnlimited; mPipelineState = PS_GREEN; } + mInitialGreenDepth = mGreenDepth; memset(mPipeliningClassPenalty, 0, sizeof(int16_t) * nsAHttpTransaction::CLASS_MAX); } @@ -3628,8 +3765,9 @@ nsConnectionEntry::OnPipelineFeedbackInfo( LOG(("Transaction completed at pipeline depth of %d. Host = %s\n", depth, mConnInfo->Origin())); - if (depth >= 3) - mGreenDepth = kPipelineUnlimited; + // Don't set this. We want to keep our initial random value.. + //if (depth >= 3) + // mGreenDepth = kPipelineUnlimited; } nsAHttpTransaction::Classifier classification; @@ -3657,6 +3795,11 @@ nsConnectionEntry::OnPipelineFeedbackInfo( mPipelineState, mConnInfo->Origin())); mPipelineState = PS_RED; mPipeliningPenalty = 0; +#ifdef WTF_TEST + fprintf(stderr, "WTF-bad: Red pipeline status disabled host %s\n", + mConnInfo->Origin()); +#endif + } if (mLastCreditTime.IsNull()) diff --git a/netwerk/protocol/http/nsHttpConnectionMgr.h b/netwerk/protocol/http/nsHttpConnectionMgr.h index 7ca2a2b28d033..71a17c536bfa9 100644 --- a/netwerk/protocol/http/nsHttpConnectionMgr.h +++ b/netwerk/protocol/http/nsHttpConnectionMgr.h @@ -21,6 +21,18 @@ #include "nsIObserver.h" #include "nsITimer.h" +#include "nsIRandomGenerator.h" + +// We need our own optional debug define because pipelining behavior +// is significantly altered by rendering speed (which is abysmal on +// debug builds) +#ifdef DEBUG +# define WTF_DEBUG +#endif + +#ifdef WTF_DEBUG +# define WTF_TEST +#endif class nsIHttpUpgradeListener; @@ -525,6 +537,7 @@ private: nsresult BuildPipeline(nsConnectionEntry *, nsAHttpTransaction *, nsHttpPipeline **); + bool HasPipelines(nsConnectionEntry *); bool RestrictConnections(nsConnectionEntry *); nsresult ProcessNewTransaction(nsHttpTransaction *); nsresult EnsureSocketThreadTarget(); @@ -542,7 +555,7 @@ private: nsresult MakeNewConnection(nsConnectionEntry *ent, nsHttpTransaction *trans); - bool AddToShortestPipeline(nsConnectionEntry *ent, + bool AddToBestPipeline(nsConnectionEntry *ent, nsHttpTransaction *trans, nsHttpTransaction::Classifier classification, uint16_t depthLimit); diff --git a/netwerk/protocol/http/nsHttpHandler.h b/netwerk/protocol/http/nsHttpHandler.h index 13cc72e8ef663..1119e9009101e 100644 --- a/netwerk/protocol/http/nsHttpHandler.h +++ b/netwerk/protocol/http/nsHttpHandler.h @@ -316,6 +316,8 @@ public: nsACString& hostLine); bool GetPipelineAggressive() { return mPipelineAggressive; } + uint32_t GetMaxPipelinedRequests() { return mMaxPipelinedRequests; } + uint32_t GetMaxOptimisticPipelinedRequests() { return mMaxOptimisticPipelinedRequests; } void GetMaxPipelineObjectSize(int64_t *outVal) { *outVal = mMaxPipelineObjectSize; diff --git a/netwerk/protocol/http/nsHttpPipeline.cpp b/netwerk/protocol/http/nsHttpPipeline.cpp index 293de8e39c898..207c55b131dba 100644 --- a/netwerk/protocol/http/nsHttpPipeline.cpp +++ b/netwerk/protocol/http/nsHttpPipeline.cpp @@ -14,6 +14,7 @@ #include "nsCOMPtr.h" #include "nsSocketTransportService2.h" #include +#include "nsHttpRequestHead.h" #ifdef DEBUG #include "prthread.h" @@ -85,6 +86,32 @@ nsHttpPipeline::~nsHttpPipeline() free(mPushBackBuf); } +// Generate a shuffled request ordering sequence +void +nsHttpPipeline::ShuffleTransOrder(uint32_t count) +{ + if (count < 2) + return; + + uint32_t pos = mRequestQ[0]->PipelinePosition(); + uint32_t i = 0; + + for (i=0; i < count; ++i) { + uint32_t ridx = rand() % count; + + nsAHttpTransaction *tmp = mRequestQ[i]; + mRequestQ[i] = mRequestQ[ridx]; + mRequestQ[ridx] = tmp; + } + + for (i=0; i < count; ++i) { + mRequestQ[i]->SetPipelinePosition(pos); + pos++; + } + + LOG(("nsHttpPipeline::ShuffleTransOrder: Shuffled %d transactions.\n", count)); +} + nsresult nsHttpPipeline::AddTransaction(nsAHttpTransaction *trans) { @@ -111,6 +138,8 @@ nsHttpPipeline::AddTransaction(nsAHttpTransaction *trans) // the pipeline object. trans->SetConnection(this); + ShuffleTransOrder(mRequestQ.Length()); + if (mConnection && !mClosed && mRequestQ.Length() == 1) mConnection->ResumeSend(); @@ -772,8 +801,11 @@ nsHttpPipeline::CancelPipeline(nsresult originalReason) if (respLen > 1) mResponseQ.TruncateLength(1); - DontReuse(); - Classify(nsAHttpTransaction::CLASS_SOLO); + /* Don't flag timed out connections as unreusable.. Tor is just slow :( */ + if (originalReason != NS_ERROR_NET_TIMEOUT) { + DontReuse(); + Classify(nsAHttpTransaction::CLASS_SOLO); + } return total; } @@ -853,8 +885,19 @@ nsHttpPipeline::FillSendBuf() uint32_t n; uint64_t avail; + uint64_t totalSent = 0; + uint64_t reqsSent = 0; + uint64_t alreadyPending = 0; + + mSendBufIn->Available(&alreadyPending); + RefPtr trans; nsITransport *transport = Transport(); +#ifdef WTF_TEST + uint64_t totalAvailable = Available(); + RefPtr ci; + GetConnectionInfo(getter_AddRefs(ci)); +#endif while ((trans = Request(0)) != nullptr) { avail = trans->Available(); @@ -875,6 +918,7 @@ nsHttpPipeline::FillSendBuf() } mSendingToProgress += n; + totalSent += n; if (!mSuppressSendEvents && transport) { // Simulate a SENDING_TO event trans->OnTransportStatus(transport, @@ -885,6 +929,20 @@ nsHttpPipeline::FillSendBuf() avail = trans->Available(); if (avail == 0) { +#ifdef WTF_TEST + nsHttpRequestHead *head = trans->RequestHead(); + nsAutoCString requestURI; + if (head) { + head->RequestURI(requestURI); + } else { + requestURI = ""; + } + fprintf(stderr, "WTF-order: Pipelined req %d/%d (%dB). Url: %s%s\n", + trans->PipelinePosition(), PipelineDepth(), n, + ci->Origin(), requestURI.get()); +#endif + reqsSent++; + // move transaction from request queue to response queue mRequestQ.RemoveElementAt(0); mResponseQ.AppendElement(trans); @@ -904,6 +962,13 @@ nsHttpPipeline::FillSendBuf() else mRequestIsPartial = true; } + +#ifdef WTF_TEST + if (totalSent) + fprintf(stderr, "WTF-combine: Sent %lld/%lld bytes of %lld combined pipelined requests for host %s\n", + alreadyPending+totalSent, totalAvailable, reqsSent, ci->Origin()); +#endif + return NS_OK; } diff --git a/netwerk/protocol/http/nsHttpPipeline.h b/netwerk/protocol/http/nsHttpPipeline.h index 6dc027f19bc60..94c5dfdf38d6f 100644 --- a/netwerk/protocol/http/nsHttpPipeline.h +++ b/netwerk/protocol/http/nsHttpPipeline.h @@ -32,6 +32,8 @@ public: return true; } + uint32_t RequestDepth() { return mRequestQ.Length(); } + private: virtual ~nsHttpPipeline(); @@ -40,6 +42,8 @@ private: static nsresult ReadFromPipe(nsIInputStream *, void *, const char *, uint32_t, uint32_t, uint32_t *); + void ShuffleTransOrder(uint32_t); + // convenience functions nsAHttpTransaction *Request(int32_t i) { -- GitLab