Bug 1516277 - Add local execution mode to nsThread r=janv,froydnj
authorYaron Tausky <ytausky@mozilla.com>
Wed, 06 Mar 2019 16:26:07 +0000
changeset 520508 29e28daebae57e16db9d32017e7f34eb968d02fb
parent 520507 5d8d62f021f6296a6e5216258997d2d451187008
child 520509 3df57792885326a678ce60ae59d20b395f17cc35
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjanv, froydnj
bugs1516277
milestone67.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 1516277 - Add local execution mode to nsThread r=janv,froydnj Differential Revision: https://phabricator.services.mozilla.com/D21241
dom/localstorage/LSObject.cpp
dom/localstorage/test/unit/test_eviction.js
xpcom/threads/SynchronizedEventQueue.h
xpcom/threads/ThreadEventQueue.h
xpcom/threads/nsThread.cpp
xpcom/threads/nsThread.h
--- a/dom/localstorage/LSObject.cpp
+++ b/dom/localstorage/LSObject.cpp
@@ -971,25 +971,20 @@ nsresult RequestHelper::StartAndReturnRe
   // spin the event loop. Nothing can dispatch to the nested event target
   // except BackgroundChild::GetOrCreateForCurrentThread(), so spinning of the
   // event loop can't fire any other events.
   // This way the thread is synchronously blocked in a safe manner and the
   // runnable gets executed.
   {
     auto thread = static_cast<nsThread*>(NS_GetCurrentThread());
 
-    auto queue =
-        static_cast<ThreadEventQueue<EventQueue>*>(thread->EventQueue());
-
-    mNestedEventTarget = queue->PushEventQueue();
+    const nsLocalExecutionGuard localExecution(thread->EnterLocalExecution());
+    mNestedEventTarget = localExecution.GetEventTarget();
     MOZ_ASSERT(mNestedEventTarget);
 
-    auto autoPopEventQueue = mozilla::MakeScopeExit(
-        [&] { queue->PopEventQueue(mNestedEventTarget); });
-
     mNestedEventTargetWrapper =
         new NestedEventTargetWrapper(mNestedEventTarget);
 
     nsCOMPtr<nsIEventTarget> domFileThread =
         IPCBlobInputStreamThread::GetOrCreate();
     if (NS_WARN_IF(!domFileThread)) {
       return NS_ERROR_FAILURE;
     }
@@ -1025,17 +1020,17 @@ nsresult RequestHelper::StartAndReturnRe
         {
           StaticMutexAutoLock lock(gRequestHelperMutex);
           if (NS_WARN_IF(gPendingSyncMessage)) {
             return true;
           }
         }
 
         return false;
-      }));
+      }, thread));
     }
 
     // If mWaiting is still set to true, it means that the event loop spinning
     // was aborted and we need to cancel the request in the parent since we
     // don't care about the result anymore.
     // We can check mWaiting here because it's only ever touched on the main
     // thread.
     if (NS_WARN_IF(mWaiting)) {
@@ -1062,21 +1057,20 @@ nsresult RequestHelper::StartAndReturnRe
       rv = domFileThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
 
       return NS_ERROR_FAILURE;
     }
 
-    // PopEventQueue will be called automatically when we leave this scope.
-    // If the event loop spinning was aborted and other threads dispatched new
-    // runnables to the nested event queue, they will be moved to the main
-    // event queue here and later asynchronusly processed.  So nothing will be
-    // lost.
+    // localExecution will be destructed when we leave this scope. If the event
+    // loop spinning was aborted and other threads dispatched new runnables to
+    // the nested event queue, they will be moved to the main event queue here
+    // and later asynchronusly processed.  So nothing will be lost.
   }
 
   if (NS_WARN_IF(NS_FAILED(mResultCode))) {
     return mResultCode;
   }
 
   aResponse = std::move(mResponse);
   return NS_OK;
--- a/dom/localstorage/test/unit/test_eviction.js
+++ b/dom/localstorage/test/unit/test_eviction.js
@@ -35,16 +35,17 @@ async function testSteps() {
     let storage = getLocalStorage(getPrincipal(getSpec(i)));
     storages.push(storage);
   }
 
   info("Filling up entire default storage");
 
   for (let i = 0; i < data.urlCount; i++) {
     storages[i].setItem(data.key, data.value);
+    await returnToEventLoop();
   }
 
   info("Verifying no more data can be written");
 
   for (let i = 0; i < data.urlCount; i++) {
     try {
       storages[i].setItem("B", "");
       ok(false, "Should have thrown");
--- a/xpcom/threads/SynchronizedEventQueue.h
+++ b/xpcom/threads/SynchronizedEventQueue.h
@@ -8,16 +8,18 @@
 #define mozilla_SynchronizedEventQueue_h
 
 #include "mozilla/AlreadyAddRefed.h"
 #include "mozilla/AbstractEventQueue.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Mutex.h"
 #include "nsTObserverArray.h"
 
+class nsIEventTarget;
+class nsISerialEventTarget;
 class nsIThreadObserver;
 
 namespace mozilla {
 
 // A SynchronizedEventQueue is an abstract class for event queues that can be
 // used across threads. A SynchronizedEventQueue implementation will typically
 // use locks and condition variables to guarantee consistency. The methods of
 // SynchronizedEventQueue are split between ThreadTargetSink (which contains
@@ -83,16 +85,38 @@ class SynchronizedEventQueue : public Th
   virtual void SuspendInputEventPrioritization() = 0;
   virtual void ResumeInputEventPrioritization() = 0;
 
   size_t SizeOfExcludingThis(
       mozilla::MallocSizeOf aMallocSizeOf) const override {
     return mEventObservers.ShallowSizeOfExcludingThis(aMallocSizeOf);
   }
 
+  /**
+   * This method causes any events currently enqueued on the thread to be
+   * suppressed until PopEventQueue is called, and any event dispatched to this
+   * thread's nsIEventTarget will queue as well. Calls to PushEventQueue may be
+   * nested and must each be paired with a call to PopEventQueue in order to
+   * restore the original state of the thread. The returned nsIEventTarget may
+   * be used to push events onto the nested queue. Dispatching will be disabled
+   * once the event queue is popped. The thread will only ever process pending
+   * events for the innermost event queue. Must only be called on the target
+   * thread.
+   */
+  virtual already_AddRefed<nsISerialEventTarget> PushEventQueue() = 0;
+
+  /**
+   * Revert a call to PushEventQueue. When an event queue is popped, any events
+   * remaining in the queue are appended to the elder queue. This also causes
+   * the nsIEventTarget returned from PushEventQueue to stop dispatching events.
+   * Must only be called on the target thread, and with the innermost event
+   * queue.
+   */
+  virtual void PopEventQueue(nsIEventTarget* aTarget) = 0;
+
  protected:
   virtual ~SynchronizedEventQueue() {}
 
  private:
   nsTObserverArray<nsCOMPtr<nsIThreadObserver>> mEventObservers;
 };
 
 }  // namespace mozilla
--- a/xpcom/threads/ThreadEventQueue.h
+++ b/xpcom/threads/ThreadEventQueue.h
@@ -48,37 +48,18 @@ class ThreadEventQueue final : public Sy
 
   void Disconnect(const MutexAutoLock& aProofOfLock) final {}
 
   void EnableInputEventPrioritization() final;
   void FlushInputEventPrioritization() final;
   void SuspendInputEventPrioritization() final;
   void ResumeInputEventPrioritization() final;
 
-  /**
-   * This method causes any events currently enqueued on the thread to be
-   * suppressed until PopEventQueue is called, and any event dispatched to this
-   * thread's nsIEventTarget will queue as well. Calls to PushEventQueue may be
-   * nested and must each be paired with a call to PopEventQueue in order to
-   * restore the original state of the thread. The returned nsIEventTarget may
-   * be used to push events onto the nested queue. Dispatching will be disabled
-   * once the event queue is popped. The thread will only ever process pending
-   * events for the innermost event queue. Must only be called on the target
-   * thread.
-   */
-  already_AddRefed<nsISerialEventTarget> PushEventQueue();
-
-  /**
-   * Revert a call to PushEventQueue. When an event queue is popped, any events
-   * remaining in the queue are appended to the elder queue. This also causes
-   * the nsIEventTarget returned from PushEventQueue to stop dispatching events.
-   * Must only be called on the target thread, and with the innermost event
-   * queue.
-   */
-  void PopEventQueue(nsIEventTarget* aTarget);
+  already_AddRefed<nsISerialEventTarget> PushEventQueue() final;
+  void PopEventQueue(nsIEventTarget* aTarget) final;
 
   already_AddRefed<nsIThreadObserver> GetObserver() final;
   already_AddRefed<nsIThreadObserver> GetObserverOnThread() final;
   void SetObserver(nsIThreadObserver* aObserver) final;
 
   Mutex& MutexRef() { return mLock; }
 
   size_t SizeOfExcludingThis(
--- a/xpcom/threads/nsThread.cpp
+++ b/xpcom/threads/nsThread.cpp
@@ -42,16 +42,17 @@
 #include "mozilla/dom/ScriptSettings.h"
 #include "nsThreadSyncDispatch.h"
 #include "nsServiceManagerUtils.h"
 #include "GeckoProfiler.h"
 #ifdef MOZ_GECKO_PROFILER
 #  include "ProfilerMarkerPayload.h"
 #endif
 #include "InputEventStatistics.h"
+#include "ThreadEventQueue.h"
 #include "ThreadEventTarget.h"
 #include "ThreadDelay.h"
 
 #ifdef XP_LINUX
 #  ifdef __GLIBC__
 #    include <gnu/libc-version.h>
 #  endif
 #  include <sys/mman.h>
@@ -1052,16 +1053,28 @@ nsThread::ProcessNextEvent(bool aMayWait
   // This only applys to the toplevel event loop! Nested event loops (e.g.
   // during sync dispatch) are waiting for some state change and must be able
   // to block even if something has requested shutdown of the thread. Otherwise
   // we'll just busywait as we endlessly look for an event, fail to find one,
   // and repeat the nested event loop since its state change hasn't happened
   // yet.
   bool reallyWait = aMayWait && (mNestedEventLoopDepth > 0 || !ShuttingDown());
 
+  if (mIsInLocalExecutionMode) {
+    EventQueuePriority priority;
+    if (const nsCOMPtr<nsIRunnable> event =
+            mEvents->GetEvent(reallyWait, &priority)) {
+      *aResult = true;
+      event->Run();
+    } else {
+      *aResult = false;
+    }
+    return NS_OK;
+  }
+
   if (IsMainThread()) {
     DoMainThreadSpecificProcessing(reallyWait);
   }
 
   ++mNestedEventLoopDepth;
 
   // We only want to create an AutoNoJSAPI on threads that actually do DOM stuff
   // (including workers).  Those are exactly the threads that have an
@@ -1389,8 +1402,31 @@ nsThread::GetEventTarget(nsIEventTarget*
   nsCOMPtr<nsIEventTarget> target = this;
   target.forget(aEventTarget);
   return NS_OK;
 }
 
 nsIEventTarget* nsThread::EventTarget() { return this; }
 
 nsISerialEventTarget* nsThread::SerialEventTarget() { return this; }
+
+nsLocalExecutionRecord nsThread::EnterLocalExecution() {
+  MOZ_RELEASE_ASSERT(!mIsInLocalExecutionMode);
+  MOZ_ASSERT(IsOnCurrentThread());
+  MOZ_ASSERT(EventQueue());
+  return nsLocalExecutionRecord(*EventQueue(), mIsInLocalExecutionMode);
+}
+
+nsLocalExecutionGuard::nsLocalExecutionGuard(
+    nsLocalExecutionRecord&& aLocalExecutionRecord)
+    : mEventQueueStack(aLocalExecutionRecord.mEventQueueStack),
+      mLocalEventTarget(mEventQueueStack.PushEventQueue()),
+      mLocalExecutionFlag(aLocalExecutionRecord.mLocalExecutionFlag) {
+  MOZ_ASSERT(mLocalEventTarget);
+  MOZ_ASSERT(!mLocalExecutionFlag);
+  mLocalExecutionFlag = true;
+}
+
+nsLocalExecutionGuard::~nsLocalExecutionGuard() {
+  MOZ_ASSERT(mLocalExecutionFlag);
+  mLocalExecutionFlag = false;
+  mEventQueueStack.PopEventQueue(mLocalEventTarget);
+}
--- a/xpcom/threads/nsThread.h
+++ b/xpcom/threads/nsThread.h
@@ -23,21 +23,25 @@
 #include "nsAutoPtr.h"
 #include "mozilla/AlreadyAddRefed.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/Array.h"
 #include "mozilla/dom/DocGroup.h"
 
 namespace mozilla {
 class CycleCollectedJSContext;
+class EventQueue;
+template <typename>
+class ThreadEventQueue;
 class ThreadEventTarget;
 }  // namespace mozilla
 
 using mozilla::NotNull;
 
+class nsLocalExecutionRecord;
 class nsThreadEnumerator;
 
 // See https://www.w3.org/TR/longtasks
 #define LONGTASK_BUSY_WINDOW_MS 50
 
 // A native thread
 class nsThread : public nsIThreadInternal,
                  public nsISupportsPriority,
@@ -136,16 +140,31 @@ class nsThread : public nsIThreadInterna
 
   static uint32_t MaxActiveThreads();
 
   const mozilla::TimeStamp& LastLongTaskEnd() { return mLastLongTaskEnd; }
   const mozilla::TimeStamp& LastLongNonIdleTaskEnd() {
     return mLastLongNonIdleTaskEnd;
   }
 
+  // When entering local execution mode a new event queue is created and used as
+  // an event source. This queue is only accessible through an
+  // nsLocalExecutionGuard constructed from the nsLocalExecutionRecord returned
+  // by this function, effectively restricting the events that get run while in
+  // local execution mode to those dispatched by the owner of the guard object.
+  //
+  // Local execution is not nestable. When the nsLocalExecutionGuard is
+  // destructed, the thread exits the local execution mode.
+  //
+  // Note that code run in local execution mode is not considered a task in the
+  // spec sense. Events from the local queue are considered part of the
+  // enclosing task and as such do not trigger profiling hooks, observer
+  // notifications, etc.
+  nsLocalExecutionRecord EnterLocalExecution();
+
  private:
   void DoMainThreadSpecificProcessing(bool aReallyWait);
 
  protected:
   friend class nsThreadShutdownEvent;
 
   friend class nsThreadEnumerator;
 
@@ -222,16 +241,62 @@ class nsThread : public nsIThreadInterna
 
   // Used to track which event is being executed by ProcessNextEvent
   nsCOMPtr<nsIRunnable> mCurrentEvent;
 
   mozilla::TimeStamp mCurrentEventStart;
   mozilla::TimeStamp mNextIdleDeadline;
 
   RefPtr<mozilla::PerformanceCounter> mCurrentPerformanceCounter;
+
+  bool mIsInLocalExecutionMode = false;
+};
+
+class nsLocalExecutionRecord;
+
+// This RAII class controls the duration of the associated nsThread's local
+// execution mode and provides access to the local event target. (See
+// nsThread::EnterLocalExecution() for details.) It is constructed from an
+// nsLocalExecutionRecord, which can only be constructed by nsThread.
+class MOZ_RAII nsLocalExecutionGuard final {
+ public:
+  MOZ_IMPLICIT nsLocalExecutionGuard(
+      nsLocalExecutionRecord&& aLocalExecutionRecord);
+  nsLocalExecutionGuard(const nsLocalExecutionGuard&) = delete;
+  nsLocalExecutionGuard(nsLocalExecutionGuard&&) = delete;
+  ~nsLocalExecutionGuard();
+
+  nsCOMPtr<nsISerialEventTarget> GetEventTarget() const {
+    return mLocalEventTarget;
+  }
+
+ private:
+  mozilla::SynchronizedEventQueue& mEventQueueStack;
+  nsCOMPtr<nsISerialEventTarget> mLocalEventTarget;
+  bool& mLocalExecutionFlag;
+};
+
+class MOZ_TEMPORARY_CLASS nsLocalExecutionRecord final {
+ private:
+  friend class nsThread;
+  friend class nsLocalExecutionGuard;
+
+  nsLocalExecutionRecord(mozilla::SynchronizedEventQueue& aEventQueueStack,
+                         bool& aLocalExecutionFlag)
+      : mEventQueueStack(aEventQueueStack),
+        mLocalExecutionFlag(aLocalExecutionFlag) {}
+
+  nsLocalExecutionRecord(nsLocalExecutionRecord&&) = default;
+
+ public:
+  nsLocalExecutionRecord(const nsLocalExecutionRecord&) = delete;
+
+ private:
+  mozilla::SynchronizedEventQueue& mEventQueueStack;
+  bool& mLocalExecutionFlag;
 };
 
 class MOZ_STACK_CLASS nsThreadEnumerator final {
  public:
   nsThreadEnumerator() : mMal(nsThread::ThreadListMutex()) {}
 
   auto begin() { return nsThread::ThreadList().begin(); }
   auto end() { return nsThread::ThreadList().end(); }