Bug 1236789. Avoid creating an unnecessary thread pool thread for tail-dispatch in TaskQueue. r=bholley
☠☠ backed out by 1f9dc51c1493 ☠ ☠
authorRobert O'Callahan <robert@ocallahan.org>
Tue, 05 Jan 2016 16:35:17 +1300
changeset 285458 b5ec7338bddf9f1147e19c1d4d1b90a0cdb8da9a
parent 285457 2b0aa1cffeeaabb524b9d2936321af18c7445fbc
child 285459 4e56796fa59b7be8d0ebc717f0a6db410cb3a0fa
push id30030
push usercbook@mozilla.com
push dateThu, 25 Feb 2016 10:58:04 +0000
treeherdermozilla-central@c1e0d1890cfe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbholley
bugs1236789
milestone47.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 1236789. Avoid creating an unnecessary thread pool thread for tail-dispatch in TaskQueue. r=bholley MozReview-Commit-ID: H1rhQPBU00L
xpcom/threads/SharedThreadPool.h
xpcom/threads/TaskQueue.cpp
xpcom/threads/nsIEventTarget.idl
xpcom/threads/nsThreadPool.cpp
xpcom/threads/nsThreadPool.h
--- a/xpcom/threads/SharedThreadPool.h
+++ b/xpcom/threads/SharedThreadPool.h
@@ -50,16 +50,21 @@ public:
   // in a tight loop.
   NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override;
   NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override;
   NS_IMETHOD_(MozExternalRefCountType) Release(void) override;
 
   // Forward behaviour to wrapped thread pool implementation.
   NS_FORWARD_SAFE_NSITHREADPOOL(mPool);
 
+  // Call this when dispatching from an event on the same
+  // threadpool that is about to complete. We should not create a new thread
+  // in that case since a thread is about to become idle.
+  nsresult TailDispatch(nsIRunnable *event) { return Dispatch(event, NS_DISPATCH_TAIL); }
+
   NS_IMETHOD DispatchFromScript(nsIRunnable *event, uint32_t flags) override {
       return Dispatch(event, flags);
   }
 
   NS_IMETHOD Dispatch(already_AddRefed<nsIRunnable>&& event, uint32_t flags) override
     { return !mEventTarget ? NS_ERROR_NULL_POINTER : mEventTarget->Dispatch(Move(event), flags); }
 
   using nsIEventTarget::Dispatch;
--- a/xpcom/threads/TaskQueue.cpp
+++ b/xpcom/threads/TaskQueue.cpp
@@ -188,17 +188,17 @@ TaskQueue::Runner::Run()
     }
   }
 
   // There's at least one more event that we can run. Dispatch this Runner
   // to the thread pool again to ensure it runs again. Note that we don't just
   // run in a loop here so that we don't hog the thread pool. This means we may
   // run on another thread next time, but we rely on the memory fences from
   // mQueueMonitor for thread safety of non-threadsafe tasks.
-  nsresult rv = mQueue->mPool->Dispatch(this, NS_DISPATCH_NORMAL);
+  nsresult rv = mQueue->mPool->TailDispatch(this);
   if (NS_FAILED(rv)) {
     // Failed to dispatch, shutdown!
     MonitorAutoLock mon(mQueue->mQueueMonitor);
     mQueue->mIsRunning = false;
     mQueue->mIsShutdown = true;
     mQueue->MaybeResolveShutdown();
     mon.NotifyAll();
   }
--- a/xpcom/threads/nsIEventTarget.idl
+++ b/xpcom/threads/nsIEventTarget.idl
@@ -36,16 +36,28 @@ interface nsIEventTarget : nsISupports
    *
    * NOTE: passing this flag to dispatch may have the side-effect of causing
    * other events on the current thread to be processed while waiting for the
    * given event to be processed.
    */
   const unsigned long DISPATCH_SYNC = 1;
 
   /**
+   * This flag specifies that the dispatch is occurring from a running event
+   * that was dispatched to the same event target, and that event is about to
+   * finish.
+   *
+   * A thread pool can use this as an optimization hint to not spin up
+   * another thread, since the current thread is about to become idle.
+   *
+   * These events are always async.
+   */
+  const unsigned long DISPATCH_TAIL = 2;
+
+  /**
    * Check to see if this event target is associated with the current thread.
    *
    * @returns
    *   A boolean value that if "true" indicates that events dispatched to this
    *   event target will run on the current thread (i.e., the thread calling
    *   this method).
    */
   boolean isOnCurrentThread();
@@ -86,9 +98,10 @@ interface nsIEventTarget : nsISupports
    */
   [binaryname(DispatchFromScript)] void dispatch(in nsIRunnable event, in unsigned long flags);
 };
 
 %{C++
 // convenient aliases:
 #define NS_DISPATCH_NORMAL nsIEventTarget::DISPATCH_NORMAL
 #define NS_DISPATCH_SYNC   nsIEventTarget::DISPATCH_SYNC
+#define NS_DISPATCH_TAIL   nsIEventTarget::DISPATCH_TAIL
 %}
--- a/xpcom/threads/nsThreadPool.cpp
+++ b/xpcom/threads/nsThreadPool.cpp
@@ -59,21 +59,21 @@ nsThreadPool::~nsThreadPool()
   // after removing themselves from mThreads.
   MOZ_ASSERT(mThreads.IsEmpty());
 }
 
 nsresult
 nsThreadPool::PutEvent(nsIRunnable* aEvent)
 {
   nsCOMPtr<nsIRunnable> event(aEvent);
-  return PutEvent(event.forget());
+  return PutEvent(event.forget(), 0);
 }
 
 nsresult
-nsThreadPool::PutEvent(already_AddRefed<nsIRunnable>&& aEvent)
+nsThreadPool::PutEvent(already_AddRefed<nsIRunnable>&& aEvent, uint32_t aFlags)
 {
   // Avoid spawning a new thread while holding the event queue lock...
 
   bool spawnThread = false;
   uint32_t stackSize = 0;
   {
     MutexAutoLock lock(mMutex);
 
@@ -81,16 +81,17 @@ nsThreadPool::PutEvent(already_AddRefed<
       return NS_ERROR_NOT_AVAILABLE;
     }
     LOG(("THRD-P(%p) put [%d %d %d]\n", this, mIdleCount, mThreads.Count(),
          mThreadLimit));
     MOZ_ASSERT(mIdleCount <= (uint32_t)mThreads.Count(), "oops");
 
     // Make sure we have a thread to service this event.
     if (mThreads.Count() < (int32_t)mThreadLimit &&
+        !(aFlags & NS_DISPATCH_TAIL) &&
         // Spawn a new thread if we don't have enough idle threads to serve
         // pending events immediately.
         mEvents.Count(lock) >= mIdleCount) {
       spawnThread = true;
     }
 
     mEvents.PutEvent(Move(aEvent), lock);
     stackSize = mStackSize;
@@ -267,17 +268,17 @@ nsThreadPool::Dispatch(already_AddRefed<
       new nsThreadSyncDispatch(thread, Move(aEvent));
     PutEvent(wrapper);
 
     while (wrapper->IsPending()) {
       NS_ProcessNextEvent(thread);
     }
   } else {
     NS_ASSERTION(aFlags == NS_DISPATCH_NORMAL, "unexpected dispatch flags");
-    PutEvent(Move(aEvent));
+    PutEvent(Move(aEvent), aFlags);
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsThreadPool::IsOnCurrentThread(bool* aResult)
 {
   MutexAutoLock lock(mMutex);
--- a/xpcom/threads/nsThreadPool.h
+++ b/xpcom/threads/nsThreadPool.h
@@ -32,17 +32,17 @@ public:
 
   nsThreadPool();
 
 private:
   ~nsThreadPool();
 
   void ShutdownThread(nsIThread* aThread);
   nsresult PutEvent(nsIRunnable* aEvent);
-  nsresult PutEvent(already_AddRefed<nsIRunnable>&& aEvent);
+  nsresult PutEvent(already_AddRefed<nsIRunnable>&& aEvent, uint32_t aFlags);
 
   nsCOMArray<nsIThread> mThreads;
   mozilla::Mutex        mMutex;
   nsEventQueue          mEvents;
   uint32_t              mThreadLimit;
   uint32_t              mIdleThreadLimit;
   uint32_t              mIdleThreadTimeout;
   uint32_t              mIdleCount;