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 321623 fab432069073857f66824c73353a6067fb493286
parent 321622 bd9dc9379305055245e0751095b6e6bdeb214b32
child 321624 5c6db81955887e4ad32094a51a0129870cb23016
push id30931
push userkwierso@gmail.com
push dateTue, 08 Nov 2016 21:58:36 +0000
treeherdermozilla-central@783356f1476e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbillm
bugs1306591
milestone52.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 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;