Bug 1355746 - Part 2. Polish IdleTaskRunner and reuse it for background parsing. draft
authorHenry Chang <hchang@mozilla.com>
Wed, 12 Jul 2017 09:23:15 +0800
changeset 642450 89a3a01fb3f5d41d712c4b798890ecf06cec3a4c
parent 642449 bc25b9fd84a85622dbe7db18cfc1c139b8df42c5
child 642451 1644732b27bb31097b678cab22e50b2d982847db
push id72752
push userhchang@mozilla.com
push dateTue, 08 Aug 2017 08:47:58 +0000
bugs1355746
milestone57.0a1
Bug 1355746 - Part 2. Polish IdleTaskRunner and reuse it for background parsing. This patch is mainly to make IdleTaskRunner reusable by nsHtml5TreeOpExecutor. The only necessary work to that purpose is to remove the dependency of sShuttingDown, which was a static variable in nsJSEnvironment.cpp. The idea is to have a "MayStopProcessing" as a callback for the consumer to return sShuttingDown. In addition to sShuttingDown, we use std::function<bool()> as the runner main callback type. MozReview-Commit-ID: FT2X1unSvPS
dom/base/nsJSEnvironment.cpp
parser/html/nsHtml5TreeOpExecutor.cpp
xpcom/threads/IdleTaskRunner.cpp
xpcom/threads/IdleTaskRunner.h
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -1570,17 +1570,17 @@ nsJSContext::ClearMaxCCSliceTime()
 
 uint32_t
 nsJSContext::GetMaxCCSliceTimeSinceClear()
 {
   return gCCStats.mMaxSliceTimeSinceClear;
 }
 
 static bool
-ICCRunnerFired(TimeStamp aDeadline, void* aData)
+ICCRunnerFired(TimeStamp aDeadline)
 {
   if (sDidShutdown) {
     return false;
   }
 
   // Ignore ICC timer fires during IGC. Running ICC during an IGC will cause us
   // to synchronously finish the GC, which is bad.
 
@@ -1611,18 +1611,22 @@ nsJSContext::BeginCycleCollectionCallbac
   KillCCRunner();
 
   gCCStats.RunForgetSkippable();
 
   MOZ_ASSERT(!sICCRunner, "Tried to create a new ICC timer when one already existed.");
 
   // Create an ICC timer even if ICC is globally disabled, because we could be manually triggering
   // an incremental collection, and we want to be sure to finish it.
-  sICCRunner = CollectorRunner::Create(ICCRunnerFired, kICCIntersliceDelay,
-                                       kIdleICCSliceBudget, true);
+  sICCRunner = IdleTaskRunner::Create(ICCRunnerFired,
+                                      kICCIntersliceDelay,
+                                      kIdleICCSliceBudget,
+                                      true,
+                                      []{ return sShuttingDown; },
+                                      TaskCategory::GarbageCollection);
 }
 
 static_assert(NS_GC_DELAY > kMaxICCDuration, "A max duration ICC shouldn't reduce GC delay to 0");
 
 //static
 void
 nsJSContext::EndCycleCollectionCallback(CycleCollectorResults &aResults)
 {
@@ -1824,21 +1828,23 @@ InterSliceGCRunnerFired(TimeStamp aDeadl
 }
 
 // static
 void
 GCTimerFired(nsITimer *aTimer, void *aClosure)
 {
   nsJSContext::KillGCTimer();
   // Now start the actual GC after initial timer has fired.
-  sInterSliceGCRunner = CollectorRunner::Create(InterSliceGCRunnerFired,
-                                                NS_INTERSLICE_GC_DELAY,
-                                                sActiveIntersliceGCBudget,
-                                                false,
-                                                aClosure);
+  sInterSliceGCRunner = IdleTaskRunner::Create([aClosure](TimeStamp aDeadline) {
+    return InterSliceGCRunnerFired(aDeadline, aClosure);
+  }, NS_INTERSLICE_GC_DELAY,
+     sActiveIntersliceGCBudget,
+     false,
+     []{ return sShuttingDown; },
+     TaskCategory::GarbageCollection);
 }
 
 // static
 void
 ShrinkingGCTimerFired(nsITimer* aTimer, void* aClosure)
 {
   nsJSContext::KillShrinkingGCTimer();
   sIsCompactingOnUserInactive = true;
@@ -1852,17 +1858,17 @@ ShouldTriggerCC(uint32_t aSuspected)
 {
   return sNeedsFullCC ||
          aSuspected > NS_CC_PURPLE_LIMIT ||
          (aSuspected > NS_CC_FORCED_PURPLE_LIMIT &&
           TimeUntilNow(sLastCCEndTime) > NS_CC_FORCED);
 }
 
 static bool
-CCRunnerFired(TimeStamp aDeadline, void* aData)
+CCRunnerFired(TimeStamp aDeadline)
 {
   if (sDidShutdown) {
     return false;
   }
 
   static uint32_t ccDelay = NS_CC_DELAY;
   if (sCCLockedOut) {
     ccDelay = NS_CC_DELAY / 3;
@@ -2014,23 +2020,23 @@ nsJSContext::RunNextCollectorTimer()
   }
 
   // Check the CC timers after the GC timers, because the CC timers won't do
   // anything if a GC is in progress.
   MOZ_ASSERT(!sCCLockedOut, "Don't check the CC timers if the CC is locked out.");
 
   if (sCCRunner) {
     if (ReadyToTriggerExpensiveCollectorTimer()) {
-      CCRunnerFired(TimeStamp(), nullptr);
+      CCRunnerFired(TimeStamp());
     }
     return;
   }
 
   if (sICCRunner) {
-    ICCRunnerFired(TimeStamp(), nullptr);
+    ICCRunnerFired(TimeStamp());
     return;
   }
 }
 
 // static
 void
 nsJSContext::PokeGC(JS::gcreason::Reason aReason,
                     JSObject* aObj,
@@ -2127,18 +2133,20 @@ nsJSContext::MaybePokeCC()
 
   if (ShouldTriggerCC(nsCycleCollector_suspectedCount())) {
     sCCRunnerFireCount = 0;
 
     // We can kill some objects before running forgetSkippable.
     nsCycleCollector_dispatchDeferredDeletion();
 
     sCCRunner =
-      CollectorRunner::Create(CCRunnerFired, NS_CC_SKIPPABLE_DELAY,
-                              kForgetSkippableSliceDuration, true);
+      IdleTaskRunner::Create(CCRunnerFired, NS_CC_SKIPPABLE_DELAY,
+                             kForgetSkippableSliceDuration, true,
+                             []{ return sShuttingDown; },
+                             TaskCategory::GarbageCollection);
   }
 }
 
 //static
 void
 nsJSContext::KillGCTimer()
 {
   if (sGCTimer) {
@@ -2312,18 +2320,23 @@ DOMGCSliceCallback(JSContext* aCx, JS::G
     case JS::GC_SLICE_END:
       sGCUnnotifiedTotalTime +=
         aDesc.lastSliceEnd(aCx) - aDesc.lastSliceStart(aCx);
 
       // Schedule another GC slice if the GC has more work to do.
       nsJSContext::KillInterSliceGCRunner();
       if (!sShuttingDown && !aDesc.isComplete_) {
         sInterSliceGCRunner =
-          CollectorRunner::Create(InterSliceGCRunnerFired, NS_INTERSLICE_GC_DELAY,
-                                  sActiveIntersliceGCBudget, false);
+          IdleTaskRunner::Create([](TimeStamp aDeadline) {
+            return InterSliceGCRunnerFired(aDeadline, nullptr);
+          }, NS_INTERSLICE_GC_DELAY,
+             sActiveIntersliceGCBudget,
+             false,
+             []{ return sShuttingDown; },
+             TaskCategory::GarbageCollection);
       }
 
       if (ShouldTriggerCC(nsCycleCollector_suspectedCount())) {
         nsCycleCollector_dispatchDeferredDeletion();
       }
 
       if (sPostGCEventsToConsole) {
         nsString prefix, gcstats;
--- a/parser/html/nsHtml5TreeOpExecutor.cpp
+++ b/parser/html/nsHtml5TreeOpExecutor.cpp
@@ -28,16 +28,17 @@
 #include "mozilla/css/Loader.h"
 #include "GeckoProfiler.h"
 #include "nsIScriptError.h"
 #include "nsIScriptContext.h"
 #include "mozilla/Preferences.h"
 #include "nsIHTMLDocument.h"
 #include "nsIViewSourceChannel.h"
 #include "xpcpublic.h"
+#include "mozilla/IdleTaskRunner.h"
 
 using namespace mozilla;
 
 NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(nsHtml5TreeOpExecutor)
   NS_INTERFACE_TABLE_INHERITED(nsHtml5TreeOpExecutor,
                                nsIContentSink)
 NS_INTERFACE_TABLE_TAIL_INHERITING(nsHtml5DocumentBuilder)
 
@@ -57,17 +58,17 @@ class nsHtml5ExecutorReflusher : public 
     NS_IMETHOD Run() override
     {
       mExecutor->RunFlushLoop();
       return NS_OK;
     }
 };
 
 static mozilla::LinkedList<nsHtml5TreeOpExecutor>* gBackgroundFlushList = nullptr;
-static nsITimer* gFlushTimer = nullptr;
+StaticRefPtr<IdleTaskRunner> gBackgroundFlushRunner;
 
 nsHtml5TreeOpExecutor::nsHtml5TreeOpExecutor()
   : nsHtml5DocumentBuilder(false)
   , mSuppressEOF(false)
   , mReadingFromStage(false)
   , mStreamParser(nullptr)
   , mPreloadedURLs(23)  // Mean # of preloadable resources per page on dmoz
   , mSpeculationReferrerPolicy(mozilla::net::RP_Unset)
@@ -81,19 +82,19 @@ nsHtml5TreeOpExecutor::nsHtml5TreeOpExec
 nsHtml5TreeOpExecutor::~nsHtml5TreeOpExecutor()
 {
   if (gBackgroundFlushList && isInList()) {
     mOpQueue.Clear();
     removeFrom(*gBackgroundFlushList);
     if (gBackgroundFlushList->isEmpty()) {
       delete gBackgroundFlushList;
       gBackgroundFlushList = nullptr;
-      if (gFlushTimer) {
-        gFlushTimer->Cancel();
-        NS_RELEASE(gFlushTimer);
+      if (gBackgroundFlushRunner) {
+        gBackgroundFlushRunner->Cancel();
+        gBackgroundFlushRunner = nullptr;
       }
     }
   }
   NS_ASSERTION(mOpQueue.IsEmpty(), "Somehow there's stuff in the op queue.");
 }
 
 // nsIContentSink
 NS_IMETHODIMP
@@ -245,29 +246,31 @@ nsHtml5TreeOpExecutor::MarkAsBroken(nsre
     if (NS_FAILED(mDocument->Dispatch(TaskCategory::Network,
                                       terminator.forget()))) {
       NS_WARNING("failed to dispatch executor flush event");
     }
   }
   return aReason;
 }
 
-void
-FlushTimerCallback(nsITimer* aTimer, void* aClosure)
+static bool
+BackgroundFlushCallback(TimeStamp /*aDeadline*/)
 {
   RefPtr<nsHtml5TreeOpExecutor> ex = gBackgroundFlushList->popFirst();
   if (ex) {
     ex->RunFlushLoop();
   }
   if (gBackgroundFlushList && gBackgroundFlushList->isEmpty()) {
     delete gBackgroundFlushList;
     gBackgroundFlushList = nullptr;
-    gFlushTimer->Cancel();
-    NS_RELEASE(gFlushTimer);
+    gBackgroundFlushRunner->Cancel();
+    gBackgroundFlushRunner = nullptr;
+    return true;
   }
+  return true;
 }
 
 void
 nsHtml5TreeOpExecutor::ContinueInterruptedParsingAsync()
 {
   if (!mDocument || !mDocument->IsInBackgroundWindow()) {
     nsCOMPtr<nsIRunnable> flusher = new nsHtml5ExecutorReflusher(this);
     if (NS_FAILED(mDocument->Dispatch(TaskCategory::Network,
@@ -276,26 +279,27 @@ nsHtml5TreeOpExecutor::ContinueInterrupt
     }
   } else {
     if (!gBackgroundFlushList) {
       gBackgroundFlushList = new mozilla::LinkedList<nsHtml5TreeOpExecutor>();
     }
     if (!isInList()) {
       gBackgroundFlushList->insertBack(this);
     }
-    if (!gFlushTimer) {
-      nsCOMPtr<nsITimer> t = do_CreateInstance("@mozilla.org/timer;1");
-      t.swap(gFlushTimer);
-      // The timer value 50 should not hopefully slow down background pages too
-      // much, yet lets event loop to process enough between ticks.
-      // See bug 734015.
-      gFlushTimer->InitWithNamedFuncCallback(FlushTimerCallback, nullptr,
-                                             50, nsITimer::TYPE_REPEATING_SLACK,
-                                             "FlushTimerCallback");
+    if (gBackgroundFlushRunner) {
+      NS_WARNING("We've already scheduled a task for background list flush.");
+      return;
     }
+    // Now we set up a repetitive idle scheduler for flushing background list.
+    gBackgroundFlushRunner =
+      IdleTaskRunner::Create(&BackgroundFlushCallback,
+                             250, // The hard deadline: 250ms.
+                             nsContentSink::sInteractiveParseTime / 1000, // Required budget.
+                             true, // repeating
+                             []{ return false; }); // MayStopProcessing
   }
 }
 
 void
 nsHtml5TreeOpExecutor::FlushSpeculativeLoads()
 {
   nsTArray<nsHtml5SpeculativeLoad> speculativeLoadQueue;
   mStage.MoveSpeculativeLoadsTo(speculativeLoadQueue);
--- a/xpcom/threads/IdleTaskRunner.cpp
+++ b/xpcom/threads/IdleTaskRunner.cpp
@@ -7,58 +7,72 @@
 #include "IdleTaskRunner.h"
 #include "nsRefreshDriver.h"
 #include "mozilla/SystemGroup.h"
 #include "nsComponentManagerUtils.h"
 
 namespace mozilla {
 
 already_AddRefed<IdleTaskRunner>
-IdleTaskRunner::Create(IdleTaskRunnerCallback aCallback, uint32_t aDelay,
-                       int64_t aBudget, bool aRepeating, void* aData)
+IdleTaskRunner::Create(const CallbackType& aCallback, uint32_t aDelay,
+                       int64_t aBudget, bool aRepeating,
+                       const MayStopProcessingCallbackType& aMayStopProcessing,
+                       TaskCategory aTaskCategory)
 {
-  if (sShuttingDown) {
+  if (aMayStopProcessing && aMayStopProcessing()) {
     return nullptr;
   }
 
   RefPtr<IdleTaskRunner> runner =
-    new IdleTaskRunner(aCallback, aDelay, aBudget, aRepeating, aData);
+    new IdleTaskRunner(aCallback, aDelay, aBudget, aRepeating,
+                       aMayStopProcessing, aTaskCategory);
   runner->Schedule(false); // Initial scheduling shouldn't use idle dispatch.
   return runner.forget();
 }
 
-IdleTaskRunner::IdleTaskRunner(IdleTaskRunnerCallback aCallback,
+IdleTaskRunner::IdleTaskRunner(const CallbackType& aCallback,
                                uint32_t aDelay, int64_t aBudget,
-                               bool aRepeating, void* aData)
+                               bool aRepeating,
+                               const MayStopProcessingCallbackType& aMayStopProcessing,
+                               TaskCategory aTaskCategory)
   : mCallback(aCallback), mDelay(aDelay)
   , mBudget(TimeDuration::FromMilliseconds(aBudget))
-  , mRepeating(aRepeating), mTimerActive(false), mData(aData)
+  , mRepeating(aRepeating), mTimerActive(false)
+  , mMayStopProcessing(aMayStopProcessing)
+  , mTaskCategory(aTaskCategory)
 {
 }
 
 NS_IMETHODIMP
 IdleTaskRunner::Run()
 {
   if (!mCallback) {
     return NS_OK;
   }
 
   // Deadline is null when called from timer.
+  TimeStamp now = TimeStamp::Now();
   bool deadLineWasNull = mDeadline.IsNull();
   bool didRun = false;
-  if (deadLineWasNull || ((TimeStamp::Now() + mBudget) < mDeadline)) {
+  bool allowIdleDispatch = false;
+  if (deadLineWasNull || ((now + mBudget) < mDeadline)) {
     CancelTimer();
-    didRun = mCallback(mDeadline, mData);
+    didRun = mCallback(mDeadline);
+    // If we didn't do meaningful work, don't schedule using immediate
+    // idle dispatch, since that could lead to a loop until the idle
+    // period ends.
+    allowIdleDispatch = didRun;
+  } else if (now >= mDeadline) {
+    allowIdleDispatch = true;
   }
 
   if (mCallback && (mRepeating || !didRun)) {
-    // If we didn't do meaningful work, don't schedule using immediate
-    // idle dispatch, since that could lead to a loop until the idle
-    // period ends.
-    Schedule(didRun);
+    Schedule(allowIdleDispatch);
+  } else {
+    mCallback = nullptr;
   }
 
   return NS_OK;
 }
 
 static void
 TimedOut(nsITimer* aTimer, void* aClosure)
 {
@@ -101,17 +115,17 @@ ScheduleTimedOut(nsITimer* aTimer, void*
 
 void
 IdleTaskRunner::Schedule(bool aAllowIdleDispatch)
 {
   if (!mCallback) {
     return;
   }
 
-  if (sShuttingDown) {
+  if (mMayStopProcessing && mMayStopProcessing()) {
     Cancel();
     return;
   }
 
   mDeadline = TimeStamp();
   TimeStamp now = TimeStamp::Now();
   TimeStamp hint = nsRefreshDriver::GetIdleDeadlineHint(now);
   if (hint != now) {
@@ -129,17 +143,19 @@ IdleTaskRunner::Schedule(bool aAllowIdle
       if (!mScheduleTimer) {
         mScheduleTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
         if (!mScheduleTimer) {
           return;
         }
       } else {
         mScheduleTimer->Cancel();
       }
-      mScheduleTimer->SetTarget(SystemGroup::EventTargetFor(TaskCategory::GarbageCollection));
+      if (TaskCategory::Count != mTaskCategory) {
+        mScheduleTimer->SetTarget(SystemGroup::EventTargetFor(mTaskCategory));
+      }
       // We weren't allowed to do idle dispatch immediately, do it after a
       // short timeout.
       mScheduleTimer->InitWithNamedFuncCallback(ScheduleTimedOut, this, 16,
                                                 nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY,
                                                 "IdleTaskRunner");
     }
   }
 }
@@ -157,31 +173,33 @@ IdleTaskRunner::CancelTimer()
     mTimer->Cancel();
   }
   if (mScheduleTimer) {
     mScheduleTimer->Cancel();
   }
   mTimerActive = false;
 }
 
-void 
+void
 IdleTaskRunner::SetTimerInternal(uint32_t aDelay)
 {
   if (mTimerActive) {
     return;
   }
 
   if (!mTimer) {
     mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
   } else {
     mTimer->Cancel();
   }
 
   if (mTimer) {
-    mTimer->SetTarget(SystemGroup::EventTargetFor(TaskCategory::GarbageCollection));
+    if (TaskCategory::Count != mTaskCategory) {
+      mTimer->SetTarget(SystemGroup::EventTargetFor(mTaskCategory));
+    }
     mTimer->InitWithNamedFuncCallback(TimedOut, this, aDelay,
                                       nsITimer::TYPE_ONE_SHOT,
                                       "IdleTaskRunner");
     mTimerActive = true;
   }
 }
 
 } // end of namespace mozilla
--- a/xpcom/threads/IdleTaskRunner.h
+++ b/xpcom/threads/IdleTaskRunner.h
@@ -5,51 +5,68 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef IdleTaskRunner_h
 #define IdleTaskRunner_h
 
 #include "mozilla/TimeStamp.h"
 #include "mozilla/TaskCategory.h"
 #include "nsThreadUtils.h"
+#include <functional>
 
 namespace mozilla {
 
-// Return true if some meaningful work was done.
-typedef bool (*IdleTaskRunnerCallback) (TimeStamp aDeadline, void* aData);
-
+// A general purpose repeating callback runner (it can be configured
+// to a one-time runner, too.) If it is running repeatedly,
+// one has to either explicitly Cancel() the runner or have
+// MayContinueProcessing() callback return false to completely remove
+// the runner.
 class IdleTaskRunner final : public IdleRunnable
 {
 public:
+  // Return true if some meaningful work was done.
+  using CallbackType = std::function<bool(TimeStamp aDeadline)>;
+
+  // A callback for "stop processing" decision. Return true to
+  // stop processing. This can be an alternative to Cancel() or
+  // work together in different way.
+  using MayStopProcessingCallbackType = std::function<bool()>;
+
+public:
   static already_AddRefed<IdleTaskRunner>
-  Create(IdleTaskRunnerCallback aCallback, uint32_t aDelay,
-         int64_t aBudget, bool aRepeating, void* aData = nullptr);
+  Create(const CallbackType& aCallback, uint32_t aDelay,
+         int64_t aBudget, bool aRepeating,
+         const MayStopProcessingCallbackType& aMayStopProcessing,
+         TaskCategory aTaskCategory = TaskCategory::Count);
 
   NS_IMETHOD Run() override;
 
   void SetDeadline(mozilla::TimeStamp aDeadline) override;
   void SetTimer(uint32_t aDelay, nsIEventTarget* aTarget) override;
 
   nsresult Cancel() override;
   void Schedule(bool aAllowIdleDispatch);
 
 private:
-  explicit IdleTaskRunner(IdleTaskRunnerCallback aCallback,
+  explicit IdleTaskRunner(const CallbackType& aCallback,
                           uint32_t aDelay, int64_t aBudget,
-                          bool aRepeating, void* aData);
+                          bool aRepeating,
+                          const MayStopProcessingCallbackType& aMayStopProcessing,
+                          TaskCategory aTaskCategory);
   ~IdleTaskRunner();
   void CancelTimer();
   void SetTimerInternal(uint32_t aDelay);
 
   nsCOMPtr<nsITimer> mTimer;
   nsCOMPtr<nsITimer> mScheduleTimer;
-  IdleTaskRunnerCallback mCallback;
+  CallbackType mCallback;
   uint32_t mDelay;
   TimeStamp mDeadline;
   TimeDuration mBudget;
   bool mRepeating;
   bool mTimerActive;
-  void* mData;
+  MayStopProcessingCallbackType mMayStopProcessing;
+  TaskCategory mTaskCategory;
 };
 
 } // end of namespace mozilla.
 
 #endif