Bug 1213443 - Parallelism for <link rel=prefetch> r=bz
authorNicholas Hurley <hurley@todesschaf.org>
Mon, 19 Oct 2015 12:46:57 -0700
changeset 289600 a0f3133b70d26dce623a76e3b7749cfda04ae828
parent 289599 689d1b250ba09967b33f484e4e9da525634a8926
child 289601 a2b068308d0455c19e9fb52b23522c08998ff619
push id8654
push userraliiev@mozilla.com
push dateThu, 29 Oct 2015 11:48:40 +0000
treeherdermozilla-aurora@bc4551debe17 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs1213443
milestone44.0a1
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