2017-10-28 02:10:06 +03:00
|
|
|
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
2015-09-28 14:49:43 +03:00
|
|
|
* 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"
|
2015-11-26 10:38:00 +03:00
|
|
|
#include "Logging.h"
|
2015-09-28 14:49:43 +03:00
|
|
|
|
|
|
|
namespace mozilla {
|
|
|
|
namespace gfx {
|
|
|
|
|
|
|
|
JobScheduler* JobScheduler::sSingleton = nullptr;
|
|
|
|
|
|
|
|
bool JobScheduler::Init(uint32_t aNumThreads, uint32_t aNumQueues) {
|
|
|
|
MOZ_ASSERT(!sSingleton);
|
|
|
|
MOZ_ASSERT(aNumThreads >= aNumQueues);
|
|
|
|
|
|
|
|
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) {
|
2015-09-28 14:49:52 +03:00
|
|
|
sSingleton->mWorkerThreads.push_back(
|
|
|
|
WorkerThread::Create(sSingleton->mDrawingQueues[i % aNumQueues]));
|
2015-09-28 14:49:43 +03:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void JobScheduler::ShutDown() {
|
|
|
|
MOZ_ASSERT(IsEnabled());
|
|
|
|
if (!IsEnabled()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (auto queue : sSingleton->mDrawingQueues) {
|
|
|
|
queue->ShutDown();
|
|
|
|
delete queue;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (WorkerThread* thread : sSingleton->mWorkerThreads) {
|
|
|
|
// this will block until the thread is joined.
|
|
|
|
delete thread;
|
|
|
|
}
|
|
|
|
|
|
|
|
sSingleton->mWorkerThreads.clear();
|
|
|
|
delete sSingleton;
|
|
|
|
sSingleton = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
JobStatus JobScheduler::ProcessJob(Job* aJob) {
|
|
|
|
MOZ_ASSERT(aJob);
|
|
|
|
auto status = aJob->Run();
|
|
|
|
if (status == JobStatus::Error || status == JobStatus::Complete) {
|
|
|
|
delete aJob;
|
|
|
|
}
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
void JobScheduler::SubmitJob(Job* aJob) {
|
|
|
|
MOZ_ASSERT(aJob);
|
2015-10-18 08:24:48 +03:00
|
|
|
RefPtr<SyncObject> start = aJob->GetStartSync();
|
2015-09-28 14:49:43 +03:00
|
|
|
if (start && start->Register(aJob)) {
|
|
|
|
// The Job buffer starts with a non-signaled sync object, it
|
|
|
|
// is now registered in the list of task buffers waiting on the
|
|
|
|
// sync object, so we should not place it in the queue.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
GetQueueForJob(aJob)->SubmitJob(aJob);
|
|
|
|
}
|
|
|
|
|
2016-02-23 21:15:30 +03:00
|
|
|
void JobScheduler::Join(SyncObject* aCompletion) {
|
|
|
|
RefPtr<EventObject> waitForCompletion = new EventObject();
|
|
|
|
JobScheduler::SubmitJob(new SetEventJob(waitForCompletion, aCompletion));
|
|
|
|
waitForCompletion->Wait();
|
|
|
|
}
|
|
|
|
|
2015-09-28 14:49:43 +03:00
|
|
|
MultiThreadedJobQueue* JobScheduler::GetQueueForJob(Job* aJob) {
|
|
|
|
return aJob->IsPinnedToAThread() ? aJob->GetWorkerThread()->GetJobQueue()
|
|
|
|
: GetDrawingQueue();
|
|
|
|
}
|
|
|
|
|
|
|
|
Job::Job(SyncObject* aStart, SyncObject* aCompletion, WorkerThread* aThread)
|
2015-09-28 14:49:59 +03:00
|
|
|
: mNextWaitingJob(nullptr),
|
|
|
|
mStartSync(aStart),
|
2015-09-28 14:49:43 +03:00
|
|
|
mCompletionSync(aCompletion),
|
|
|
|
mPinToThread(aThread) {
|
|
|
|
if (mStartSync) {
|
|
|
|
mStartSync->AddSubsequent(this);
|
|
|
|
}
|
|
|
|
if (mCompletionSync) {
|
|
|
|
mCompletionSync->AddPrerequisite(this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Job::~Job() {
|
|
|
|
if (mCompletionSync) {
|
|
|
|
// printf(" -- Job %p dtor completion %p\n", this, mCompletionSync);
|
|
|
|
mCompletionSync->Signal();
|
|
|
|
mCompletionSync = nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
JobStatus SetEventJob::Run() {
|
|
|
|
mEvent->Set();
|
|
|
|
return JobStatus::Complete;
|
|
|
|
}
|
|
|
|
|
|
|
|
SetEventJob::SetEventJob(EventObject* aEvent, SyncObject* aStart,
|
|
|
|
SyncObject* aCompletion, WorkerThread* aWorker)
|
|
|
|
: Job(aStart, aCompletion, aWorker), mEvent(aEvent) {}
|
|
|
|
|
|
|
|
SetEventJob::~SetEventJob() {}
|
|
|
|
|
|
|
|
SyncObject::SyncObject(uint32_t aNumPrerequisites)
|
|
|
|
: mSignals(aNumPrerequisites),
|
2015-09-28 14:49:59 +03:00
|
|
|
mFirstWaitingJob(nullptr)
|
2015-09-28 14:49:43 +03:00
|
|
|
#ifdef DEBUG
|
|
|
|
,
|
|
|
|
mNumPrerequisites(aNumPrerequisites),
|
|
|
|
mAddedPrerequisites(0)
|
|
|
|
#endif
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2015-09-28 14:49:59 +03:00
|
|
|
SyncObject::~SyncObject() { MOZ_ASSERT(mFirstWaitingJob == nullptr); }
|
2018-11-30 13:46:48 +03:00
|
|
|
|
2015-09-28 14:49:43 +03:00
|
|
|
bool SyncObject::Register(Job* aJob) {
|
|
|
|
MOZ_ASSERT(aJob);
|
|
|
|
|
|
|
|
// For now, ensure that when we schedule the first subsequent, we have already
|
|
|
|
// created all of the prerequisites. This is an arbitrary restriction because
|
|
|
|
// we specify the number of prerequisites in the constructor, but in the
|
|
|
|
// typical scenario, if the assertion FreezePrerequisite blows up here it
|
|
|
|
// probably means we got the initial nmber of prerequisites wrong. We can
|
|
|
|
// decide to remove this restriction if needed.
|
|
|
|
FreezePrerequisites();
|
|
|
|
|
|
|
|
int32_t signals = mSignals;
|
|
|
|
|
|
|
|
if (signals > 0) {
|
|
|
|
AddWaitingJob(aJob);
|
|
|
|
// Since Register and Signal can be called concurrently, it can happen that
|
|
|
|
// reading mSignals in Register happens before decrementing mSignals in
|
|
|
|
// Signal, but SubmitWaitingJobs happens before AddWaitingJob. This ordering
|
|
|
|
// means the SyncObject ends up in the signaled state with a task sitting in
|
|
|
|
// the waiting list. To prevent that we check mSignals a second time and
|
|
|
|
// submit again if signals reached zero in the mean time. We do this instead
|
|
|
|
// of holding a mutex around mSignals+mJobs to reduce lock contention.
|
|
|
|
int32_t signals2 = mSignals;
|
|
|
|
if (signals2 == 0) {
|
|
|
|
SubmitWaitingJobs();
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void SyncObject::Signal() {
|
|
|
|
int32_t signals = --mSignals;
|
|
|
|
MOZ_ASSERT(signals >= 0);
|
|
|
|
|
|
|
|
if (signals == 0) {
|
|
|
|
SubmitWaitingJobs();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SyncObject::AddWaitingJob(Job* aJob) {
|
2015-09-28 14:49:59 +03:00
|
|
|
// Push (using atomics) the task into the list of waiting tasks.
|
|
|
|
for (;;) {
|
|
|
|
Job* first = mFirstWaitingJob;
|
|
|
|
aJob->mNextWaitingJob = first;
|
|
|
|
if (mFirstWaitingJob.compareExchange(first, aJob)) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2015-09-28 14:49:43 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void SyncObject::SubmitWaitingJobs() {
|
2015-09-28 14:49:59 +03:00
|
|
|
// Scheduling the tasks can cause code that modifies <this>'s reference
|
|
|
|
// count to run concurrently, and cause the caller of this function to
|
|
|
|
// be owned by another thread. We need to make sure the reference count
|
|
|
|
// does not reach 0 on another thread before the end of this method, so
|
|
|
|
// hold a strong ref to prevent that!
|
2015-10-18 08:24:48 +03:00
|
|
|
RefPtr<SyncObject> kungFuDeathGrip(this);
|
2015-09-28 14:49:59 +03:00
|
|
|
|
|
|
|
// First atomically swap mFirstWaitingJob and waitingJobs...
|
|
|
|
Job* waitingJobs = nullptr;
|
|
|
|
for (;;) {
|
|
|
|
waitingJobs = mFirstWaitingJob;
|
|
|
|
if (mFirstWaitingJob.compareExchange(waitingJobs, nullptr)) {
|
|
|
|
break;
|
|
|
|
}
|
2015-09-28 14:49:43 +03:00
|
|
|
}
|
|
|
|
|
2015-09-28 14:49:59 +03:00
|
|
|
// ... and submit all of the waiting tasks in waitingJob now that they belong
|
|
|
|
// to this thread.
|
|
|
|
while (waitingJobs) {
|
|
|
|
Job* next = waitingJobs->mNextWaitingJob;
|
|
|
|
waitingJobs->mNextWaitingJob = nullptr;
|
|
|
|
JobScheduler::GetQueueForJob(waitingJobs)->SubmitJob(waitingJobs);
|
|
|
|
waitingJobs = next;
|
2015-09-28 14:49:43 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SyncObject::IsSignaled() { return mSignals == 0; }
|
|
|
|
|
|
|
|
void SyncObject::FreezePrerequisites() {
|
|
|
|
MOZ_ASSERT(mAddedPrerequisites == mNumPrerequisites);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SyncObject::AddPrerequisite(Job* aJob) {
|
|
|
|
MOZ_ASSERT(++mAddedPrerequisites <= mNumPrerequisites);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SyncObject::AddSubsequent(Job* aJob) {}
|
|
|
|
|
2015-09-28 14:49:52 +03:00
|
|
|
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.
|
2015-11-26 10:38:00 +03:00
|
|
|
gfxDevCrash(LogReason::JobStatusError)
|
|
|
|
<< "Invalid job status " << (int)status;
|
2015-09-28 14:49:52 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-28 14:49:43 +03:00
|
|
|
} // namespace gfx
|
|
|
|
} // namespace mozilla
|