bug 1306591, add secondary event queue to let high priority messages to be processed sooner, r=billm
authorOlli Pettay <Olli.Pettay@helsinki.fi>
Tue, 08 Nov 2016 14:05:45 +0200
changeset 348316 fab432069073857f66824c73353a6067fb493286
parent 348315 bd9dc9379305055245e0751095b6e6bdeb214b32
child 348317 5c6db81955887e4ad32094a51a0129870cb23016
push id10298
push userraliiev@mozilla.com
push dateMon, 14 Nov 2016 12:33:03 +0000
treeherdermozilla-aurora@7e29173b1641 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbillm
bugs1306591
milestone52.0a1
bug 1306591, add secondary event queue to let high priority messages to be processed sooner, r=billm
devtools/client/inspector/test/browser_inspector_highlighter-selector_01.js
devtools/client/inspector/test/browser_inspector_highlighter-selector_02.js
dom/svg/test/test_pathAnimInterpolation.xhtml
ipc/glue/MessageChannel.cpp
ipc/glue/MessageChannel.h
layout/ipc/PVsync.ipdl
xpcom/threads/ThrottledEventQueue.cpp
xpcom/threads/nsEventQueue.cpp
xpcom/threads/nsEventQueue.h
xpcom/threads/nsIRunnable.idl
xpcom/threads/nsThread.cpp
xpcom/threads/nsThread.h
xpcom/threads/nsThreadPool.cpp
xpcom/threads/nsThreadPool.h
--- a/devtools/client/inspector/test/browser_inspector_highlighter-selector_01.js
+++ b/devtools/client/inspector/test/browser_inspector_highlighter-selector_01.js
@@ -32,16 +32,18 @@ const TEST_DATA = [{
 }, {
   selector: ".item",
   containerCount: 5
 }, {
   selector: "#test-node, ul, .item",
   containerCount: 7
 }];
 
+requestLongerTimeout(5);
+
 add_task(function* () {
   let {inspector, testActor} = yield openInspectorForURL(TEST_URL);
   let front = inspector.inspector;
   let highlighter = yield front.getHighlighterByType("SelectorHighlighter");
 
   let contextNode = yield getNodeFront("body", inspector);
 
   for (let {selector, containerCount} of TEST_DATA) {
--- a/devtools/client/inspector/test/browser_inspector_highlighter-selector_02.js
+++ b/devtools/client/inspector/test/browser_inspector_highlighter-selector_02.js
@@ -25,16 +25,18 @@ const TEST_DATA = [{
   selector: ".root-level-node",
   containerCount: 0
 }, {
   inIframe: true,
   selector: ".sub-level-node",
   containerCount: 1
 }];
 
+requestLongerTimeout(5);
+
 add_task(function* () {
   let {inspector, testActor} = yield openInspectorForURL(TEST_URL);
   let front = inspector.inspector;
   let highlighter = yield front.getHighlighterByType("SelectorHighlighter");
 
   for (let {inIframe, selector, containerCount} of TEST_DATA) {
     info("Showing the highlighter on " + selector + ". Expecting " +
       containerCount + " highlighter containers");
--- a/dom/svg/test/test_pathAnimInterpolation.xhtml
+++ b/dom/svg/test/test_pathAnimInterpolation.xhtml
@@ -330,15 +330,19 @@ function run()
     }
 
     is(seg.pathSegTypeAsLetter + actual, test.expectedType + test.expected,
        "Path segment for interpolation " +
          (test.usesAddition ? "with addition " : "") +
          " from " + test.from + " to " + test.to);
   }
 
+  // Clear all the tests. We have tons of them attached to the DOM and refresh driver tick will
+  // go through them all by calling animation controller.
+  gSVG.remove();
+
   SimpleTest.finish();
 }
 
 window.addEventListener("load", run, false);
 ]]></script>
 </body>
 </html>
--- a/ipc/glue/MessageChannel.cpp
+++ b/ipc/glue/MessageChannel.cpp
@@ -1539,17 +1539,20 @@ MessageChannel::RunMessage(MessageTask& 
     }
 
     // Check that we're going to run the first message that's valid to run.
 #ifdef DEBUG
     for (RefPtr<MessageTask> task : mPending) {
         if (task == &aTask) {
             break;
         }
-        MOZ_ASSERT(!ShouldRunMessage(task->Msg()));
+
+        MOZ_ASSERT(!ShouldRunMessage(task->Msg()) ||
+                   aTask.Msg().priority() != task->Msg().priority());
+
     }
 #endif
 
     if (!mDeferred.empty()) {
         MaybeUndeferIncall();
     }
 
     if (!ShouldRunMessage(msg)) {
@@ -1564,16 +1567,18 @@ MessageChannel::RunMessage(MessageTask& 
         // Interrupt call sent before entering that loop.
         mOutOfTurnReplies[msg.seqno()] = Move(msg);
         return;
     }
 
     DispatchMessage(Move(msg));
 }
 
+NS_IMPL_ISUPPORTS_INHERITED(MessageChannel::MessageTask, CancelableRunnable, nsIRunnablePriority)
+
 nsresult
 MessageChannel::MessageTask::Run()
 {
     if (!mChannel) {
         return NS_OK;
     }
 
     mChannel->AssertWorkerThread();
@@ -1629,16 +1634,24 @@ MessageChannel::MessageTask::Post()
 void
 MessageChannel::MessageTask::Clear()
 {
     mChannel->AssertWorkerThread();
 
     mChannel = nullptr;
 }
 
+NS_IMETHODIMP
+MessageChannel::MessageTask::GetPriority(uint32_t* aPriority)
+{
+  *aPriority = mMessage.priority() == Message::HIGH_PRIORITY ?
+               PRIORITY_HIGH : PRIORITY_NORMAL;
+  return NS_OK;
+}
+
 void
 MessageChannel::DispatchMessage(Message &&aMsg)
 {
     AssertWorkerThread();
     mMonitor->AssertCurrentThreadOwns();
 
     Maybe<AutoNoJSAPI> nojsapi;
     if (ScriptSettingsInitialized() && NS_IsMainThread())
--- a/ipc/glue/MessageChannel.h
+++ b/ipc/glue/MessageChannel.h
@@ -449,36 +449,41 @@ class MessageChannel : HasResultCodes
     {
         MOZ_RELEASE_ASSERT(mWorkerLoopID != MessageLoop::current()->id(),
                            "on worker thread but should not be!");
     }
 
   private:
     class MessageTask :
         public CancelableRunnable,
-        public LinkedListElement<RefPtr<MessageTask>>
+        public LinkedListElement<RefPtr<MessageTask>>,
+        public nsIRunnablePriority
     {
     public:
         explicit MessageTask(MessageChannel* aChannel, Message&& aMessage)
           : mChannel(aChannel), mMessage(Move(aMessage)), mScheduled(false)
         {}
 
+        NS_DECL_ISUPPORTS_INHERITED
+
         NS_IMETHOD Run() override;
         nsresult Cancel() override;
+        NS_IMETHOD GetPriority(uint32_t* aPriority) override;
         void Post();
         void Clear();
 
         bool IsScheduled() const { return mScheduled; }
 
         Message& Msg() { return mMessage; }
         const Message& Msg() const { return mMessage; }
 
     private:
         MessageTask() = delete;
         MessageTask(const MessageTask&) = delete;
+        ~MessageTask() {}
 
         MessageChannel* mChannel;
         Message mMessage;
         bool mScheduled : 1;
     };
 
     bool ShouldRunMessage(const Message& aMsg);
     void RunMessage(MessageTask& aTask);
--- a/layout/ipc/PVsync.ipdl
+++ b/layout/ipc/PVsync.ipdl
@@ -16,17 +16,17 @@ namespace layout {
  * interfaces for content to observe/unobserve vsync event notifications.
  */
 async protocol PVsync
 {
   manager PBackground;
 
 child:
   // Send vsync event from chrome to content process.
-  async Notify(TimeStamp aVsyncTimestamp) compress;
+  prio(high) async Notify(TimeStamp aVsyncTimestamp) compress;
 
   // Send the vsync rate to the content process.
   async VsyncRate(float aVsyncRate);
 
 parent:
   // Content process use these messages to acquire the vsync event.
   async Observe();
   async Unobserve();
--- a/xpcom/threads/ThrottledEventQueue.cpp
+++ b/xpcom/threads/ThrottledEventQueue.cpp
@@ -74,16 +74,18 @@ class ThrottledEventQueue::Inner final :
       mInner->ExecuteRunnable();
       return NS_OK;
     }
   };
 
   mutable Mutex mMutex;
   mutable CondVar mIdleCondVar;
 
+  mozilla::CondVar mEventsAvailable;
+
   // any thread, protected by mutex
   nsEventQueue mEventQueue;
 
   // written on main thread, read on any thread
   nsCOMPtr<nsIEventTarget> mBaseTarget;
 
   // any thread, protected by mutex
   nsCOMPtr<nsIRunnable> mExecutor;
@@ -92,17 +94,18 @@ class ThrottledEventQueue::Inner final :
   Atomic<uint32_t> mExecutionDepth;
 
   // any thread, protected by mutex
   bool mShutdownStarted;
 
   explicit Inner(nsIEventTarget* aBaseTarget)
     : mMutex("ThrottledEventQueue")
     , mIdleCondVar(mMutex, "ThrottledEventQueue:Idle")
-    , mEventQueue(mMutex)
+    , mEventsAvailable(mMutex, "[ThrottledEventQueue::Inner.mEventsAvailable]")
+    , mEventQueue(mEventsAvailable, nsEventQueue::eNormalQueue)
     , mBaseTarget(aBaseTarget)
     , mExecutionDepth(0)
     , mShutdownStarted(false)
   {
   }
 
   ~Inner()
   {
--- a/xpcom/threads/nsEventQueue.cpp
+++ b/xpcom/threads/nsEventQueue.cpp
@@ -14,22 +14,23 @@
 using namespace mozilla;
 
 static LazyLogModule sEventQueueLog("nsEventQueue");
 #ifdef LOG
 #undef LOG
 #endif
 #define LOG(args) MOZ_LOG(sEventQueueLog, mozilla::LogLevel::Debug, args)
 
-nsEventQueue::nsEventQueue(Mutex& aLock)
+nsEventQueue::nsEventQueue(mozilla::CondVar& aCondVar, EventQueueType aType)
   : mHead(nullptr)
   , mTail(nullptr)
   , mOffsetHead(0)
   , mOffsetTail(0)
-  , mEventsAvailable(aLock, "[nsEventQueue.mEventsAvailable]")
+  , mEventsAvailable(aCondVar)
+  , mType(aType)
 {
 }
 
 nsEventQueue::~nsEventQueue()
 {
   // It'd be nice to be able to assert that no one else is holding the lock,
   // but NSPR doesn't really expose APIs for it.
   NS_ASSERTION(IsEmpty(),
@@ -39,26 +40,34 @@ nsEventQueue::~nsEventQueue()
     FreePage(mHead);
   }
 }
 
 bool
 nsEventQueue::GetEvent(bool aMayWait, nsIRunnable** aResult,
                        MutexAutoLock& aProofOfLock)
 {
+  if (aResult) {
+    *aResult = nullptr;
+  }
+
   while (IsEmpty()) {
     if (!aMayWait) {
-      if (aResult) {
-        *aResult = nullptr;
-      }
       return false;
     }
     LOG(("EVENTQ(%p): wait begin\n", this));
     mEventsAvailable.Wait();
     LOG(("EVENTQ(%p): wait end\n", this));
+
+    if (mType == eSharedCondVarQueue) {
+      if (IsEmpty()) {
+        return false;
+      }
+      break;
+    }
   }
 
   if (aResult) {
     MOZ_ASSERT(mOffsetHead < EVENTS_PER_PAGE);
     MOZ_ASSERT_IF(mHead == mTail, mOffsetHead <= mOffsetTail);
     *aResult = mHead->mEvents[mOffsetHead++];
 
     MOZ_ASSERT(*aResult);
--- a/xpcom/threads/nsEventQueue.h
+++ b/xpcom/threads/nsEventQueue.h
@@ -8,26 +8,33 @@
 #define nsEventQueue_h__
 
 #include <stdlib.h>
 #include "mozilla/CondVar.h"
 #include "mozilla/Mutex.h"
 #include "nsIRunnable.h"
 #include "nsCOMPtr.h"
 #include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/UniquePtr.h"
 
 class nsThreadPool;
 
 // A threadsafe FIFO event queue...
 class nsEventQueue
 {
 public:
   typedef mozilla::MutexAutoLock MutexAutoLock;
 
-  explicit nsEventQueue(mozilla::Mutex& aLock);
+  enum EventQueueType
+  {
+    eNormalQueue,
+    eSharedCondVarQueue
+  };
+
+  nsEventQueue(mozilla::CondVar& aCondVar, EventQueueType aType);
   ~nsEventQueue();
 
   // This method adds a new event to the pending event queue.  The queue holds
   // a strong reference to the event after this method returns.  This method
   // cannot fail.
   void PutEvent(nsIRunnable* aEvent, MutexAutoLock& aProofOfLock);
   void PutEvent(already_AddRefed<nsIRunnable>&& aEvent,
                 MutexAutoLock& aProofOfLock);
@@ -87,26 +94,30 @@ private:
     free(aPage);
   }
 
   Page* mHead;
   Page* mTail;
 
   uint16_t mOffsetHead;  // offset into mHead where next item is removed
   uint16_t mOffsetTail;  // offset into mTail where next item is added
-  mozilla::CondVar mEventsAvailable;
+  mozilla::CondVar& mEventsAvailable;
+
+  EventQueueType mType;
 
   // These methods are made available to nsThreadPool as a hack, since
   // nsThreadPool needs to have its threads sleep for fixed amounts of
   // time as well as being able to wake up all threads when thread
   // limits change.
   friend class nsThreadPool;
   void Wait(PRIntervalTime aInterval)
   {
+    MOZ_ASSERT(mType == eNormalQueue);
     mEventsAvailable.Wait(aInterval);
   }
   void NotifyAll()
   {
+    MOZ_ASSERT(mType == eNormalQueue);
     mEventsAvailable.NotifyAll();
   }
 };
 
 #endif  // nsEventQueue_h__
--- a/xpcom/threads/nsIRunnable.idl
+++ b/xpcom/threads/nsIRunnable.idl
@@ -12,8 +12,16 @@
 [scriptable, function, uuid(4a2abaf0-6886-11d3-9382-00104ba0fd40)]
 interface nsIRunnable : nsISupports
 {
     /**
      * The function implementing the task to be run.
      */
     void run();
 };
+
+[uuid(e75aa42a-80a9-11e6-afb5-e89d87348e2c)]
+interface nsIRunnablePriority : nsISupports
+{
+    const unsigned short PRIORITY_NORMAL = 0;
+    const unsigned short PRIORITY_HIGH = 1;
+    readonly attribute unsigned long priority;
+};
--- a/xpcom/threads/nsThread.cpp
+++ b/xpcom/threads/nsThread.cpp
@@ -591,17 +591,18 @@ nsThread::SaveMemoryReportNearOOM(Should
 int sCanaryOutputFD = -1;
 #endif
 
 nsThread::nsThread(MainThreadFlag aMainThread, uint32_t aStackSize)
   : mLock("nsThread.mLock")
   , mScriptObserver(nullptr)
   , mEvents(WrapNotNull(&mEventsRoot))
   , mEventsRoot(mLock)
-  , mIdleEvents(mLock)
+  , mIdleEventsAvailable(mLock, "[nsThread.mEventsAvailable]")
+  , mIdleEvents(mIdleEventsAvailable, nsEventQueue::eNormalQueue)
   , mPriority(PRIORITY_NORMAL)
   , mThread(nullptr)
   , mNestedEventLoopDepth(0)
   , mStackSize(aStackSize)
   , mShutdownContext(nullptr)
   , mShutdownRequired(false)
   , mEventsAreDoomed(false)
   , mIsMainThread(aMainThread)
@@ -762,16 +763,50 @@ nsThread::DispatchInternal(already_AddRe
     return NS_OK;
   }
 
   NS_ASSERTION(aFlags == NS_DISPATCH_NORMAL ||
                aFlags == NS_DISPATCH_AT_END, "unexpected dispatch flags");
   return PutEvent(event.take(), aTarget);
 }
 
+bool
+nsThread::nsChainedEventQueue::GetEvent(bool aMayWait, nsIRunnable** aEvent,
+                                        mozilla::MutexAutoLock& aProofOfLock)
+{
+  bool retVal = false;
+  do {
+    if (mProcessSecondaryQueueRunnable) {
+      MOZ_ASSERT(mSecondaryQueue->HasPendingEvent(aProofOfLock));
+      retVal = mSecondaryQueue->GetEvent(aMayWait, aEvent, aProofOfLock);
+      MOZ_ASSERT(*aEvent);
+      mProcessSecondaryQueueRunnable = false;
+      return retVal;
+    }
+
+    // We don't want to wait if mSecondaryQueue has some events.
+    bool reallyMayWait =
+      aMayWait && !mSecondaryQueue->HasPendingEvent(aProofOfLock);
+    retVal =
+      mNormalQueue->GetEvent(reallyMayWait, aEvent, aProofOfLock);
+
+    // Let's see if we should next time process an event from the secondary
+    // queue.
+    mProcessSecondaryQueueRunnable =
+      mSecondaryQueue->HasPendingEvent(aProofOfLock);
+
+    if (*aEvent) {
+      // We got an event, return early.
+      return retVal;
+    }
+  } while(aMayWait || mProcessSecondaryQueueRunnable);
+
+  return retVal;
+}
+
 //-----------------------------------------------------------------------------
 // nsIEventTarget
 
 NS_IMETHODIMP
 nsThread::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags)
 {
   nsCOMPtr<nsIRunnable> event(aEvent);
   return Dispatch(event.forget(), aFlags);
--- a/xpcom/threads/nsThread.h
+++ b/xpcom/threads/nsThread.h
@@ -14,16 +14,17 @@
 #include "nsEventQueue.h"
 #include "nsThreadUtils.h"
 #include "nsString.h"
 #include "nsTObserverArray.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/NotNull.h"
 #include "nsAutoPtr.h"
 #include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/UniquePtr.h"
 
 namespace mozilla {
 class CycleCollectedJSContext;
 }
 
 using mozilla::NotNull;
 
 // A native thread
@@ -135,47 +136,75 @@ protected:
   struct nsThreadShutdownContext* ShutdownInternal(bool aSync);
 
   // Wrapper for nsEventQueue that supports chaining.
   class nsChainedEventQueue
   {
   public:
     explicit nsChainedEventQueue(mozilla::Mutex& aLock)
       : mNext(nullptr)
-      , mQueue(aLock)
+      , mEventsAvailable(aLock, "[nsChainedEventQueue.mEventsAvailable]")
+      , mProcessSecondaryQueueRunnable(false)
     {
+      mNormalQueue =
+        mozilla::MakeUnique<nsEventQueue>(mEventsAvailable,
+                                          nsEventQueue::eSharedCondVarQueue);
+      // Both queues need to use the same CondVar!
+      mSecondaryQueue =
+        mozilla::MakeUnique<nsEventQueue>(mEventsAvailable,
+                                          nsEventQueue::eSharedCondVarQueue);
     }
 
     bool GetEvent(bool aMayWait, nsIRunnable** aEvent,
-                  mozilla::MutexAutoLock& aProofOfLock)
-    {
-      return mQueue.GetEvent(aMayWait, aEvent, aProofOfLock);
-    }
+                  mozilla::MutexAutoLock& aProofOfLock);
 
     void PutEvent(nsIRunnable* aEvent, mozilla::MutexAutoLock& aProofOfLock)
     {
-      mQueue.PutEvent(aEvent, aProofOfLock);
+      RefPtr<nsIRunnable> event(aEvent);
+      PutEvent(event.forget(), aProofOfLock);
     }
 
     void PutEvent(already_AddRefed<nsIRunnable> aEvent,
                   mozilla::MutexAutoLock& aProofOfLock)
     {
-      mQueue.PutEvent(mozilla::Move(aEvent), aProofOfLock);
+      RefPtr<nsIRunnable> event(aEvent);
+      nsCOMPtr<nsIRunnablePriority> runnablePrio =
+        do_QueryInterface(event);
+      uint32_t prio = nsIRunnablePriority::PRIORITY_NORMAL;
+      if (runnablePrio) {
+        runnablePrio->GetPriority(&prio);
+      }
+      MOZ_ASSERT(prio == nsIRunnablePriority::PRIORITY_NORMAL ||
+                 prio == nsIRunnablePriority::PRIORITY_HIGH);
+      if (prio == nsIRunnablePriority::PRIORITY_NORMAL) {
+        mNormalQueue->PutEvent(event.forget(), aProofOfLock);
+      } else {
+        mSecondaryQueue->PutEvent(event.forget(), aProofOfLock);
+      }
     }
 
     bool HasPendingEvent(mozilla::MutexAutoLock& aProofOfLock)
     {
-      return mQueue.HasPendingEvent(aProofOfLock);
+      return mNormalQueue->HasPendingEvent(aProofOfLock) ||
+             mSecondaryQueue->HasPendingEvent(aProofOfLock);
     }
 
     nsChainedEventQueue* mNext;
     RefPtr<nsNestedEventTarget> mEventTarget;
 
   private:
-    nsEventQueue mQueue;
+    mozilla::CondVar mEventsAvailable;
+    mozilla::UniquePtr<nsEventQueue> mNormalQueue;
+    mozilla::UniquePtr<nsEventQueue> mSecondaryQueue;
+
+    // Try to process one high priority runnable after each normal
+    // priority runnable. This gives the processing model HTML spec has for
+    // 'Update the rendering' in the case only vsync messages are in the
+    // secondary queue and prevents starving the normal queue.
+    bool mProcessSecondaryQueueRunnable;
   };
 
   class nsNestedEventTarget final : public nsIEventTarget
   {
   public:
     NS_DECL_THREADSAFE_ISUPPORTS
     NS_DECL_NSIEVENTTARGET
 
@@ -218,16 +247,17 @@ protected:
   NotNull<nsChainedEventQueue*> mEvents;  // never null
   nsChainedEventQueue mEventsRoot;
 
   // mIdlePeriod keeps track of the current idle period. If at any
   // time the main event queue is empty, calling
   // mIdlePeriod->GetIdlePeriodHint() will give an estimate of when
   // the current idle period will end.
   nsCOMPtr<nsIIdlePeriod> mIdlePeriod;
+  mozilla::CondVar mIdleEventsAvailable;
   nsEventQueue mIdleEvents;
 
   int32_t   mPriority;
   PRThread* mThread;
   uint32_t  mNestedEventLoopDepth;
   uint32_t  mStackSize;
 
   // The shutdown context for ourselves.
--- a/xpcom/threads/nsThreadPool.cpp
+++ b/xpcom/threads/nsThreadPool.cpp
@@ -37,17 +37,18 @@ NS_IMPL_RELEASE(nsThreadPool)
 NS_IMPL_CLASSINFO(nsThreadPool, nullptr, nsIClassInfo::THREADSAFE,
                   NS_THREADPOOL_CID)
 NS_IMPL_QUERY_INTERFACE_CI(nsThreadPool, nsIThreadPool, nsIEventTarget,
                            nsIRunnable)
 NS_IMPL_CI_INTERFACE_GETTER(nsThreadPool, nsIThreadPool, nsIEventTarget)
 
 nsThreadPool::nsThreadPool()
   : mMutex("[nsThreadPool.mMutex]")
-  , mEvents(mMutex)
+  , mEventsAvailable(mMutex, "[nsThreadPool.mEventsAvailable]")
+  , mEvents(mEventsAvailable, nsEventQueue::eNormalQueue)
   , mThreadLimit(DEFAULT_THREAD_LIMIT)
   , mIdleThreadLimit(DEFAULT_IDLE_THREAD_LIMIT)
   , mIdleThreadTimeout(DEFAULT_IDLE_THREAD_TIMEOUT)
   , mIdleCount(0)
   , mStackSize(nsIThreadManager::DEFAULT_STACK_SIZE)
   , mShutdown(false)
 {
   LOG(("THRD-P(%p) constructor!!!\n", this));
--- a/xpcom/threads/nsThreadPool.h
+++ b/xpcom/threads/nsThreadPool.h
@@ -36,16 +36,17 @@ private:
   ~nsThreadPool();
 
   void ShutdownThread(nsIThread* aThread);
   nsresult PutEvent(nsIRunnable* aEvent);
   nsresult PutEvent(already_AddRefed<nsIRunnable> aEvent, uint32_t aFlags);
 
   nsCOMArray<nsIThread> mThreads;
   mozilla::Mutex        mMutex;
+  mozilla::CondVar      mEventsAvailable;
   nsEventQueue          mEvents;
   uint32_t              mThreadLimit;
   uint32_t              mIdleThreadLimit;
   uint32_t              mIdleThreadTimeout;
   uint32_t              mIdleCount;
   uint32_t              mStackSize;
   nsCOMPtr<nsIThreadPoolListener> mListener;
   bool                  mShutdown;