Bug 1083101 - Win32 implementation of the JobScheduler. r=jrmuizel
authorNicolas Silva <nsilva@mozilla.com>
Mon, 28 Sep 2015 13:49:52 +0200
changeset 264733 6a9e2e39539c9a95dda3a062a1892a6ea8df462f
parent 264732 c152e94eb0ed0bb20ed4c9b794164bcb416c340c
child 264734 8d2b068f331de7d85772a030e4584528eaa868ae
push id29446
push userkwierso@gmail.com
push dateMon, 28 Sep 2015 22:36:46 +0000
treeherdermozilla-central@c7db5b605dd4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjrmuizel
bugs1083101
milestone44.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 1083101 - Win32 implementation of the JobScheduler. r=jrmuizel
gfx/2d/JobScheduler.cpp
gfx/2d/JobScheduler.h
gfx/2d/JobScheduler_posix.cpp
gfx/2d/JobScheduler_posix.h
gfx/2d/JobScheduler_win32.cpp
gfx/2d/JobScheduler_win32.h
gfx/2d/moz.build
gfx/tests/gtest/TestJobScheduler.cpp
--- a/gfx/2d/JobScheduler.cpp
+++ b/gfx/2d/JobScheduler.cpp
@@ -18,17 +18,17 @@ bool JobScheduler::Init(uint32_t aNumThr
   sSingleton = new JobScheduler();
   sSingleton->mNextQueue = 0;
 
   for (uint32_t i = 0; i < aNumQueues; ++i) {
     sSingleton->mDrawingQueues.push_back(new MultiThreadedJobQueue());
   }
 
   for (uint32_t i = 0; i < aNumThreads; ++i) {
-    sSingleton->mWorkerThreads.push_back(new WorkerThread(sSingleton->mDrawingQueues[i%aNumQueues]));
+    sSingleton->mWorkerThreads.push_back(WorkerThread::Create(sSingleton->mDrawingQueues[i%aNumQueues]));
   }
   return true;
 }
 
 void JobScheduler::ShutDown()
 {
   MOZ_ASSERT(IsEnabled());
   if (!IsEnabled()) {
@@ -227,10 +227,38 @@ SyncObject::AddPrerequisite(Job* aJob)
   MOZ_ASSERT(++mAddedPrerequisites <= mNumPrerequisites);
 }
 
 void
 SyncObject::AddSubsequent(Job* aJob)
 {
 }
 
+WorkerThread::WorkerThread(MultiThreadedJobQueue* aJobQueue)
+: mQueue(aJobQueue)
+{
+  aJobQueue->RegisterThread();
+}
+
+void
+WorkerThread::Run()
+{
+  SetName("gfx worker");
+
+  for (;;) {
+    Job* commands = nullptr;
+    if (!mQueue->WaitForJob(commands)) {
+      mQueue->UnregisterThread();
+      return;
+    }
+
+    JobStatus status = JobScheduler::ProcessJob(commands);
+
+    if (status == JobStatus::Error) {
+      // Don't try to handle errors for now, but that's open to discussions.
+      // I expect errors to be mostly OOM issues.
+      MOZ_CRASH();
+    }
+  }
+}
+
 } //namespace
 } //namespace
--- a/gfx/2d/JobScheduler.h
+++ b/gfx/2d/JobScheduler.h
@@ -10,21 +10,24 @@
 #include "mozilla/gfx/Types.h"
 
 #ifdef WIN32
 #include "mozilla/gfx/JobScheduler_win32.h"
 #else
 #include "mozilla/gfx/JobScheduler_posix.h"
 #endif
 
+#include <vector>
+
 namespace mozilla {
 namespace gfx {
 
 class MultiThreadedJobQueue;
 class SyncObject;
+class WorkerThread;
 
 class JobScheduler {
 public:
   /// Return one of the queues that the drawing worker threads pull from, chosen
   /// pseudo-randomly.
   static MultiThreadedJobQueue* GetDrawingQueue()
   {
     return sSingleton->mDrawingQueues[
@@ -219,13 +222,32 @@ private:
 /// RAII helper.
 struct MutexAutoLock {
     MutexAutoLock(Mutex* aMutex) : mMutex(aMutex) { mMutex->Lock(); }
     ~MutexAutoLock() { mMutex->Unlock(); }
 protected:
     Mutex* mMutex;
 };
 
+/// Base class for worker threads.
+class WorkerThread
+{
+public:
+  static WorkerThread* Create(MultiThreadedJobQueue* aJobQueue);
+
+  virtual ~WorkerThread() {}
+
+  void Run();
+
+  MultiThreadedJobQueue* GetJobQueue() { return mQueue; }
+
+protected:
+  explicit WorkerThread(MultiThreadedJobQueue* aJobQueue);
+
+  virtual void SetName(const char* aName) {}
+
+  MultiThreadedJobQueue* mQueue;
+};
 
 } // namespace
 } // namespace
 
 #endif
\ No newline at end of file
--- a/gfx/2d/JobScheduler_posix.cpp
+++ b/gfx/2d/JobScheduler_posix.cpp
@@ -6,16 +6,62 @@
 #include "JobScheduler.h"
 #include "mozilla/gfx/Logging.h"
 
 using namespace std;
 
 namespace mozilla {
 namespace gfx {
 
+void* ThreadCallback(void* threadData);
+
+class WorkerThreadPosix : public WorkerThread {
+public:
+  explicit WorkerThreadPosix(MultiThreadedJobQueue* aJobQueue)
+  : WorkerThread(aJobQueue)
+  {
+    pthread_create(&mThread, nullptr, ThreadCallback, static_cast<WorkerThread*>(this));
+  }
+
+  ~WorkerThreadPosix()
+  {
+    pthread_join(mThread, nullptr);
+  }
+
+  virtual void SetName(const char* aName) override
+  {
+    // Call this from the thread itself because of Mac.
+#ifdef XP_MACOSX
+    pthread_setname_np(aName);
+#elif defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__)
+    pthread_set_name_np(mThread, aName);
+#elif defined(__NetBSD__)
+    pthread_setname_np(mThread, "%s", (void*)aName);
+#else
+    pthread_setname_np(mThread, aName);
+#endif
+  }
+
+protected:
+  pthread_t mThread;
+};
+
+void* ThreadCallback(void* threadData)
+{
+  WorkerThread* thread = static_cast<WorkerThread*>(threadData);
+  thread->Run();
+  return nullptr;
+}
+
+WorkerThread*
+WorkerThread::Create(MultiThreadedJobQueue* aJobQueue)
+{
+  return new WorkerThreadPosix(aJobQueue);
+}
+
 MultiThreadedJobQueue::MultiThreadedJobQueue()
 : mThreadsCount(0)
 , mShuttingDown(false)
 {}
 
 MultiThreadedJobQueue::~MultiThreadedJobQueue()
 {
   MOZ_ASSERT(mJobs.empty());
@@ -103,72 +149,16 @@ MultiThreadedJobQueue::UnregisterThread(
 {
   MutexAutoLock lock(&mMutex);
   mThreadsCount -= 1;
   if (mThreadsCount == 0) {
     mShutdownCondvar.Broadcast();
   }
 }
 
-void* ThreadCallback(void* threadData)
-{
-  WorkerThread* thread = (WorkerThread*)threadData;
-  thread->Run();
-  return nullptr;
-}
-
-WorkerThread::WorkerThread(MultiThreadedJobQueue* aJobQueue)
-: mQueue(aJobQueue)
-{
-  aJobQueue->RegisterThread();
-  pthread_create(&mThread, nullptr, ThreadCallback, this);
-}
-
-WorkerThread::~WorkerThread()
-{
-  pthread_join(mThread, nullptr);
-}
-
-void
-WorkerThread::SetName(const char* aName)
-{
-  // Call this from the thread itself because of Mac.
-#ifdef XP_MACOSX
-  pthread_setname_np(aName);
-#elif defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__)
-  pthread_set_name_np(mThread, aName);
-#elif defined(__NetBSD__)
-  pthread_setname_np(mThread, "%s", (void*)aName);
-#else
-  pthread_setname_np(mThread, aName);
-#endif
-}
-
-void
-WorkerThread::Run()
-{
-  SetName("gfx worker");
-
-  for (;;) {
-    Job* commands = nullptr;
-    if (!mQueue->WaitForJob(commands)) {
-      mQueue->UnregisterThread();
-      return;
-    }
-
-    JobStatus status = JobScheduler::ProcessJob(commands);
-
-    if (status == JobStatus::Error) {
-      // Don't try to handle errors for now, but that's open to discussions.
-      // I expect errors to be mostly OOM issues.
-      MOZ_CRASH();
-    }
-  }
-}
-
 EventObject::EventObject()
 : mIsSet(false)
 {}
 
 EventObject::~EventObject()
 {}
 
 bool
--- a/gfx/2d/JobScheduler_posix.h
+++ b/gfx/2d/JobScheduler_posix.h
@@ -17,16 +17,17 @@
 #include "mozilla/RefPtr.h"
 #include "mozilla/DebugOnly.h"
 
 namespace mozilla {
 namespace gfx {
 
 class Job;
 class PosixCondVar;
+class WorkerThread;
 
 class Mutex {
 public:
   Mutex() {
     DebugOnly<int> err = pthread_mutex_init(&mMutex, nullptr);
     MOZ_ASSERT(!err);
   }
 
@@ -126,37 +127,16 @@ protected:
   PosixCondVar mAvailableCondvar;
   PosixCondVar mShutdownCondvar;
   int32_t mThreadsCount;
   bool mShuttingDown;
 
   friend class WorkerThread;
 };
 
-/// Worker thread that continuously dequeues Jobs from a MultiThreadedJobQueue
-/// and process them.
-///
-/// The public interface of this class must remain identical to its equivalent
-/// in JobScheduler_win32.h
-class WorkerThread {
-public:
-  explicit WorkerThread(MultiThreadedJobQueue* aJobQueue);
-
-  ~WorkerThread();
-
-  void Run();
-
-  MultiThreadedJobQueue* GetJobQueue() { return mQueue; }
-protected:
-  void SetName(const char* name);
-
-  MultiThreadedJobQueue* mQueue;
-  pthread_t mThread;
-};
-
 /// An object that a thread can synchronously wait on.
 /// Usually set by a SetEventJob.
 class EventObject : public external::AtomicRefCounted<EventObject>
 {
 public:
   MOZ_DECLARE_REFCOUNTED_TYPENAME(EventObject)
 
   EventObject();
new file mode 100644
--- /dev/null
+++ b/gfx/2d/JobScheduler_win32.cpp
@@ -0,0 +1,143 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "JobScheduler.h"
+#include "mozilla/gfx/Logging.h"
+
+using namespace std;
+
+namespace mozilla {
+namespace gfx {
+
+DWORD __stdcall ThreadCallback(void* threadData);
+
+class WorkerThreadWin32 : public WorkerThread {
+public:
+  explicit WorkerThreadWin32(MultiThreadedJobQueue* aJobQueue)
+  : WorkerThread(aJobQueue)
+  {
+    mThread = ::CreateThread(nullptr, 0, ThreadCallback, static_cast<WorkerThread*>(this), 0, nullptr);
+  }
+
+  ~WorkerThreadWin32()
+  {
+    ::WaitForSingleObject(mThread, INFINITE);
+    ::CloseHandle(mThread);
+  }
+
+protected:
+  HANDLE mThread;
+};
+
+DWORD __stdcall ThreadCallback(void* threadData)
+{
+  WorkerThread* thread = static_cast<WorkerThread*>(threadData);
+  thread->Run();
+  return 0;
+}
+
+WorkerThread*
+WorkerThread::Create(MultiThreadedJobQueue* aJobQueue)
+{
+  return new WorkerThreadWin32(aJobQueue);
+}
+
+bool
+MultiThreadedJobQueue::PopJob(Job*& aOutJob, AccessType aAccess)
+{
+  for (;;) {
+    while (aAccess == BLOCKING && mJobs.empty()) {
+      {
+        MutexAutoLock lock(&mMutex);
+        if (mShuttingDown) {
+          return false;
+        }
+      }
+
+      HANDLE handles[] = { mAvailableEvent, mShutdownEvent };
+      ::WaitForMultipleObjects(2, handles, FALSE, INFINITE);
+    }
+
+    MutexAutoLock lock(&mMutex);
+
+    if (mShuttingDown) {
+      return false;
+    }
+
+    if (mJobs.empty()) {
+      if (aAccess == NON_BLOCKING) {
+        return false;
+      }
+      continue;
+    }
+
+    Job* task = mJobs.front();
+    MOZ_ASSERT(task);
+
+    mJobs.pop_front();
+
+    if (mJobs.empty()) {
+      ::ResetEvent(mAvailableEvent);
+    }
+
+    aOutJob = task;
+    return true;
+  }
+}
+
+void
+MultiThreadedJobQueue::SubmitJob(Job* aJob)
+{
+  MOZ_ASSERT(aJob);
+  MutexAutoLock lock(&mMutex);
+  mJobs.push_back(aJob);
+  ::SetEvent(mAvailableEvent);
+}
+
+void
+MultiThreadedJobQueue::ShutDown()
+{
+  {
+    MutexAutoLock lock(&mMutex);
+    mShuttingDown = true;
+  }
+  while (mThreadsCount) {
+    ::SetEvent(mAvailableEvent);
+    ::WaitForSingleObject(mShutdownEvent, INFINITE);
+  }
+}
+
+size_t
+MultiThreadedJobQueue::NumJobs()
+{
+  MutexAutoLock lock(&mMutex);
+  return mJobs.size();
+}
+
+bool
+MultiThreadedJobQueue::IsEmpty()
+{
+  MutexAutoLock lock(&mMutex);
+  return mJobs.empty();
+}
+
+void
+MultiThreadedJobQueue::RegisterThread()
+{
+  mThreadsCount += 1;
+}
+
+void
+MultiThreadedJobQueue::UnregisterThread()
+{
+  MutexAutoLock lock(&mMutex);
+  mThreadsCount -= 1;
+  if (mThreadsCount == 0) {
+    ::SetEvent(mShutdownEvent);
+  }
+}
+
+} // namespace
+} // namespace
--- a/gfx/2d/JobScheduler_win32.h
+++ b/gfx/2d/JobScheduler_win32.h
@@ -5,72 +5,129 @@
 
 #ifdef WIN32
 #ifndef MOZILLA_GFX_TASKSCHEDULER_WIN32_H_
 #define MOZILLA_GFX_TASKSCHEDULER_WIN32_H_
 
 #define NOT_IMPLEMENTED MOZ_CRASH("Not implemented")
 
 #include "mozilla/RefPtr.h"
+#include <windows.h>
+#include <list>
 
 namespace mozilla {
 namespace gfx {
 
 class WorkerThread;
 class Job;
 
 class Mutex {
 public:
-  Mutex() { NOT_IMPLEMENTED; }
-  ~Mutex() { NOT_IMPLEMENTED; }
-  void Lock() { NOT_IMPLEMENTED; }
-  void Unlock() { NOT_IMPLEMENTED; }
+  Mutex() {
+    ::InitializeCriticalSection(&mMutex);
+#ifdef DEBUG
+    mOwner = 0;
+#endif
+  }
+
+  ~Mutex() { ::DeleteCriticalSection(&mMutex); }
+
+  void Lock() {
+    ::EnterCriticalSection(&mMutex);
+#ifdef DEBUG
+    MOZ_ASSERT(mOwner != GetCurrentThreadId(), "recursive locking");
+    mOwner = GetCurrentThreadId();
+#endif
+  }
+
+  void Unlock() {
+#ifdef DEBUG
+    // GetCurrentThreadId cannot return 0: it is not a valid thread id
+    MOZ_ASSERT(mOwner == GetCurrentThreadId(), "mismatched lock/unlock");
+    mOwner = 0;
+#endif
+    ::LeaveCriticalSection(&mMutex);
+  }
+
+protected:
+  CRITICAL_SECTION mMutex;
+#ifdef DEBUG
+  DWORD mOwner;
+#endif
 };
 
 // The public interface of this class must remain identical to its equivalent
 // in JobScheduler_posix.h
 class MultiThreadedJobQueue {
 public:
   enum AccessType {
     BLOCKING,
     NON_BLOCKING
   };
 
-  bool WaitForJob(Job*& aOutCommands) { NOT_IMPLEMENTED; }
-  bool PopJob(Job*& aOutCommands, AccessType aAccess) { NOT_IMPLEMENTED; }
-  void SubmitJob(Job* aCommands) { NOT_IMPLEMENTED; }
-  void ShutDown() { NOT_IMPLEMENTED; }
-  size_t NumJobs() { NOT_IMPLEMENTED;  }
-  bool IsEmpty() { NOT_IMPLEMENTED; }
-  void RegisterThread() { NOT_IMPLEMENTED; }
-  void UnregisterThread() { NOT_IMPLEMENTED; }
+  MultiThreadedJobQueue()
+  : mThreadsCount(0)
+  , mShuttingDown(false)
+  {
+    mAvailableEvent = ::CreateEvent(nullptr, TRUE, FALSE, nullptr);
+    mShutdownEvent = ::CreateEvent(nullptr, TRUE, FALSE, nullptr);
+  }
+
+  ~MultiThreadedJobQueue()
+  {
+    ::CloseHandle(mAvailableEvent);
+    ::CloseHandle(mShutdownEvent);
+  }
+
+  bool WaitForJob(Job*& aOutJob) { return PopJob(aOutJob, BLOCKING); }
+
+  bool PopJob(Job*& aOutJob, AccessType aAccess);
+
+  void SubmitJob(Job* aJob);
+
+  void ShutDown();
+
+  size_t NumJobs();
+
+  bool IsEmpty();
+
+  void RegisterThread();
+
+  void UnregisterThread();
+
+protected:
+  std::list<Job*> mJobs;
+  Mutex mMutex;
+  HANDLE mAvailableEvent;
+  HANDLE mShutdownEvent;
+  int32_t mThreadsCount;
+  bool mShuttingDown;
 
   friend class WorkerThread;
 };
 
 
 // The public interface of this class must remain identical to its equivalent
 // in JobScheduler_posix.h
 class EventObject : public external::AtomicRefCounted<EventObject>
 {
 public:
   MOZ_DECLARE_REFCOUNTED_TYPENAME(EventObject)
 
-  EventObject() { NOT_IMPLEMENTED; }
-  ~EventObject() { NOT_IMPLEMENTED; }
-  void Wait() { NOT_IMPLEMENTED; }
-  bool Peak() { NOT_IMPLEMENTED; }
-  void Set() { NOT_IMPLEMENTED; }
-};
+  EventObject() { mEvent = ::CreateEvent(nullptr, TRUE, FALSE, nullptr); }
+
+  ~EventObject() { ::CloseHandle(mEvent); }
+
+  void Wait() { ::WaitForSingleObject(mEvent, INFINITE); }
 
-// The public interface of this class must remain identical to its equivalent
-// in JobScheduler_posix.h
-class WorkerThread {
-public:
-  explicit WorkerThread(MultiThreadedJobQueue* aJobQueue) { NOT_IMPLEMENTED; }
-  void Run();
+  bool Peak() { return ::WaitForSingleObject(mEvent, 0) == WAIT_OBJECT_0; }
+
+  void Set() { ::SetEvent(mEvent); }
+protected:
+  // TODO: it's expensive to create events so we should try to reuse them
+  HANDLE mEvent;
 };
 
 } // namespace
 } // namespace
 
 #endif
 #endif
--- a/gfx/2d/moz.build
+++ b/gfx/2d/moz.build
@@ -59,16 +59,17 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'coco
         'SourceSurfaceCG.cpp',
     ]
 elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
     SOURCES += [
         'DrawTargetD2D.cpp',
         'DrawTargetD2D1.cpp',
         'ExtendInputEffectD2D1.cpp',
         'FilterNodeD2D1.cpp',
+        'JobScheduler_win32.cpp',
         'PathD2D.cpp',
         'RadialGradientEffectD2D1.cpp',
         'ScaledFontDWrite.cpp',
         'ScaledFontWin.cpp',
         'SourceSurfaceD2D.cpp',
         'SourceSurfaceD2D1.cpp',
         'SourceSurfaceD2DTarget.cpp',
     ]
--- a/gfx/tests/gtest/TestJobScheduler.cpp
+++ b/gfx/tests/gtest/TestJobScheduler.cpp
@@ -1,37 +1,40 @@
 /* vim:set ts=2 sw=2 sts=2 et: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
-#ifndef WIN32
-
 #include "gtest/gtest.h"
 #include "gmock/gmock.h"
 
 #include "mozilla/gfx/JobScheduler.h"
 
+#ifndef WIN32
 #include <pthread.h>
 #include <sched.h>
+#endif
+
 #include <stdlib.h>
 #include <time.h>
 
 namespace test_scheduler {
 
 using namespace mozilla::gfx;
 using namespace mozilla;
 
 // Artificially cause threads to yield randomly in an attempt to make racy
 // things more apparent (if any).
 void MaybeYieldThread()
 {
+#ifndef WIN32
   if (rand() % 5 == 0) {
     sched_yield();
   }
+#endif
 }
 
 /// Used by the TestCommand to check that tasks are processed in the right order.
 struct SanityChecker {
   std::vector<uint64_t> mAdvancements;
   mozilla::gfx::Mutex mMutex;
 
   explicit SanityChecker(uint64_t aNumCmdBuffers)
@@ -237,10 +240,8 @@ TEST(Moz2D, JobScheduler_Chain) {
       for (uint32_t buffers = 1; buffers < 100; buffers += 3) {
         mozilla::gfx::JobScheduler::Init(threads, queues);
         test_scheduler::TestSchedulerChain(threads, buffers);
         mozilla::gfx::JobScheduler::ShutDown();
       }
     }
   }
 }
-
-#endif