Bug 1395029 - Scheduler support for blockThreadedExecution (r=froydnj)
authorBill McCloskey <billm@mozilla.com>
Thu, 21 Sep 2017 15:57:49 -0700
changeset 444703 41d3c1e5dacc237ee9cb67e3c867fe7901162aa6
parent 444702 764e854bb9b3161bb41cf6f0cf9c0272be4ffc85
child 444704 d1666bcca1a5c90372b9849dda2a1fa2b9326d9f
push id1618
push userCallek@gmail.com
push dateThu, 11 Jan 2018 17:45:48 +0000
treeherdermozilla-release@882ca853e05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfroydnj
bugs1395029
milestone58.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 1395029 - Scheduler support for blockThreadedExecution (r=froydnj) MozReview-Commit-ID: EXRMRh9SCsQ
js/xpconnect/idl/xpccomponents.idl
js/xpconnect/src/XPCComponents.cpp
xpcom/threads/Scheduler.cpp
xpcom/threads/Scheduler.h
--- a/js/xpconnect/idl/xpccomponents.idl
+++ b/js/xpconnect/idl/xpccomponents.idl
@@ -120,16 +120,25 @@ interface nsIXPCComponents_utils_Sandbox
  */
 [scriptable, function, uuid(71000535-b0fd-44d1-8ce0-909760e3953c)]
 interface ScheduledGCCallback : nsISupports
 {
     void callback();
 };
 
 /**
+ * Interface for callback to be passed to Cu.blockThreadedExecution.
+ */
+[scriptable, function, uuid(c3b85a5c-c328-47d4-aaaf-384c4ff9d77d)]
+interface nsIBlockThreadedExecutionCallback : nsISupports
+{
+    void callback();
+};
+
+/**
 * interface of Components.utils
 */
 [scriptable, uuid(86003fe3-ee9a-4620-91dc-eef8b1e58815)]
 interface nsIXPCComponents_Utils : nsISupports
 {
 
     /* reportError is designed to be called from JavaScript only.
      *
@@ -703,16 +712,31 @@ interface nsIXPCComponents_Utils : nsISu
      */
     ACString readFile(in nsIFile file);
 
     /*
      * Reads the given local file URL and returns its contents. This has the
      * same semantics of readFile.
      */
     ACString readURI(in nsIURI url);
+
+    /**
+     * If the main thread is using any kind of fancy cooperative
+     * scheduling (e.g., Quantum DOM scheduling),
+     * blockThreadedExecution disables it temporarily. The
+     * aBlockedCallback is called when it has been completely disabled
+     * and events are back to running sequentially on a single main
+     * thread. Calling unblockThreadedExecution will re-enable thread
+     * scheduling of the main thread. Multiple calls to
+     * blockThreadedExecution will require the same number of calls to
+     * unblockThreadedExecution in order to resume cooperative
+     * scheduling.
+     */
+    void blockThreadedExecution(in nsIBlockThreadedExecutionCallback aBlockedCallback);
+    void unblockThreadedExecution();
 };
 
 /**
 * Interface for the 'Components' object.
 *
 * The first interface contains things that are available to non-chrome XBL code
 * that runs in a scope with an ExpandedPrincipal. The second interface
 * includes members that are only exposed to chrome.
--- a/js/xpconnect/src/XPCComponents.cpp
+++ b/js/xpconnect/src/XPCComponents.cpp
@@ -24,16 +24,17 @@
 #include "mozilla/ResultExtensions.h"
 #include "mozilla/URLPreloader.h"
 #include "mozilla/XPTInterfaceInfoManager.h"
 #include "mozilla/dom/DOMException.h"
 #include "mozilla/dom/DOMExceptionBinding.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/StructuredCloneTags.h"
 #include "mozilla/dom/WindowBinding.h"
+#include "mozilla/Scheduler.h"
 #include "nsZipArchive.h"
 #include "nsIDOMFileList.h"
 #include "nsWindowMemoryReporter.h"
 #include "nsDOMClassInfo.h"
 #include "ShimInterfaceInfo.h"
 #include "nsIAddonInterposition.h"
 #include "nsIScriptError.h"
 #include "nsISimpleEnumerator.h"
@@ -3207,16 +3208,30 @@ nsXPCComponents_Utils::ReadURI(nsIURI* a
 NS_IMETHODIMP
 nsXPCComponents_Utils::Now(double* aRetval)
 {
     TimeStamp start = TimeStamp::ProcessCreation();
     *aRetval = (TimeStamp::Now() - start).ToMilliseconds();
     return NS_OK;
 }
 
+NS_IMETHODIMP
+nsXPCComponents_Utils::BlockThreadedExecution(nsIBlockThreadedExecutionCallback* aCallback)
+{
+    Scheduler::BlockThreadedExecution(aCallback);
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXPCComponents_Utils::UnblockThreadedExecution()
+{
+    Scheduler::UnblockThreadedExecution();
+    return NS_OK;
+}
+
 /***************************************************************************/
 /***************************************************************************/
 /***************************************************************************/
 
 
 nsXPCComponentsBase::nsXPCComponentsBase(XPCWrappedNativeScope* aScope)
     :   mScope(aScope)
 {
--- a/xpcom/threads/Scheduler.cpp
+++ b/xpcom/threads/Scheduler.cpp
@@ -16,16 +16,17 @@
 #include "mozilla/SchedulerGroup.h"
 #include "nsCycleCollector.h"
 #include "nsIThread.h"
 #include "nsPrintfCString.h"
 #include "nsThread.h"
 #include "nsThreadManager.h"
 #include "PrioritizedEventQueue.h"
 #include "xpcpublic.h"
+#include "xpccomponents.h"
 
 // Windows silliness. winbase.h defines an empty no-argument Yield macro.
 #undef Yield
 
 using namespace mozilla;
 
 // Using the anonymous namespace here causes GCC to generate:
 // error: 'mozilla::SchedulerImpl' has a field 'mozilla::SchedulerImpl::mQueue' whose type uses the anonymous namespace
@@ -88,16 +89,17 @@ private:
 using mozilla::detail::SchedulerEventQueue;
 
 class mozilla::SchedulerImpl
 {
 public:
   explicit SchedulerImpl(SchedulerEventQueue* aQueue);
 
   void Start();
+  void Stop(already_AddRefed<nsIRunnable> aStoppedCallback);
   void Shutdown();
 
   void Dispatch(already_AddRefed<nsIRunnable> aEvent);
 
   void Yield();
 
   static void EnterNestedEventLoop(Scheduler::EventLoopActivation& aOuterActivation);
   static void ExitNestedEventLoop(Scheduler::EventLoopActivation& aOuterActivation);
@@ -113,16 +115,19 @@ public:
   static void YieldCallback(JSContext* aCx);
   static bool InterruptCallback(JSContext* aCx);
 
   CooperativeThreadPool* GetThreadPool() { return mThreadPool.get(); }
 
   static bool UnlabeledEventRunning() { return sUnlabeledEventRunning; }
   static bool AnyEventRunning() { return sNumThreadsRunning > 0; }
 
+  void BlockThreadedExecution(nsIBlockThreadedExecutionCallback* aCallback);
+  void UnblockThreadedExecution();
+
   CooperativeThreadPool::Resource* GetQueueResource() { return &mQueueResource; }
   bool UseCooperativeScheduling() const { return mQueue->UseCooperativeScheduling(); }
 
   // Preferences.
   static bool sPrefScheduler;
   static bool sPrefChaoticScheduling;
   static bool sPrefPreemption;
   static size_t sPrefThreadCount;
@@ -138,16 +143,19 @@ private:
   size_t mNumThreads;
 
   // Protects mQueue as well as mThreadPool. The lock comes from the SchedulerEventQueue.
   Mutex& mLock;
   CondVar mShutdownCondVar;
 
   bool mShuttingDown;
 
+  // Runnable to call when the scheduler has finished shutting down.
+  nsTArray<nsCOMPtr<nsIRunnable>> mShutdownCallbacks;
+
   UniquePtr<CooperativeThreadPool> mThreadPool;
 
   RefPtr<SchedulerEventQueue> mQueue;
 
   class QueueResource : public CooperativeThreadPool::Resource
   {
   public:
     explicit QueueResource(SchedulerImpl* aScheduler)
@@ -197,16 +205,21 @@ private:
     MessageLoop* mOldMainLoop;
     RefPtr<SynchronizedEventQueue> mMainQueue;
   };
   ThreadController mController;
 
   static size_t sNumThreadsRunning;
   static bool sUnlabeledEventRunning;
 
+  // Number of times that BlockThreadedExecution has been called without
+  // corresponding calls to UnblockThreadedExecution. If this is non-zero,
+  // scheduling is disabled.
+  size_t mNumSchedulerBlocks = 0;
+
   JSContext* mContexts[CooperativeThreadPool::kMaxThreads];
 };
 
 bool SchedulerImpl::sPrefScheduler = false;
 bool SchedulerImpl::sPrefChaoticScheduling = false;
 bool SchedulerImpl::sPrefPreemption = false;
 bool SchedulerImpl::sPrefUseMultipleQueues = false;
 size_t SchedulerImpl::sPrefThreadCount = 2;
@@ -438,16 +451,18 @@ SchedulerImpl::Switcher()
 SchedulerImpl::SwitcherThread(void* aData)
 {
   static_cast<SchedulerImpl*>(aData)->Switcher();
 }
 
 void
 SchedulerImpl::Start()
 {
+  MOZ_ASSERT(mNumSchedulerBlocks == 0);
+
   NS_DispatchToMainThread(NS_NewRunnableFunction("Scheduler::Start", [this]() -> void {
     // Let's pretend the runnable here isn't actually running.
     MOZ_ASSERT(sUnlabeledEventRunning);
     sUnlabeledEventRunning = false;
     MOZ_ASSERT(sNumThreadsRunning == 1);
     sNumThreadsRunning = 0;
 
     mQueue->SetScheduler(this);
@@ -487,26 +502,50 @@ SchedulerImpl::Start()
     xpc::ResumeCooperativeContext();
 
     // Put things back to the way they were before we started scheduling.
     MOZ_ASSERT(!sUnlabeledEventRunning);
     sUnlabeledEventRunning = true;
     MOZ_ASSERT(sNumThreadsRunning == 0);
     sNumThreadsRunning = 1;
 
-    // Delete the SchedulerImpl. Don't use it after this point.
-    Scheduler::sScheduler = nullptr;
+    mShuttingDown = false;
+    nsTArray<nsCOMPtr<nsIRunnable>> callbacks = Move(mShutdownCallbacks);
+    for (nsIRunnable* runnable : callbacks) {
+      runnable->Run();
+    }
   }));
 }
 
 void
+SchedulerImpl::Stop(already_AddRefed<nsIRunnable> aStoppedCallback)
+{
+  MOZ_ASSERT(mNumSchedulerBlocks > 0);
+
+  // Note that this may be called when mShuttingDown is already true. We still
+  // want to invoke the callback in that case.
+
+  MutexAutoLock lock(mLock);
+  mShuttingDown = true;
+  mShutdownCallbacks.AppendElement(aStoppedCallback);
+  mShutdownCondVar.Notify();
+}
+
+void
 SchedulerImpl::Shutdown()
 {
+  MOZ_ASSERT(mNumSchedulerBlocks == 0);
+
   MutexAutoLock lock(mLock);
   mShuttingDown = true;
+
+  // Delete the SchedulerImpl once shutdown is complete.
+  mShutdownCallbacks.AppendElement(NS_NewRunnableFunction("SchedulerImpl::Shutdown",
+                                                          [] { Scheduler::sScheduler = nullptr; }));
+
   mShutdownCondVar.Notify();
 }
 
 bool
 SchedulerImpl::QueueResource::IsAvailable(const MutexAutoLock& aProofOfLock)
 {
   mScheduler->mLock.AssertCurrentThreadOwns();
 
@@ -676,16 +715,37 @@ SchedulerImpl::ThreadController::OnResum
 
 void
 SchedulerImpl::Yield()
 {
   MutexAutoLock lock(mLock);
   CooperativeThreadPool::Yield(nullptr, lock);
 }
 
+void
+SchedulerImpl::BlockThreadedExecution(nsIBlockThreadedExecutionCallback* aCallback)
+{
+  if (mNumSchedulerBlocks++ == 0 || mShuttingDown) {
+    Stop(NewRunnableMethod("BlockThreadedExecution", aCallback,
+                           &nsIBlockThreadedExecutionCallback::Callback));
+  } else {
+    // The scheduler is already blocked.
+    nsCOMPtr<nsIBlockThreadedExecutionCallback> kungFuDeathGrip(aCallback);
+    aCallback->Callback();
+  }
+}
+
+void
+SchedulerImpl::UnblockThreadedExecution()
+{
+  if (--mNumSchedulerBlocks == 0) {
+    Start();
+  }
+}
+
 /* static */ already_AddRefed<nsThread>
 Scheduler::Init(nsIIdlePeriod* aIdlePeriod)
 {
   MOZ_ASSERT(!sScheduler);
 
   RefPtr<SchedulerEventQueue> queue;
   RefPtr<nsThread> mainThread;
   if (Scheduler::UseMultipleQueues()) {
@@ -784,8 +844,30 @@ Scheduler::UnlabeledEventRunning()
   return SchedulerImpl::UnlabeledEventRunning();
 }
 
 /* static */ bool
 Scheduler::AnyEventRunning()
 {
   return SchedulerImpl::AnyEventRunning();
 }
+
+/* static */ void
+Scheduler::BlockThreadedExecution(nsIBlockThreadedExecutionCallback* aCallback)
+{
+  if (!sScheduler) {
+    nsCOMPtr<nsIBlockThreadedExecutionCallback> kungFuDeathGrip(aCallback);
+    aCallback->Callback();
+    return;
+  }
+
+  sScheduler->BlockThreadedExecution(aCallback);
+}
+
+/* static */ void
+Scheduler::UnblockThreadedExecution()
+{
+  if (!sScheduler) {
+    return;
+  }
+
+  sScheduler->UnblockThreadedExecution();
+}
--- a/xpcom/threads/Scheduler.h
+++ b/xpcom/threads/Scheduler.h
@@ -13,16 +13,17 @@
 #include "mozilla/ThreadLocal.h"
 #include "mozilla/UniquePtr.h"
 #include "nsTArray.h"
 #include "nsILabelableRunnable.h"
 
 // Windows silliness. winbase.h defines an empty no-argument Yield macro.
 #undef Yield
 
+class nsIBlockThreadedExecutionCallback;
 class nsIIdlePeriod;
 class nsThread;
 
 namespace mozilla {
 
 class SchedulerGroup;
 class SchedulerImpl;
 class SynchronizedEventQueue;
@@ -69,16 +70,19 @@ public:
 
   static bool IsCooperativeThread();
 
   static void Yield();
 
   static bool UnlabeledEventRunning();
   static bool AnyEventRunning();
 
+  static void BlockThreadedExecution(nsIBlockThreadedExecutionCallback* aCallback);
+  static void UnblockThreadedExecution();
+
   class MOZ_RAII EventLoopActivation
   {
   public:
     using EventGroups = nsILabelableRunnable::SchedulerGroupSet;
     EventLoopActivation();
     ~EventLoopActivation();
 
     static void Init();