From 120cce1d84d9710ec8945d23cb04046252ba56c2 Mon Sep 17 00:00:00 2001 From: Nicolas Silva Date: Mon, 28 Sep 2015 13:49:52 +0200 Subject: [PATCH] Bug 1083101 - Win32 implementation of the JobScheduler. r=jrmuizel --- gfx/2d/JobScheduler.cpp | 30 +++++- gfx/2d/JobScheduler.h | 22 +++++ gfx/2d/JobScheduler_posix.cpp | 102 +++++++++---------- gfx/2d/JobScheduler_posix.h | 22 +---- gfx/2d/JobScheduler_win32.cpp | 143 +++++++++++++++++++++++++++ gfx/2d/JobScheduler_win32.h | 105 +++++++++++++++----- gfx/2d/moz.build | 1 + gfx/tests/gtest/TestJobScheduler.cpp | 9 +- 8 files changed, 328 insertions(+), 106 deletions(-) create mode 100644 gfx/2d/JobScheduler_win32.cpp diff --git a/gfx/2d/JobScheduler.cpp b/gfx/2d/JobScheduler.cpp index 8d7ab727e900..e486f3b4c74d 100644 --- a/gfx/2d/JobScheduler.cpp +++ b/gfx/2d/JobScheduler.cpp @@ -23,7 +23,7 @@ bool JobScheduler::Init(uint32_t aNumThreads, uint32_t aNumQueues) } 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; } @@ -232,5 +232,33 @@ 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 diff --git a/gfx/2d/JobScheduler.h b/gfx/2d/JobScheduler.h index e58cee721a13..50da4874d5e2 100644 --- a/gfx/2d/JobScheduler.h +++ b/gfx/2d/JobScheduler.h @@ -15,11 +15,14 @@ #include "mozilla/gfx/JobScheduler_posix.h" #endif +#include + namespace mozilla { namespace gfx { class MultiThreadedJobQueue; class SyncObject; +class WorkerThread; class JobScheduler { public: @@ -224,6 +227,25 @@ struct MutexAutoLock { 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 diff --git a/gfx/2d/JobScheduler_posix.cpp b/gfx/2d/JobScheduler_posix.cpp index 0b7df32c7f6c..c19103e28d82 100644 --- a/gfx/2d/JobScheduler_posix.cpp +++ b/gfx/2d/JobScheduler_posix.cpp @@ -11,6 +11,52 @@ 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(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(threadData); + thread->Run(); + return nullptr; +} + +WorkerThread* +WorkerThread::Create(MultiThreadedJobQueue* aJobQueue) +{ + return new WorkerThreadPosix(aJobQueue); +} + MultiThreadedJobQueue::MultiThreadedJobQueue() : mThreadsCount(0) , mShuttingDown(false) @@ -108,62 +154,6 @@ MultiThreadedJobQueue::UnregisterThread() } } -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) {} diff --git a/gfx/2d/JobScheduler_posix.h b/gfx/2d/JobScheduler_posix.h index f4071c34e472..74cd975df224 100644 --- a/gfx/2d/JobScheduler_posix.h +++ b/gfx/2d/JobScheduler_posix.h @@ -22,6 +22,7 @@ namespace gfx { class Job; class PosixCondVar; +class WorkerThread; class Mutex { public: @@ -131,27 +132,6 @@ class MultiThreadedJobQueue { 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 diff --git a/gfx/2d/JobScheduler_win32.cpp b/gfx/2d/JobScheduler_win32.cpp new file mode 100644 index 000000000000..d5aedf012afa --- /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(this), 0, nullptr); + } + + ~WorkerThreadWin32() + { + ::WaitForSingleObject(mThread, INFINITE); + ::CloseHandle(mThread); + } + +protected: + HANDLE mThread; +}; + +DWORD __stdcall ThreadCallback(void* threadData) +{ + WorkerThread* thread = static_cast(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 diff --git a/gfx/2d/JobScheduler_win32.h b/gfx/2d/JobScheduler_win32.h index ca2db97dad0b..2ff0a4761152 100644 --- a/gfx/2d/JobScheduler_win32.h +++ b/gfx/2d/JobScheduler_win32.h @@ -10,6 +10,8 @@ #define NOT_IMPLEMENTED MOZ_CRASH("Not implemented") #include "mozilla/RefPtr.h" +#include +#include namespace mozilla { namespace gfx { @@ -19,10 +21,37 @@ 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 @@ -34,14 +63,43 @@ class MultiThreadedJobQueue { 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 mJobs; + Mutex mMutex; + HANDLE mAvailableEvent; + HANDLE mShutdownEvent; + int32_t mThreadsCount; + bool mShuttingDown; friend class WorkerThread; }; @@ -54,19 +112,18 @@ class EventObject : public external::AtomicRefCounted 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); } -// 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(); + ~EventObject() { ::CloseHandle(mEvent); } + + void Wait() { ::WaitForSingleObject(mEvent, INFINITE); } + + 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 diff --git a/gfx/2d/moz.build b/gfx/2d/moz.build index 5480302b99dc..16c1d7ad9a46 100644 --- a/gfx/2d/moz.build +++ b/gfx/2d/moz.build @@ -64,6 +64,7 @@ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': 'DrawTargetD2D1.cpp', 'ExtendInputEffectD2D1.cpp', 'FilterNodeD2D1.cpp', + 'JobScheduler_win32.cpp', 'PathD2D.cpp', 'RadialGradientEffectD2D1.cpp', 'ScaledFontDWrite.cpp', diff --git a/gfx/tests/gtest/TestJobScheduler.cpp b/gfx/tests/gtest/TestJobScheduler.cpp index d1eed148c351..b6a155fb35d1 100644 --- a/gfx/tests/gtest/TestJobScheduler.cpp +++ b/gfx/tests/gtest/TestJobScheduler.cpp @@ -3,15 +3,16 @@ * 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 #include +#endif + #include #include @@ -24,9 +25,11 @@ using namespace mozilla; // 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. @@ -242,5 +245,3 @@ TEST(Moz2D, JobScheduler_Chain) { } } } - -#endif