Bug 1396155 - Support active tab prioritization and round-robin scheduling in LabeledEventQueue (r=froydnj)
authorBill McCloskey <billm@mozilla.com>
Fri, 01 Sep 2017 16:38:47 -0700
changeset 429138 0b295c9c9ffd05a089dd36519cc3000baf469763
parent 429137 8eaa67e815a5283834c43fbd0220590e4c14859a
child 429139 9967ddf61c89b809b0c819019e37ccaeb637ae34
push id7761
push userjlund@mozilla.com
push dateFri, 15 Sep 2017 00:19:52 +0000
treeherdermozilla-beta@c38455951db4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfroydnj
bugs1396155
milestone57.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 1396155 - Support active tab prioritization and round-robin scheduling in LabeledEventQueue (r=froydnj) MozReview-Commit-ID: Ax5rWAKN50h
xpcom/threads/LabeledEventQueue.cpp
xpcom/threads/LabeledEventQueue.h
xpcom/threads/SchedulerGroup.h
--- a/xpcom/threads/LabeledEventQueue.cpp
+++ b/xpcom/threads/LabeledEventQueue.cpp
@@ -1,19 +1,23 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "LabeledEventQueue.h"
+#include "mozilla/dom/TabChild.h"
+#include "mozilla/dom/TabGroup.h"
 #include "mozilla/Scheduler.h"
 #include "mozilla/SchedulerGroup.h"
 #include "nsQueryObject.h"
 
+using namespace mozilla::dom;
+
 LabeledEventQueue::LabeledEventQueue()
 {
 }
 
 static SchedulerGroup*
 GetSchedulerGroup(nsIRunnable* aEvent)
 {
   RefPtr<SchedulerGroup::Runnable> groupRunnable = do_QueryObject(aEvent);
@@ -84,16 +88,20 @@ LabeledEventQueue::PutEvent(already_AddR
     }
   }
 
   mNumEvents++;
   epoch->mNumEvents++;
 
   RunnableEpochQueue* queue = isLabeled ? mLabeled.LookupOrAdd(group) : &mUnlabeled;
   queue->Push(QueueEntry(event.forget(), epoch->mEpochNumber));
+
+  if (group && !group->isInList()) {
+    mSchedulerGroups.insertBack(group);
+  }
 }
 
 void
 LabeledEventQueue::PopEpoch()
 {
   Epoch& epoch = mEpochs.FirstElement();
   MOZ_ASSERT(epoch.mNumEvents > 0);
   if (epoch.mNumEvents == 1) {
@@ -120,36 +128,66 @@ LabeledEventQueue::GetEvent(EventPriorit
       PopEpoch();
       mUnlabeled.Pop();
       MOZ_ASSERT(entry.mEpochNumber == epoch.mEpochNumber);
       MOZ_ASSERT(entry.mRunnable.get());
       return entry.mRunnable.forget();
     }
   }
 
-  for (auto iter = mLabeled.Iter(); !iter.Done(); iter.Next()) {
-    SchedulerGroup* key = iter.Key();
-    RunnableEpochQueue* queue = iter.Data();
+  // Move active tabs to the front of the queue. The mAvoidActiveTabCount field
+  // prevents us from preferentially processing events from active tabs twice in
+  // a row. This scheme is designed to prevent starvation.
+  if (TabChild::HasActiveTabs() && mAvoidActiveTabCount <= 0) {
+    for (TabChild* tabChild : TabChild::GetActiveTabs()) {
+      SchedulerGroup* group = tabChild->TabGroup();
+      if (!group->isInList() || group == mSchedulerGroups.getFirst()) {
+        continue;
+      }
+
+      // For each active tab we move to the front of the queue, we have to
+      // process two SchedulerGroups (the active tab and another one, presumably
+      // a background group) before we prioritize active tabs again.
+      mAvoidActiveTabCount += 2;
+
+      group->removeFrom(mSchedulerGroups);
+      mSchedulerGroups.insertFront(group);
+    }
+  }
+
+  // Iterate over SchedulerGroups in order. Each time we pass by a
+  // SchedulerGroup, we move it to the back of the list. This ensures that we
+  // process SchedulerGroups in a round-robin order (ignoring active tab
+  // prioritization).
+  SchedulerGroup* firstGroup = mSchedulerGroups.getFirst();
+  SchedulerGroup* group = firstGroup;
+  do {
+    RunnableEpochQueue* queue = mLabeled.Get(group);
+    MOZ_ASSERT(queue);
     MOZ_ASSERT(!queue->IsEmpty());
 
+    mAvoidActiveTabCount--;
+    SchedulerGroup* next = group->removeAndGetNext();
+    mSchedulerGroups.insertBack(group);
+
     QueueEntry entry = queue->FirstElement();
-    if (entry.mEpochNumber != epoch.mEpochNumber) {
-      continue;
-    }
-
-    if (IsReadyToRun(entry.mRunnable, key)) {
+    if (entry.mEpochNumber == epoch.mEpochNumber &&
+        IsReadyToRun(entry.mRunnable, group)) {
       PopEpoch();
 
       queue->Pop();
       if (queue->IsEmpty()) {
-        iter.Remove();
+        mLabeled.Remove(group);
+        group->removeFrom(mSchedulerGroups);
       }
       return entry.mRunnable.forget();
     }
-  }
+
+    group = next;
+  } while (group != firstGroup);
 
   return nullptr;
 }
 
 bool
 LabeledEventQueue::IsEmpty(const MutexAutoLock& aProofOfLock)
 {
   return mEpochs.IsEmpty();
--- a/xpcom/threads/LabeledEventQueue.h
+++ b/xpcom/threads/LabeledEventQueue.h
@@ -2,16 +2,17 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_LabeledEventQueue_h
 #define mozilla_LabeledEventQueue_h
 
+#include <stdint.h>
 #include "mozilla/AbstractEventQueue.h"
 #include "mozilla/Queue.h"
 #include "nsClassHashtable.h"
 #include "nsHashKeys.h"
 
 namespace mozilla {
 
 class SchedulerGroup;
@@ -126,17 +127,26 @@ private:
   };
 
   void PopEpoch();
 
   using RunnableEpochQueue = Queue<QueueEntry, 32>;
   using LabeledMap = nsClassHashtable<nsRefPtrHashKey<SchedulerGroup>, RunnableEpochQueue>;
   using EpochQueue = Queue<Epoch, 8>;
 
+  // List of SchedulerGroups that have events in the queue.
+  LinkedList<SchedulerGroup> mSchedulerGroups;
+
   LabeledMap mLabeled;
   RunnableEpochQueue mUnlabeled;
   EpochQueue mEpochs;
   size_t mNumEvents = 0;
+
+  // Number of SchedulerGroups that must be processed before we prioritize an
+  // active tab. This field is designed to guarantee a 1:1 interleaving between
+  // foreground and background SchedulerGroups. For details, see its usage in
+  // LabeledEventQueue.cpp.
+  int64_t mAvoidActiveTabCount = 0;
 };
 
 } // namespace mozilla
 
 #endif // mozilla_LabeledEventQueue_h
--- a/xpcom/threads/SchedulerGroup.h
+++ b/xpcom/threads/SchedulerGroup.h
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_SchedulerGroup_h
 #define mozilla_SchedulerGroup_h
 
 #include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/LinkedList.h"
 #include "mozilla/TaskCategory.h"
 #include "mozilla/ThreadLocal.h"
 #include "mozilla/TimeStamp.h"
 #include "nsCOMPtr.h"
 #include "nsILabelableRunnable.h"
 #include "nsISupportsImpl.h"
 #include "nsThreadUtils.h"
 
@@ -35,17 +36,17 @@ class TabGroup;
 // (with roughly one group per tab). Runnables will be annotated with the set of
 // groups that they touch. Two runnables may run concurrently on different
 // fibers as long as they touch different groups.
 //
 // A SchedulerGroup is an abstract class to represent a "group". Essentially the
 // only functionality offered by a SchedulerGroup is the ability to dispatch
 // runnables to the group. TabGroup, DocGroup, and SystemGroup are the concrete
 // implementations of SchedulerGroup.
-class SchedulerGroup
+class SchedulerGroup : public LinkedListElement<SchedulerGroup>
 {
 public:
   SchedulerGroup();
 
   NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
 
   // This method returns true if all members of the "group" are in a
   // "background" state.