Bug 1236789. Avoid creating an unnecessary thread pool thread for tail-dispatch in TaskQueue. r=bholley
authorRobert O'Callahan <robert@ocallahan.org>
Tue, 05 Jan 2016 16:35:17 +1300
changeset 288710 024ce6807e881595694a78ae6c4213cee4a9cf94
parent 288709 0ed826f3d2775d8d5eacb76fc39703db3670c622
child 288711 a2575708048d15d18c068c88d2fb5a5a30093b08
push id30087
push usercbook@mozilla.com
push dateTue, 15 Mar 2016 09:43:43 +0000
treeherdermozilla-central@5e14887312d4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbholley
bugs1236789
milestone48.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;