1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "TaskController.h"
8 #include "nsIIdleRunnable.h"
9 #include "nsIRunnable.h"
10 #include "nsThreadUtils.h"
12 #include <initializer_list>
13 #include "GeckoProfiler.h"
14 #include "mozilla/EventQueue.h"
15 #include "mozilla/BackgroundHangMonitor.h"
16 #include "mozilla/InputTaskManager.h"
17 #include "mozilla/IOInterposer.h"
18 #include "mozilla/StaticMutex.h"
19 #include "mozilla/SchedulerGroup.h"
20 #include "mozilla/ScopeExit.h"
21 #include "mozilla/Unused.h"
22 #include "nsIThreadInternal.h"
23 #include "nsQueryObject.h"
29 typedef HRESULT(WINAPI
* SetThreadDescriptionPtr
)(HANDLE hThread
,
30 PCWSTR lpThreadDescription
);
35 std::unique_ptr
<TaskController
> TaskController::sSingleton
;
36 thread_local
size_t mThreadPoolIndex
= -1;
37 std::atomic
<uint64_t> Task::sCurrentTaskSeqNo
= 0;
39 const int32_t kMaximumPoolThreadCount
= 8;
41 static int32_t GetPoolThreadCount() {
42 if (PR_GetEnv("MOZ_TASKCONTROLLER_THREADCOUNT")) {
43 return strtol(PR_GetEnv("MOZ_TASKCONTROLLER_THREADCOUNT"), nullptr, 0);
46 int32_t numCores
= std::max
<int32_t>(1, PR_GetNumberOfProcessors());
54 return std::min
<int32_t>(kMaximumPoolThreadCount
, numCores
- 1);
57 #if defined(MOZ_GECKO_PROFILER) && defined(MOZ_COLLECTING_RUNNABLE_TELEMETRY)
58 # define AUTO_PROFILE_FOLLOWING_TASK(task) \
60 (task)->GetName(name); \
61 AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_NONSENSITIVE("Task", OTHER, name); \
62 AUTO_PROFILER_MARKER_TEXT("Runnable", OTHER, {}, name);
64 # define AUTO_PROFILE_FOLLOWING_TASK(task)
68 UpdateCachesForCurrentIterationAndReportPriorityModifierChanged(
69 const MutexAutoLock
& aProofOfLock
, IterationType aIterationType
) {
70 mCurrentSuspended
= IsSuspended(aProofOfLock
);
72 if (aIterationType
== IterationType::EVENT_LOOP_TURN
) {
73 int32_t oldModifier
= mCurrentPriorityModifier
;
74 mCurrentPriorityModifier
=
75 GetPriorityModifierForEventLoopTurn(aProofOfLock
);
77 if (mCurrentPriorityModifier
!= oldModifier
) {
84 Task
* Task::GetHighestPriorityDependency() {
85 Task
* currentTask
= this;
87 while (!currentTask
->mDependencies
.empty()) {
88 auto iter
= currentTask
->mDependencies
.begin();
90 while (iter
!= currentTask
->mDependencies
.end()) {
91 if ((*iter
)->mCompleted
) {
94 // Completed tasks are removed here to prevent needlessly keeping them
95 // alive or iterating over them in the future.
96 currentTask
->mDependencies
.erase(oldIter
);
100 currentTask
= iter
->get();
105 return currentTask
== this ? nullptr : currentTask
;
108 TaskController
* TaskController::Get() {
109 MOZ_ASSERT(sSingleton
.get());
110 return sSingleton
.get();
113 bool TaskController::Initialize() {
114 MOZ_ASSERT(!sSingleton
);
115 sSingleton
= std::make_unique
<TaskController
>();
116 return sSingleton
->InitializeInternal();
119 void ThreadFuncPoolThread(void* aIndex
) {
120 mThreadPoolIndex
= *reinterpret_cast<int32_t*>(aIndex
);
121 delete reinterpret_cast<int32_t*>(aIndex
);
122 TaskController::Get()->RunPoolThread();
126 static SetThreadDescriptionPtr sSetThreadDescriptionFunc
= nullptr;
129 bool TaskController::InitializeInternal() {
130 InputTaskManager::Init();
131 mMTProcessingRunnable
= NS_NewRunnableFunction(
132 "TaskController::ExecutePendingMTTasks()",
133 []() { TaskController::Get()->ProcessPendingMTTask(); });
134 mMTBlockingProcessingRunnable
= NS_NewRunnableFunction(
135 "TaskController::ExecutePendingMTTasks()",
136 []() { TaskController::Get()->ProcessPendingMTTask(true); });
139 sSetThreadDescriptionFunc
=
140 reinterpret_cast<SetThreadDescriptionPtr
>(::GetProcAddress(
141 ::GetModuleHandle(L
"Kernel32.dll"), "SetThreadDescription"));
147 // Ample stack size allocated for applications like ImageLib's AV1 decoder.
148 const PRUint32 sStackSize
= 512u * 1024u;
150 void TaskController::InitializeThreadPool() {
151 mPoolInitializationMutex
.AssertCurrentThreadOwns();
152 MOZ_ASSERT(!mThreadPoolInitialized
);
153 mThreadPoolInitialized
= true;
155 int32_t poolSize
= GetPoolThreadCount();
156 for (int32_t i
= 0; i
< poolSize
; i
++) {
157 int32_t* index
= new int32_t(i
);
158 mPoolThreads
.push_back(
159 {PR_CreateThread(PR_USER_THREAD
, ThreadFuncPoolThread
, index
,
160 PR_PRIORITY_NORMAL
, PR_GLOBAL_THREAD
,
161 PR_JOINABLE_THREAD
, 512u * 1024u),
166 void TaskController::SetPerformanceCounterState(
167 PerformanceCounterState
* aPerformanceCounterState
) {
168 mPerformanceCounterState
= aPerformanceCounterState
;
172 void TaskController::Shutdown() {
173 InputTaskManager::Cleanup();
175 sSingleton
->ShutdownThreadPoolInternal();
176 sSingleton
->ShutdownInternal();
178 MOZ_ASSERT(!sSingleton
);
181 void TaskController::ShutdownThreadPoolInternal() {
183 // Prevent racecondition on mShuttingDown and wait.
184 MutexAutoLock
lock(mGraphMutex
);
186 mShuttingDown
= true;
187 mThreadPoolCV
.NotifyAll();
189 for (PoolThread
& thread
: mPoolThreads
) {
190 PR_JoinThread(thread
.mThread
);
194 void TaskController::ShutdownInternal() { sSingleton
= nullptr; }
196 void TaskController::RunPoolThread() {
197 IOInterposer::RegisterCurrentThread();
199 // This is used to hold on to a task to make sure it is released outside the
200 // lock. This is required since it's perfectly feasible for task destructors
201 // to post events themselves.
202 RefPtr
<Task
> lastTask
;
205 nsAutoString threadWName
;
206 threadWName
.AppendLiteral(u
"TaskController Thread #");
207 threadWName
.AppendInt(static_cast<int64_t>(mThreadPoolIndex
));
209 if (sSetThreadDescriptionFunc
) {
210 sSetThreadDescriptionFunc(
211 ::GetCurrentThread(),
212 reinterpret_cast<const WCHAR
*>(threadWName
.BeginReading()));
215 nsAutoCString threadName
;
216 threadName
.AppendLiteral("TaskController Thread #");
217 threadName
.AppendInt(static_cast<int64_t>(mThreadPoolIndex
));
218 PROFILER_REGISTER_THREAD(threadName
.BeginReading());
220 MutexAutoLock
lock(mGraphMutex
);
222 bool ranTask
= false;
224 if (!mThreadableTasks
.empty()) {
225 for (auto iter
= mThreadableTasks
.begin(); iter
!= mThreadableTasks
.end();
227 // Search for the highest priority dependency of the highest priority
230 // We work with rawptrs to avoid needless refcounting. All our tasks
231 // are always kept alive by the graph. If one is removed from the graph
232 // it is kept alive by mPoolThreads[mThreadPoolIndex].mCurrentTask.
233 Task
* task
= iter
->get();
235 MOZ_ASSERT(!task
->mTaskManager
);
237 mPoolThreads
[mThreadPoolIndex
].mEffectiveTaskPriority
=
241 while ((nextTask
= task
->GetHighestPriorityDependency())) {
245 if (task
->IsMainThreadOnly() || task
->mInProgress
) {
249 mPoolThreads
[mThreadPoolIndex
].mCurrentTask
= task
;
250 mThreadableTasks
.erase(task
->mIterator
);
251 task
->mIterator
= mThreadableTasks
.end();
252 task
->mInProgress
= true;
254 bool taskCompleted
= false;
256 MutexAutoUnlock
unlock(mGraphMutex
);
258 AUTO_PROFILE_FOLLOWING_TASK(task
);
259 taskCompleted
= task
->Run();
263 task
->mInProgress
= false;
265 if (!taskCompleted
) {
266 // Presumably this task was interrupted, leave its dependencies
267 // unresolved and reinsert into the queue.
268 auto insertion
= mThreadableTasks
.insert(
269 mPoolThreads
[mThreadPoolIndex
].mCurrentTask
);
270 MOZ_ASSERT(insertion
.second
);
271 task
->mIterator
= insertion
.first
;
273 task
->mCompleted
= true;
275 task
->mIsInGraph
= false;
277 task
->mDependencies
.clear();
278 // This may have unblocked a main thread task. We could do this only
279 // if there was a main thread task before this one in the dependency
281 mMayHaveMainThreadTask
= true;
282 // Since this could have multiple dependencies thare are restricted
283 // to the main thread. Let's make sure that's awake.
284 EnsureMainThreadTasksScheduled();
286 MaybeInterruptTask(GetHighestPriorityMTTask());
289 // Store last task for release next time we release the lock or enter
291 lastTask
= mPoolThreads
[mThreadPoolIndex
].mCurrentTask
.forget();
296 // Ensure the last task is released before we enter the wait state.
298 MutexAutoUnlock
unlock(mGraphMutex
);
301 // Run another loop iteration, while we were unlocked there was an
302 // opportunity for another task to be posted or shutdown to be initiated.
308 IOInterposer::UnregisterCurrentThread();
309 MOZ_ASSERT(mThreadableTasks
.empty());
313 AUTO_PROFILER_LABEL("TaskController::RunPoolThread", IDLE
);
314 mThreadPoolCV
.Wait();
319 void TaskController::AddTask(already_AddRefed
<Task
>&& aTask
) {
320 RefPtr
<Task
> task(aTask
);
322 if (!task
->IsMainThreadOnly()) {
323 MutexAutoLock
lock(mPoolInitializationMutex
);
324 if (!mThreadPoolInitialized
) {
325 InitializeThreadPool();
326 mThreadPoolInitialized
= true;
330 MutexAutoLock
lock(mGraphMutex
);
332 if (TaskManager
* manager
= task
->GetManager()) {
333 if (manager
->mTaskCount
== 0) {
334 mTaskManagers
.insert(manager
);
336 manager
->DidQueueTask();
338 // Set this here since if this manager's priority modifier doesn't change
339 // we will not reprioritize when iterating over the queue.
340 task
->mPriorityModifier
= manager
->mCurrentPriorityModifier
;
343 #ifdef MOZ_GECKO_PROFILER
344 task
->mInsertionTime
= TimeStamp::Now();
348 task
->mIsInGraph
= true;
350 for (const RefPtr
<Task
>& otherTask
: task
->mDependencies
) {
351 MOZ_ASSERT(!otherTask
->mTaskManager
||
352 otherTask
->mTaskManager
== task
->mTaskManager
);
356 LogTask::LogDispatch(task
);
358 std::pair
<std::set
<RefPtr
<Task
>, Task::PriorityCompare
>::iterator
, bool>
360 if (task
->IsMainThreadOnly()) {
361 insertion
= mMainThreadTasks
.insert(std::move(task
));
363 insertion
= mThreadableTasks
.insert(std::move(task
));
365 (*insertion
.first
)->mIterator
= insertion
.first
;
366 MOZ_ASSERT(insertion
.second
);
368 MaybeInterruptTask(*insertion
.first
);
371 void TaskController::WaitForTaskOrMessage() {
372 MutexAutoLock
lock(mGraphMutex
);
373 while (!mMayHaveMainThreadTask
) {
374 AUTO_PROFILER_LABEL("TaskController::WaitForTaskOrMessage", IDLE
);
375 mMainThreadCV
.Wait();
379 void TaskController::ExecuteNextTaskOnlyMainThread() {
380 MOZ_ASSERT(NS_IsMainThread());
381 MutexAutoLock
lock(mGraphMutex
);
382 ExecuteNextTaskOnlyMainThreadInternal(lock
);
385 void TaskController::ProcessPendingMTTask(bool aMayWait
) {
386 MOZ_ASSERT(NS_IsMainThread());
387 MutexAutoLock
lock(mGraphMutex
);
390 // We only ever process one event here. However we may sometimes
391 // not actually process a real event because of suspended tasks.
392 // This loop allows us to wait until we've processed something
395 mMTTaskRunnableProcessedTask
= ExecuteNextTaskOnlyMainThreadInternal(lock
);
397 if (mMTTaskRunnableProcessedTask
|| !aMayWait
) {
401 BackgroundHangMonitor().NotifyWait();
404 // ProcessNextEvent will also have attempted to wait, however we may have
405 // given it a Runnable when all the tasks in our task graph were suspended
406 // but we weren't able to cheaply determine that.
407 AUTO_PROFILER_LABEL("TaskController::ProcessPendingMTTask", IDLE
);
408 mMainThreadCV
.Wait();
411 BackgroundHangMonitor().NotifyActivity();
414 if (mMayHaveMainThreadTask
) {
415 EnsureMainThreadTasksScheduled();
419 void TaskController::ReprioritizeTask(Task
* aTask
, uint32_t aPriority
) {
420 MutexAutoLock
lock(mGraphMutex
);
421 std::set
<RefPtr
<Task
>, Task::PriorityCompare
>* queue
= &mMainThreadTasks
;
422 if (!aTask
->IsMainThreadOnly()) {
423 queue
= &mThreadableTasks
;
426 MOZ_ASSERT(aTask
->mIterator
!= queue
->end());
427 queue
->erase(aTask
->mIterator
);
429 aTask
->mPriority
= aPriority
;
431 auto insertion
= queue
->insert(aTask
);
432 MOZ_ASSERT(insertion
.second
);
433 aTask
->mIterator
= insertion
.first
;
435 MaybeInterruptTask(aTask
);
438 // Code supporting runnable compatibility.
439 // Task that wraps a runnable.
440 class RunnableTask
: public Task
{
442 RunnableTask(already_AddRefed
<nsIRunnable
>&& aRunnable
, int32_t aPriority
,
443 bool aMainThread
= true)
444 : Task(aMainThread
, aPriority
), mRunnable(aRunnable
) {}
446 virtual bool Run() override
{
447 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
448 MOZ_ASSERT(NS_IsMainThread());
449 // If we're on the main thread, we want to record our current
450 // runnable's name in a static so that BHR can record it.
451 Array
<char, nsThread::kRunnableNameBufSize
> restoreRunnableName
;
452 restoreRunnableName
[0] = '\0';
453 auto clear
= MakeScopeExit([&] {
454 MOZ_ASSERT(NS_IsMainThread());
455 nsThread::sMainThreadRunnableName
= restoreRunnableName
;
458 nsThread::GetLabeledRunnableName(mRunnable
, name
,
459 EventQueuePriority(GetPriority()));
461 restoreRunnableName
= nsThread::sMainThreadRunnableName
;
463 // Copy the name into sMainThreadRunnableName's buffer, and append a
465 uint32_t length
= std::min((uint32_t)nsThread::kRunnableNameBufSize
- 1,
466 (uint32_t)name
.Length());
467 memcpy(nsThread::sMainThreadRunnableName
.begin(), name
.BeginReading(),
469 nsThread::sMainThreadRunnableName
[length
] = '\0';
477 void SetIdleDeadline(TimeStamp aDeadline
) override
{
478 nsCOMPtr
<nsIIdleRunnable
> idleRunnable
= do_QueryInterface(mRunnable
);
480 idleRunnable
->SetDeadline(aDeadline
);
484 PerformanceCounter
* GetPerformanceCounter() const override
{
485 return nsThread::GetPerformanceCounterBase(mRunnable
);
488 virtual bool GetName(nsACString
& aName
) override
{
489 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
490 nsThread::GetLabeledRunnableName(mRunnable
, aName
,
491 EventQueuePriority(GetPriority()));
499 RefPtr
<nsIRunnable
> mRunnable
;
502 void TaskController::DispatchRunnable(already_AddRefed
<nsIRunnable
>&& aRunnable
,
504 TaskManager
* aManager
) {
505 RefPtr
<RunnableTask
> task
= new RunnableTask(std::move(aRunnable
), aPriority
);
507 task
->SetManager(aManager
);
508 TaskController::Get()->AddTask(task
.forget());
511 nsIRunnable
* TaskController::GetRunnableForMTTask(bool aReallyWait
) {
512 MutexAutoLock
lock(mGraphMutex
);
514 while (mMainThreadTasks
.empty()) {
519 AUTO_PROFILER_LABEL("TaskController::GetRunnableForMTTask::Wait", IDLE
);
520 mMainThreadCV
.Wait();
523 return aReallyWait
? mMTBlockingProcessingRunnable
: mMTProcessingRunnable
;
526 bool TaskController::HasMainThreadPendingTasks() {
527 auto resetIdleState
= MakeScopeExit([&idleManager
= mIdleTaskManager
] {
529 idleManager
->State().ClearCachedIdleDeadline();
533 for (bool considerIdle
: {false, true}) {
534 if (considerIdle
&& !mIdleTaskManager
) {
538 MutexAutoLock
lock(mGraphMutex
);
541 mIdleTaskManager
->State().ForgetPendingTaskGuarantee();
542 // Temporarily unlock so we can peek our idle deadline.
543 // XXX We could do this _before_ we take the lock if the API would let us.
544 // We do want to do this before looking at mMainThreadTasks, in case
545 // someone adds one while we're unlocked.
547 MutexAutoUnlock
unlock(mGraphMutex
);
548 mIdleTaskManager
->State().CachePeekedIdleDeadline(unlock
);
552 // Return early if there's no tasks at all.
553 if (mMainThreadTasks
.empty()) {
557 // We can cheaply count how many tasks are suspended.
558 uint64_t totalSuspended
= 0;
559 for (TaskManager
* manager
: mTaskManagers
) {
560 DebugOnly
<bool> modifierChanged
=
562 ->UpdateCachesForCurrentIterationAndReportPriorityModifierChanged(
563 lock
, TaskManager::IterationType::NOT_EVENT_LOOP_TURN
);
564 MOZ_ASSERT(!modifierChanged
);
566 // The idle manager should be suspended unless we're doing the idle pass.
567 MOZ_ASSERT(manager
!= mIdleTaskManager
|| manager
->mCurrentSuspended
||
569 "Why are idle tasks not suspended here?");
571 if (manager
->mCurrentSuspended
) {
572 // XXX - If managers manage off-main-thread tasks this breaks! This
573 // scenario is explicitly not supported.
575 // This is only incremented inside the lock -or- decremented on the main
576 // thread so this is safe.
577 totalSuspended
+= manager
->mTaskCount
;
581 // Thi would break down if we have a non-suspended task depending on a
582 // suspended task. This is why for the moment we do not allow tasks
583 // to be dependent on tasks managed by another taskmanager.
584 if (mMainThreadTasks
.size() > totalSuspended
) {
585 // If mIdleTaskManager->mTaskCount is 0, we never updated the suspended
586 // state of mIdleTaskManager above, hence shouldn't even check it here.
587 // But in that case idle tasks are not contributing to our suspended task
589 if (mIdleTaskManager
&& mIdleTaskManager
->mTaskCount
&&
590 !mIdleTaskManager
->mCurrentSuspended
) {
591 MOZ_ASSERT(considerIdle
, "Why is mIdleTaskManager not suspended?");
592 // Check whether the idle tasks were really needed to make our "we have
593 // an unsuspended task" decision. If they were, we need to force-enable
594 // idle tasks until we run our next task.
595 if (mMainThreadTasks
.size() - mIdleTaskManager
->mTaskCount
<=
597 mIdleTaskManager
->State().EnforcePendingTaskGuarantee();
606 bool TaskController::ExecuteNextTaskOnlyMainThreadInternal(
607 const MutexAutoLock
& aProofOfLock
) {
608 // Block to make it easier to jump to our cleanup.
609 bool taskRan
= false;
611 taskRan
= DoExecuteNextTaskOnlyMainThreadInternal(aProofOfLock
);
616 if (!mIdleTaskManager
) {
620 if (mIdleTaskManager
->mTaskCount
) {
621 // We have idle tasks that we may not have gotten above because
622 // our idle state is not up to date. We need to update the idle state
623 // and try again. We need to temporarily release the lock while we do
625 MutexAutoUnlock
unlock(mGraphMutex
);
626 mIdleTaskManager
->State().UpdateCachedIdleDeadline(unlock
);
628 MutexAutoUnlock
unlock(mGraphMutex
);
629 mIdleTaskManager
->State().RanOutOfTasks(unlock
);
632 // When we unlocked, someone may have queued a new task on us. So try to
633 // see whether we can run things again.
634 taskRan
= DoExecuteNextTaskOnlyMainThreadInternal(aProofOfLock
);
637 if (mIdleTaskManager
) {
638 // The pending task guarantee is not needed anymore, since we just tried
640 mIdleTaskManager
->State().ForgetPendingTaskGuarantee();
642 if (mMainThreadTasks
.empty()) {
643 // XXX the IdlePeriodState API demands we have a MutexAutoUnlock for it.
644 // Otherwise we could perhaps just do this after we exit the locked block,
645 // by pushing the lock down into this method. Though it's not clear that
646 // we could check mMainThreadTasks.size() once we unlock, and whether we
647 // could maybe substitute mMayHaveMainThreadTask for that check.
648 MutexAutoUnlock
unlock(mGraphMutex
);
649 mIdleTaskManager
->State().RanOutOfTasks(unlock
);
656 bool TaskController::DoExecuteNextTaskOnlyMainThreadInternal(
657 const MutexAutoLock
& aProofOfLock
) {
658 nsCOMPtr
<nsIThread
> mainIThread
;
659 NS_GetMainThread(getter_AddRefs(mainIThread
));
660 nsThread
* mainThread
= static_cast<nsThread
*>(mainIThread
.get());
661 mainThread
->SetRunningEventDelay(TimeDuration(), TimeStamp());
663 uint32_t totalSuspended
= 0;
664 for (TaskManager
* manager
: mTaskManagers
) {
665 bool modifierChanged
=
667 ->UpdateCachesForCurrentIterationAndReportPriorityModifierChanged(
668 aProofOfLock
, TaskManager::IterationType::EVENT_LOOP_TURN
);
669 if (modifierChanged
) {
670 ProcessUpdatedPriorityModifier(manager
);
672 if (manager
->mCurrentSuspended
) {
673 totalSuspended
+= manager
->mTaskCount
;
677 MOZ_ASSERT(mMainThreadTasks
.size() >= totalSuspended
);
679 // This would break down if we have a non-suspended task depending on a
680 // suspended task. This is why for the moment we do not allow tasks
681 // to be dependent on tasks managed by another taskmanager.
682 if (mMainThreadTasks
.size() > totalSuspended
) {
683 for (auto iter
= mMainThreadTasks
.begin(); iter
!= mMainThreadTasks
.end();
685 Task
* task
= iter
->get();
687 if (task
->mTaskManager
&& task
->mTaskManager
->mCurrentSuspended
) {
688 // Even though we may want to run some dependencies of this task, we
689 // will run them at their own priority level and not the priority
690 // level of their dependents.
694 task
= GetFinalDependency(task
);
696 if (!task
->IsMainThreadOnly() || task
->mInProgress
||
697 (task
->mTaskManager
&& task
->mTaskManager
->mCurrentSuspended
)) {
701 mCurrentTasksMT
.push(task
);
702 mMainThreadTasks
.erase(task
->mIterator
);
703 task
->mIterator
= mMainThreadTasks
.end();
704 task
->mInProgress
= true;
705 TaskManager
* manager
= task
->GetManager();
709 MutexAutoUnlock
unlock(mGraphMutex
);
711 manager
->WillRunTask();
712 if (manager
!= mIdleTaskManager
) {
713 // Notify the idle period state that we're running a non-idle task.
714 // This needs to happen while our mutex is not locked!
715 mIdleTaskManager
->State().FlagNotIdle();
717 TimeStamp idleDeadline
=
718 mIdleTaskManager
->State().GetCachedIdleDeadline();
721 "How can we not have a deadline if our manager is enabled?");
722 task
->SetIdleDeadline(idleDeadline
);
725 if (mIdleTaskManager
) {
726 // We found a task to run; we can clear the idle deadline on our idle
727 // task manager. This _must_ be done before we actually run the task,
728 // because running the task could reenter via spinning the event loop
729 // and we want to make sure there's no cached idle deadline at that
730 // point. But we have to make sure we do it after out SetIdleDeadline
731 // call above, in the case when the task is actually an idle task.
732 mIdleTaskManager
->State().ClearCachedIdleDeadline();
735 TimeStamp now
= TimeStamp::Now();
737 #ifdef MOZ_GECKO_PROFILER
738 if (task
->GetPriority() < uint32_t(EventQueuePriority::InputHigh
)) {
739 mainThread
->SetRunningEventDelay(TimeDuration(), now
);
741 mainThread
->SetRunningEventDelay(now
- task
->mInsertionTime
, now
);
745 PerformanceCounterState::Snapshot snapshot
=
746 mPerformanceCounterState
->RunnableWillRun(
747 task
->GetPerformanceCounter(), now
,
748 manager
== mIdleTaskManager
);
751 LogTask::Run
log(task
);
752 AUTO_PROFILE_FOLLOWING_TASK(task
);
753 result
= task
->Run();
756 // Task itself should keep manager alive.
758 manager
->DidRunTask();
761 mPerformanceCounterState
->RunnableDidRun(std::move(snapshot
));
764 // Task itself should keep manager alive.
765 if (manager
&& result
&& manager
->mTaskCount
== 0) {
766 mTaskManagers
.erase(manager
);
769 task
->mInProgress
= false;
772 // Presumably this task was interrupted, leave its dependencies
773 // unresolved and reinsert into the queue.
775 mMainThreadTasks
.insert(std::move(mCurrentTasksMT
.top()));
776 MOZ_ASSERT(insertion
.second
);
777 task
->mIterator
= insertion
.first
;
778 manager
->WillRunTask();
780 task
->mCompleted
= true;
782 task
->mIsInGraph
= false;
784 // Clear dependencies to release references.
785 task
->mDependencies
.clear();
787 if (!mThreadableTasks
.empty()) {
788 // Since this could have multiple dependencies thare are not
789 // restricted to the main thread. Let's wake up our thread pool.
790 // There is a cost to this, it's possible we will want to wake up
791 // only as many threads as we have unblocked tasks, but we currently
792 // have no way to determine that easily.
793 mThreadPoolCV
.NotifyAll();
797 mCurrentTasksMT
.pop();
802 mMayHaveMainThreadTask
= false;
803 if (mIdleTaskManager
) {
804 // We did not find a task to run. We still need to clear the cached idle
805 // deadline on our idle state, because that deadline was only relevant to
806 // the execution of this function. Had we found a task, we would have
807 // cleared the deadline before running that task.
808 mIdleTaskManager
->State().ClearCachedIdleDeadline();
813 Task
* TaskController::GetFinalDependency(Task
* aTask
) {
816 while ((nextTask
= aTask
->GetHighestPriorityDependency())) {
823 void TaskController::MaybeInterruptTask(Task
* aTask
) {
824 mGraphMutex
.AssertCurrentThreadOwns();
830 // This optimization prevents many slow lookups in long chains of similar
832 if (!aTask
->mDependencies
.empty()) {
833 Task
* firstDependency
= aTask
->mDependencies
.begin()->get();
834 if (aTask
->GetPriority() <= firstDependency
->GetPriority() &&
835 !firstDependency
->mCompleted
&&
836 aTask
->IsMainThreadOnly() == firstDependency
->IsMainThreadOnly()) {
837 // This task has the same or a higher priority as one of its dependencies,
838 // never any need to interrupt.
843 Task
* finalDependency
= GetFinalDependency(aTask
);
845 if (finalDependency
->mInProgress
) {
846 // No need to wake anything, we can't schedule this task right now anyway.
850 if (aTask
->IsMainThreadOnly()) {
851 mMayHaveMainThreadTask
= true;
853 EnsureMainThreadTasksScheduled();
855 if (mCurrentTasksMT
.empty()) {
859 // We could go through the steps above here and interrupt an off main
860 // thread task in case it has a lower priority.
861 if (!finalDependency
->IsMainThreadOnly()) {
865 if (mCurrentTasksMT
.top()->GetPriority() < aTask
->GetPriority()) {
866 mCurrentTasksMT
.top()->RequestInterrupt(aTask
->GetPriority());
869 Task
* lowestPriorityTask
= nullptr;
870 for (PoolThread
& thread
: mPoolThreads
) {
871 if (!thread
.mCurrentTask
) {
872 mThreadPoolCV
.Notify();
873 // There's a free thread, no need to interrupt anything.
877 if (!lowestPriorityTask
) {
878 lowestPriorityTask
= thread
.mCurrentTask
.get();
882 // This should possibly select the lowest priority task which was started
883 // the latest. But for now we ignore that optimization.
884 // This also doesn't guarantee a task is interruptable, so that's an
885 // avenue for improvements as well.
886 if (lowestPriorityTask
->GetPriority() > thread
.mEffectiveTaskPriority
) {
887 lowestPriorityTask
= thread
.mCurrentTask
.get();
891 if (lowestPriorityTask
->GetPriority() < aTask
->GetPriority()) {
892 lowestPriorityTask
->RequestInterrupt(aTask
->GetPriority());
895 // We choose not to interrupt main thread tasks for tasks which may be
896 // executed off the main thread.
900 Task
* TaskController::GetHighestPriorityMTTask() {
901 mGraphMutex
.AssertCurrentThreadOwns();
903 if (!mMainThreadTasks
.empty()) {
904 return mMainThreadTasks
.begin()->get();
909 void TaskController::EnsureMainThreadTasksScheduled() {
911 mObserver
->OnDispatchedEvent();
913 if (mExternalCondVar
) {
914 mExternalCondVar
->Notify();
916 mMainThreadCV
.Notify();
919 void TaskController::ProcessUpdatedPriorityModifier(TaskManager
* aManager
) {
920 mGraphMutex
.AssertCurrentThreadOwns();
922 MOZ_ASSERT(NS_IsMainThread());
924 int32_t modifier
= aManager
->mCurrentPriorityModifier
;
926 std::vector
<RefPtr
<Task
>> storedTasks
;
927 // Find all relevant tasks.
928 for (auto iter
= mMainThreadTasks
.begin(); iter
!= mMainThreadTasks
.end();) {
929 if ((*iter
)->mTaskManager
== aManager
) {
930 storedTasks
.push_back(*iter
);
931 iter
= mMainThreadTasks
.erase(iter
);
937 // Reinsert found tasks with their new priorities.
938 for (RefPtr
<Task
>& ref
: storedTasks
) {
939 // Kept alive at first by the vector and then by mMainThreadTasks.
941 task
->mPriorityModifier
= modifier
;
942 auto insertion
= mMainThreadTasks
.insert(std::move(ref
));
943 MOZ_ASSERT(insertion
.second
);
944 task
->mIterator
= insertion
.first
;
948 } // namespace mozilla