Bug 1213443 - Parallelism for <link rel=prefetch> r=bz
authorNicholas Hurley <hurley@todesschaf.org>
Mon, 19 Oct 2015 12:46:57 -0700
changeset 303590 a0f3133b70d26dce623a76e3b7749cfda04ae828
parent 303589 689d1b250ba09967b33f484e4e9da525634a8926
child 303591 a2b068308d0455c19e9fb52b23522c08998ff619
push id1001
push userraliiev@mozilla.com
push dateMon, 18 Jan 2016 19:06:03 +0000
treeherdermozilla-release@8b89261f3ac4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs1213443
milestone44.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1213443 - Parallelism for <link rel=prefetch> r=bz
docshell/test/unit/test_bug442584.js
uriloader/prefetch/nsIPrefetchService.idl
uriloader/prefetch/nsPrefetchService.cpp
uriloader/prefetch/nsPrefetchService.h
--- a/docshell/test/unit/test_bug442584.js
+++ b/docshell/test/unit/test_bug442584.js
@@ -9,26 +9,22 @@ function run_test() {
   // Fill up the queue
   prefs.setBoolPref("network.prefetch-next", true);
   for (var i = 0; i < 5; i++) {
     var uri = ios.newURI("http://localhost/" + i, null, null);
     prefetch.prefetchURI(uri, uri, null, true);
   }
 
   // Make sure the queue has items in it...
-  var queue = prefetch.enumerateQueue();
-  do_check_true(queue.hasMoreElements());
+  do_check_true(prefetch.hasMoreElements());
 
   // Now disable the pref to force the queue to empty...
   prefs.setBoolPref("network.prefetch-next", false);
-  queue = prefetch.enumerateQueue();
-  do_check_false(queue.hasMoreElements());
+  do_check_false(prefetch.hasMoreElements());
 
   // Now reenable the pref, and add more items to the queue.
   prefs.setBoolPref("network.prefetch-next", true);
   for (var i = 0; i < 5; i++) {
     var uri = ios.newURI("http://localhost/" + i, null, null);
     prefetch.prefetchURI(uri, uri, null, true);
   }
-  queue = prefetch.enumerateQueue();
-  do_check_true(queue.hasMoreElements());
+  do_check_true(prefetch.hasMoreElements());
 }
-
--- a/uriloader/prefetch/nsIPrefetchService.idl
+++ b/uriloader/prefetch/nsIPrefetchService.idl
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 
 interface nsIURI;
 interface nsIDOMNode;
 interface nsISimpleEnumerator;
 
-[scriptable, uuid(bc4dbb34-b148-11e2-b82c-08002734a811)]
+[scriptable, uuid(2df8b475-f536-4a1a-afea-b39843df8005)]
 interface nsIPrefetchService : nsISupports
 {
     /**
      * Enqueue a request to prefetch the specified URI.
      *
      * @param aURI the URI of the document to prefetch
      * @param aReferrerURI the URI of the referring page
      * @param aSource the DOM node (such as a <link> tag) that requested this
@@ -21,14 +21,14 @@ interface nsIPrefetchService : nsISuppor
      * @param aExplicit the link element has an explicit prefetch link type
      */
     void prefetchURI(in nsIURI aURI,
                      in nsIURI aReferrerURI,
                      in nsIDOMNode aSource,
                      in boolean aExplicit);
 
     /**
-     * Enumerate the items in the prefetch queue.
+     * Find out if there are any prefetches running or queued
      */
-    nsISimpleEnumerator enumerateQueue();
+    boolean hasMoreElements();
 
     // XXX do we need a way to cancel prefetch requests?
 };
--- a/uriloader/prefetch/nsPrefetchService.cpp
+++ b/uriloader/prefetch/nsPrefetchService.cpp
@@ -48,124 +48,32 @@ static PRLogModuleInfo *gPrefetchLog;
 
 #undef LOG
 #define LOG(args) MOZ_LOG(gPrefetchLog, mozilla::LogLevel::Debug, args)
 
 #undef LOG_ENABLED
 #define LOG_ENABLED() MOZ_LOG_TEST(gPrefetchLog, mozilla::LogLevel::Debug)
 
 #define PREFETCH_PREF "network.prefetch-next"
+#define PARALLELISM_PREF "network.prefetch-next.parallelism"
 
 //-----------------------------------------------------------------------------
 // helpers
 //-----------------------------------------------------------------------------
 
 static inline uint32_t
 PRTimeToSeconds(PRTime t_usec)
 {
     PRTime usec_per_sec = PR_USEC_PER_SEC;
     return uint32_t(t_usec /= usec_per_sec);
 }
 
 #define NowInSeconds() PRTimeToSeconds(PR_Now())
 
 //-----------------------------------------------------------------------------
-// nsPrefetchQueueEnumerator
-//-----------------------------------------------------------------------------
-class nsPrefetchQueueEnumerator final : public nsISimpleEnumerator
-{
-public:
-    NS_DECL_ISUPPORTS
-    NS_DECL_NSISIMPLEENUMERATOR
-    explicit nsPrefetchQueueEnumerator(nsPrefetchService *aService);
-
-private:
-    ~nsPrefetchQueueEnumerator();
-
-    void Increment();
-
-    RefPtr<nsPrefetchService> mService;
-    RefPtr<nsPrefetchNode> mCurrent;
-    bool mStarted;
-};
-
-//-----------------------------------------------------------------------------
-// nsPrefetchQueueEnumerator <public>
-//-----------------------------------------------------------------------------
-nsPrefetchQueueEnumerator::nsPrefetchQueueEnumerator(nsPrefetchService *aService)
-    : mService(aService)
-    , mStarted(false)
-{
-    Increment();
-}
-
-nsPrefetchQueueEnumerator::~nsPrefetchQueueEnumerator()
-{
-}
-
-//-----------------------------------------------------------------------------
-// nsPrefetchQueueEnumerator::nsISimpleEnumerator
-//-----------------------------------------------------------------------------
-NS_IMETHODIMP
-nsPrefetchQueueEnumerator::HasMoreElements(bool *aHasMore)
-{
-    *aHasMore = (mCurrent != nullptr);
-    return NS_OK;
-}
-
-NS_IMETHODIMP
-nsPrefetchQueueEnumerator::GetNext(nsISupports **aItem)
-{
-    if (!mCurrent) return NS_ERROR_FAILURE;
-
-    NS_ADDREF(*aItem = static_cast<nsIStreamListener*>(mCurrent.get()));
-
-    Increment();
-
-    return NS_OK;
-}
-
-//-----------------------------------------------------------------------------
-// nsPrefetchQueueEnumerator <private>
-//-----------------------------------------------------------------------------
-
-void
-nsPrefetchQueueEnumerator::Increment()
-{
-  if (!mStarted) {
-    // If the service is currently serving a request, it won't be in
-    // the pending queue, so we return it first.  If it isn't, we'll
-    // just start with the pending queue.
-    mStarted = true;
-    mCurrent = mService->GetCurrentNode();
-    if (!mCurrent)
-      mCurrent = mService->GetQueueHead();
-    return;
-  }
-
-  if (mCurrent) {
-    if (mCurrent == mService->GetCurrentNode()) {
-      // If we just returned the node being processed by the service,
-      // start with the pending queue
-      mCurrent = mService->GetQueueHead();
-    }
-    else {
-      // Otherwise just advance to the next item in the queue
-      mCurrent = mCurrent->mNext;
-    }
-  }
-}
-
-//-----------------------------------------------------------------------------
-// nsPrefetchQueueEnumerator::nsISupports
-//-----------------------------------------------------------------------------
-
-NS_IMPL_ISUPPORTS(nsPrefetchQueueEnumerator, nsISimpleEnumerator)
-
-//-----------------------------------------------------------------------------
 // nsPrefetchNode <public>
 //-----------------------------------------------------------------------------
 
 nsPrefetchNode::nsPrefetchNode(nsPrefetchService *aService,
                                nsIURI *aURI,
                                nsIURI *aReferrerURI,
                                nsIDOMNode *aSource)
     : mNext(nullptr)
@@ -312,17 +220,17 @@ nsPrefetchNode::OnStopRequest(nsIRequest
     if (mBytesRead == 0 && aStatus == NS_OK) {
         // we didn't need to read (because LOAD_ONLY_IF_MODIFIED was
         // specified), but the object should report loadedSize as if it
         // did.
         mChannel->GetContentLength(&mBytesRead);
     }
 
     mService->NotifyLoadCompleted(this);
-    mService->ProcessNextURI();
+    mService->ProcessNextURI(this);
     return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
 // nsPrefetchNode::nsIInterfaceRequestor
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
@@ -401,25 +309,27 @@ nsPrefetchNode::OnRedirectResult(bool pr
 
 //-----------------------------------------------------------------------------
 // nsPrefetchService <public>
 //-----------------------------------------------------------------------------
 
 nsPrefetchService::nsPrefetchService()
     : mQueueHead(nullptr)
     , mQueueTail(nullptr)
+    , mMaxParallelism(6)
     , mStopCount(0)
     , mHaveProcessed(false)
     , mDisabled(true)
 {
 }
 
 nsPrefetchService::~nsPrefetchService()
 {
     Preferences::RemoveObserver(this, PREFETCH_PREF);
+    Preferences::RemoveObserver(this, PARALLELISM_PREF);
     // cannot reach destructor if prefetch in progress (listener owns reference
     // to this service)
     EmptyQueue();
 }
 
 nsresult
 nsPrefetchService::Init()
 {
@@ -427,55 +337,72 @@ nsPrefetchService::Init()
         gPrefetchLog = PR_NewLogModule("nsPrefetch");
 
     nsresult rv;
 
     // read prefs and hook up pref observer
     mDisabled = !Preferences::GetBool(PREFETCH_PREF, !mDisabled);
     Preferences::AddWeakObserver(this, PREFETCH_PREF);
 
+    mMaxParallelism = Preferences::GetInt(PARALLELISM_PREF, mMaxParallelism);
+    if (mMaxParallelism < 1) {
+        mMaxParallelism = 1;
+    }
+    Preferences::AddWeakObserver(this, PARALLELISM_PREF);
+
     // Observe xpcom-shutdown event
     nsCOMPtr<nsIObserverService> observerService =
       mozilla::services::GetObserverService();
     if (!observerService)
       return NS_ERROR_FAILURE;
 
     rv = observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
     NS_ENSURE_SUCCESS(rv, rv);
 
     if (!mDisabled)
         AddProgressListener();
 
     return NS_OK;
 }
 
 void
-nsPrefetchService::ProcessNextURI()
+nsPrefetchService::ProcessNextURI(nsPrefetchNode *aFinished)
 {
     nsresult rv;
     nsCOMPtr<nsIURI> uri, referrer;
 
-    mCurrentNode = nullptr;
+    if (aFinished) {
+        mCurrentNodes.RemoveElement(aFinished);
+    }
+
+    if (mCurrentNodes.Length() >= static_cast<uint32_t>(mMaxParallelism)) {
+        // We already have enough prefetches going on, so hold off
+        // for now.
+        return;
+    }
 
     do {
-        rv = DequeueNode(getter_AddRefs(mCurrentNode));
+        RefPtr<nsPrefetchNode> node;
+        rv = DequeueNode(getter_AddRefs(node));
 
         if (NS_FAILED(rv)) break;
 
         if (LOG_ENABLED()) {
             nsAutoCString spec;
-            mCurrentNode->mURI->GetSpec(spec);
+            node->mURI->GetSpec(spec);
             LOG(("ProcessNextURI [%s]\n", spec.get()));
         }
 
         //
         // if opening the channel fails, then just skip to the next uri
         //
-        RefPtr<nsPrefetchNode> node = mCurrentNode;
         rv = node->OpenChannel();
+        if (NS_SUCCEEDED(rv)) {
+            mCurrentNodes.AppendElement(node);
+        }
     }
     while (NS_FAILED(rv));
 }
 
 void
 nsPrefetchService::NotifyLoadRequested(nsPrefetchNode *node)
 {
     nsCOMPtr<nsIObserverService> observerService =
@@ -593,35 +520,40 @@ nsPrefetchService::StartPrefetching()
     if (mStopCount > 0)
         mStopCount--;
 
     LOG(("StartPrefetching [stopcount=%d]\n", mStopCount));
 
     // only start prefetching after we've received enough DOCUMENT
     // STOP notifications.  we do this inorder to defer prefetching
     // until after all sub-frames have finished loading.
-    if (mStopCount == 0 && !mCurrentNode) {
+    if (!mStopCount) {
         mHaveProcessed = true;
-        ProcessNextURI();
+        while (mQueueHead && mCurrentNodes.Length() < static_cast<uint32_t>(mMaxParallelism)) {
+            ProcessNextURI(nullptr);
+        }
     }
 }
 
 void
 nsPrefetchService::StopPrefetching()
 {
     mStopCount++;
 
     LOG(("StopPrefetching [stopcount=%d]\n", mStopCount));
 
-    // only kill the prefetch queue if we've actually started prefetching.
-    if (!mCurrentNode)
+    // only kill the prefetch queue if we are actively prefetching right now
+    if (mCurrentNodes.IsEmpty()) {
         return;
+    }
 
-    mCurrentNode->CancelChannel(NS_BINDING_ABORTED);
-    mCurrentNode = nullptr;
+    for (uint32_t i = 0; i < mCurrentNodes.Length(); ++i) {
+        mCurrentNodes[i]->CancelChannel(NS_BINDING_ABORTED);
+    }
+    mCurrentNodes.Clear();
     EmptyQueue();
 }
 
 //-----------------------------------------------------------------------------
 // nsPrefetchService::nsISupports
 //-----------------------------------------------------------------------------
 
 NS_IMPL_ISUPPORTS(nsPrefetchService,
@@ -700,19 +632,19 @@ nsPrefetchService::Prefetch(nsIURI *aURI
             LOG(("rejected: URL has a query string\n"));
             return NS_ERROR_ABORT;
         }
     }
 
     //
     // cancel if being prefetched
     //
-    if (mCurrentNode) {
+    for (uint32_t i = 0; i < mCurrentNodes.Length(); ++i) {
         bool equals;
-        if (NS_SUCCEEDED(mCurrentNode->mURI->Equals(aURI, &equals)) && equals) {
+        if (NS_SUCCEEDED(mCurrentNodes[i]->mURI->Equals(aURI, &equals)) && equals) {
             LOG(("rejected: URL is already being prefetched\n"));
             return NS_ERROR_ABORT;
         }
     }
 
     //
     // cancel if already on the prefetch queue
     //
@@ -728,39 +660,36 @@ nsPrefetchService::Prefetch(nsIURI *aURI
     RefPtr<nsPrefetchNode> enqueuedNode;
     rv = EnqueueURI(aURI, aReferrerURI, aSource,
                     getter_AddRefs(enqueuedNode));
     NS_ENSURE_SUCCESS(rv, rv);
 
     NotifyLoadRequested(enqueuedNode);
 
     // if there are no pages loading, kick off the request immediately
-    if (mStopCount == 0 && mHaveProcessed)
-        ProcessNextURI();
+    if (mStopCount == 0 && mHaveProcessed) {
+        ProcessNextURI(nullptr);
+    }
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsPrefetchService::PrefetchURI(nsIURI *aURI,
                                nsIURI *aReferrerURI,
                                nsIDOMNode *aSource,
                                bool aExplicit)
 {
     return Prefetch(aURI, aReferrerURI, aSource, aExplicit);
 }
 
 NS_IMETHODIMP
-nsPrefetchService::EnumerateQueue(nsISimpleEnumerator **aEnumerator)
+nsPrefetchService::HasMoreElements(bool *aHasMore)
 {
-    *aEnumerator = new nsPrefetchQueueEnumerator(this);
-    if (!*aEnumerator) return NS_ERROR_OUT_OF_MEMORY;
-
-    NS_ADDREF(*aEnumerator);
-
+    *aHasMore = (mCurrentNodes.Length() || mQueueHead);
     return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
 // nsPrefetchService::nsIWebProgressListener
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
@@ -833,30 +762,44 @@ nsPrefetchService::Observe(nsISupports  
     LOG(("nsPrefetchService::Observe [topic=%s]\n", aTopic));
 
     if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
         StopPrefetching();
         EmptyQueue();
         mDisabled = true;
     }
     else if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
-        if (Preferences::GetBool(PREFETCH_PREF, false)) {
-            if (mDisabled) {
-                LOG(("enabling prefetching\n"));
-                mDisabled = false;
-                AddProgressListener();
+        const char *pref = NS_ConvertUTF16toUTF8(aData).get();
+        if (!strcmp(pref, PREFETCH_PREF)) {
+            if (Preferences::GetBool(PREFETCH_PREF, false)) {
+                if (mDisabled) {
+                    LOG(("enabling prefetching\n"));
+                    mDisabled = false;
+                    AddProgressListener();
+                }
+            } else {
+                if (!mDisabled) {
+                    LOG(("disabling prefetching\n"));
+                    StopPrefetching();
+                    EmptyQueue();
+                    mDisabled = true;
+                    RemoveProgressListener();
+                }
             }
-        } 
-        else {
-            if (!mDisabled) {
-                LOG(("disabling prefetching\n"));
-                StopPrefetching();
-                EmptyQueue();
-                mDisabled = true;
-                RemoveProgressListener();
+        } else if (!strcmp(pref, PARALLELISM_PREF)) {
+            mMaxParallelism = Preferences::GetInt(PARALLELISM_PREF, mMaxParallelism);
+            if (mMaxParallelism < 1) {
+                mMaxParallelism = 1;
+            }
+            // If our parallelism has increased, go ahead and kick off enough
+            // prefetches to fill up our allowance. If we're now over our
+            // allowance, we'll just silently let some of them finish to get
+            // back below our limit.
+            while (mQueueHead && mCurrentNodes.Length() < static_cast<uint32_t>(mMaxParallelism)) {
+                ProcessNextURI(nullptr);
             }
         }
     }
 
     return NS_OK;
 }
 
 // vim: ts=4 sw=4 expandtab
--- a/uriloader/prefetch/nsPrefetchService.h
+++ b/uriloader/prefetch/nsPrefetchService.h
@@ -35,20 +35,17 @@ public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSIPREFETCHSERVICE
     NS_DECL_NSIWEBPROGRESSLISTENER
     NS_DECL_NSIOBSERVER
 
     nsPrefetchService();
 
     nsresult Init();
-    void     ProcessNextURI();
-
-    nsPrefetchNode *GetCurrentNode() { return mCurrentNode.get(); }
-    nsPrefetchNode *GetQueueHead() { return mQueueHead; }
+    void     ProcessNextURI(nsPrefetchNode *aFinished);
 
     void NotifyLoadRequested(nsPrefetchNode *node);
     void NotifyLoadCompleted(nsPrefetchNode *node);
 
 private:
     ~nsPrefetchService();
 
     nsresult Prefetch(nsIURI *aURI,
@@ -64,17 +61,18 @@ private:
     nsresult DequeueNode(nsPrefetchNode **node);
     void     EmptyQueue();
 
     void     StartPrefetching();
     void     StopPrefetching();
 
     nsPrefetchNode                   *mQueueHead;
     nsPrefetchNode                   *mQueueTail;
-    RefPtr<nsPrefetchNode>          mCurrentNode;
+    nsTArray<RefPtr<nsPrefetchNode>> mCurrentNodes;
+    int32_t                           mMaxParallelism;
     int32_t                           mStopCount;
     // true if pending document loads have ever reached zero.
     int32_t                           mHaveProcessed;
     bool                              mDisabled;
 };
 
 //-----------------------------------------------------------------------------
 // nsPrefetchNode