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/VsyncTaskManager.h"
18 #include "mozilla/IOInterposer.h"
19 #include "mozilla/StaticMutex.h"
20 #include "mozilla/SchedulerGroup.h"
21 #include "mozilla/ScopeExit.h"
22 #include "mozilla/Unused.h"
23 #include "nsIThreadInternal.h"
24 #include "nsQueryObject.h"
31 std::unique_ptr
<TaskController
> TaskController::sSingleton
;
32 thread_local
size_t mThreadPoolIndex
= -1;
33 std::atomic
<uint64_t> Task::sCurrentTaskSeqNo
= 0;
35 const int32_t kMinimumPoolThreadCount
= 2;
36 const int32_t kMaximumPoolThreadCount
= 8;
39 int32_t TaskController::GetPoolThreadCount() {
40 if (PR_GetEnv("MOZ_TASKCONTROLLER_THREADCOUNT")) {
41 return strtol(PR_GetEnv("MOZ_TASKCONTROLLER_THREADCOUNT"), nullptr, 0);
44 int32_t numCores
= std::max
<int32_t>(1, PR_GetNumberOfProcessors());
46 return std::clamp
<int32_t>(numCores
, kMinimumPoolThreadCount
,
47 kMaximumPoolThreadCount
);
50 #if defined(MOZ_COLLECTING_RUNNABLE_TELEMETRY)
53 static constexpr Span
<const char> MarkerTypeName() {
54 return MakeStringSpan("Task");
56 static void StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter
& aWriter
,
57 const nsCString
& aName
, uint32_t aPriority
) {
58 aWriter
.StringProperty("name", aName
);
59 aWriter
.IntProperty("priority", aPriority
);
61 # define EVENT_PRIORITY(NAME, VALUE) \
62 if (aPriority == (VALUE)) { \
63 aWriter.StringProperty("priorityName", #NAME); \
65 EVENT_QUEUE_PRIORITY_LIST(EVENT_PRIORITY
)
66 # undef EVENT_PRIORITY
68 aWriter
.StringProperty("priorityName", "Invalid Value");
71 static MarkerSchema
MarkerTypeDisplay() {
72 using MS
= MarkerSchema
;
73 MS schema
{MS::Location::MarkerChart
, MS::Location::MarkerTable
};
74 schema
.SetChartLabel("{marker.data.name}");
76 "{marker.name} - {marker.data.name} - priority: "
77 "{marker.data.priorityName} ({marker.data.priority})");
78 schema
.AddKeyLabelFormatSearchable("name", "Task Name", MS::Format::String
,
79 MS::Searchable::Searchable
);
80 schema
.AddKeyLabelFormat("priorityName", "Priority Name",
82 schema
.AddKeyLabelFormat("priority", "Priority level", MS::Format::Integer
);
87 class MOZ_RAII AutoProfileTask
{
89 explicit AutoProfileTask(nsACString
& aName
, uint64_t aPriority
)
90 : mName(aName
), mPriority(aPriority
) {
91 if (profiler_is_active()) {
92 mStartTime
= TimeStamp::Now();
97 if (!profiler_thread_is_being_profiled_for_markers()) {
101 AUTO_PROFILER_LABEL("AutoProfileTask", PROFILER
);
102 AUTO_PROFILER_STATS(AUTO_PROFILE_TASK
);
103 profiler_add_marker("Runnable", ::mozilla::baseprofiler::category::OTHER
,
105 ? MarkerTiming::IntervalEnd()
106 : MarkerTiming::IntervalUntilNowFrom(mStartTime
),
107 TaskMarker
{}, mName
, mPriority
);
111 TimeStamp mStartTime
;
116 # define AUTO_PROFILE_FOLLOWING_TASK(task) \
117 nsAutoCString name; \
118 (task)->GetName(name); \
119 AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_NONSENSITIVE("Task", OTHER, name); \
120 mozilla::AutoProfileTask PROFILER_RAII(name, (task)->GetPriority());
122 # define AUTO_PROFILE_FOLLOWING_TASK(task)
126 UpdateCachesForCurrentIterationAndReportPriorityModifierChanged(
127 const MutexAutoLock
& aProofOfLock
, IterationType aIterationType
) {
128 mCurrentSuspended
= IsSuspended(aProofOfLock
);
130 if (aIterationType
== IterationType::EVENT_LOOP_TURN
&& !mCurrentSuspended
) {
131 int32_t oldModifier
= mCurrentPriorityModifier
;
132 mCurrentPriorityModifier
=
133 GetPriorityModifierForEventLoopTurn(aProofOfLock
);
135 if (mCurrentPriorityModifier
!= oldModifier
) {
142 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
143 class MOZ_RAII AutoSetMainThreadRunnableName
{
145 explicit AutoSetMainThreadRunnableName(const nsCString
& aName
) {
146 MOZ_ASSERT(NS_IsMainThread());
147 // We want to record our current runnable's name in a static so
148 // that BHR can record it.
149 mRestoreRunnableName
= nsThread::sMainThreadRunnableName
;
151 // Copy the name into sMainThreadRunnableName's buffer, and append a
153 uint32_t length
= std::min((uint32_t)nsThread::kRunnableNameBufSize
- 1,
154 (uint32_t)aName
.Length());
155 memcpy(nsThread::sMainThreadRunnableName
.begin(), aName
.BeginReading(),
157 nsThread::sMainThreadRunnableName
[length
] = '\0';
160 ~AutoSetMainThreadRunnableName() {
161 nsThread::sMainThreadRunnableName
= mRestoreRunnableName
;
165 Array
<char, nsThread::kRunnableNameBufSize
> mRestoreRunnableName
;
169 Task
* Task::GetHighestPriorityDependency() {
170 Task
* currentTask
= this;
172 while (!currentTask
->mDependencies
.empty()) {
173 auto iter
= currentTask
->mDependencies
.begin();
175 while (iter
!= currentTask
->mDependencies
.end()) {
176 if ((*iter
)->mCompleted
) {
179 // Completed tasks are removed here to prevent needlessly keeping them
180 // alive or iterating over them in the future.
181 currentTask
->mDependencies
.erase(oldIter
);
185 currentTask
= iter
->get();
190 return currentTask
== this ? nullptr : currentTask
;
193 TaskController
* TaskController::Get() {
194 MOZ_ASSERT(sSingleton
.get());
195 return sSingleton
.get();
198 void TaskController::Initialize() {
199 MOZ_ASSERT(!sSingleton
);
200 sSingleton
= std::make_unique
<TaskController
>();
203 void ThreadFuncPoolThread(void* aIndex
) {
204 mThreadPoolIndex
= *reinterpret_cast<int32_t*>(aIndex
);
205 delete reinterpret_cast<int32_t*>(aIndex
);
206 TaskController::Get()->RunPoolThread();
209 TaskController::TaskController()
210 : mGraphMutex("TaskController::mGraphMutex"),
211 mThreadPoolCV(mGraphMutex
, "TaskController::mThreadPoolCV"),
212 mMainThreadCV(mGraphMutex
, "TaskController::mMainThreadCV"),
213 mRunOutOfMTTasksCounter(0) {
214 InputTaskManager::Init();
215 VsyncTaskManager::Init();
216 mMTProcessingRunnable
= NS_NewRunnableFunction(
217 "TaskController::ExecutePendingMTTasks()",
218 []() { TaskController::Get()->ProcessPendingMTTask(); });
219 mMTBlockingProcessingRunnable
= NS_NewRunnableFunction(
220 "TaskController::ExecutePendingMTTasks()",
221 []() { TaskController::Get()->ProcessPendingMTTask(true); });
224 // We want our default stack size limit to be approximately 2MB, to be safe for
225 // JS helper tasks that can use a lot of stack, but expect most threads to use
226 // much less. On Linux, however, requesting a stack of 2MB or larger risks the
227 // kernel allocating an entire 2MB huge page for it on first access, which we do
228 // not want. To avoid this possibility, we subtract 2 standard VM page sizes
230 constexpr PRUint32 sBaseStackSize
= 2048 * 1024 - 2 * 4096;
232 // TSan enforces a minimum stack size that's just slightly larger than our
233 // default helper stack size. It does this to store blobs of TSan-specific data
234 // on each thread's stack. Unfortunately, that means that even though we'll
235 // actually receive a larger stack than we requested, the effective usable space
236 // of that stack is significantly less than what we expect. To offset TSan
237 // stealing our stack space from underneath us, double the default.
239 // Similarly, ASan requires more stack space due to red-zones.
240 #if defined(MOZ_TSAN) || defined(MOZ_ASAN)
241 constexpr PRUint32 sStackSize
= 2 * sBaseStackSize
;
243 constexpr PRUint32 sStackSize
= sBaseStackSize
;
246 void TaskController::InitializeThreadPool() {
247 mPoolInitializationMutex
.AssertCurrentThreadOwns();
248 MOZ_ASSERT(!mThreadPoolInitialized
);
249 mThreadPoolInitialized
= true;
251 int32_t poolSize
= GetPoolThreadCount();
252 for (int32_t i
= 0; i
< poolSize
; i
++) {
253 int32_t* index
= new int32_t(i
);
254 mPoolThreads
.push_back(
255 {PR_CreateThread(PR_USER_THREAD
, ThreadFuncPoolThread
, index
,
256 PR_PRIORITY_NORMAL
, PR_GLOBAL_THREAD
,
257 PR_JOINABLE_THREAD
, sStackSize
),
263 size_t TaskController::GetThreadStackSize() { return sStackSize
; }
265 void TaskController::SetPerformanceCounterState(
266 PerformanceCounterState
* aPerformanceCounterState
) {
267 mPerformanceCounterState
= aPerformanceCounterState
;
271 void TaskController::Shutdown() {
272 InputTaskManager::Cleanup();
273 VsyncTaskManager::Cleanup();
275 sSingleton
->ShutdownThreadPoolInternal();
276 sSingleton
->ShutdownInternal();
278 MOZ_ASSERT(!sSingleton
);
281 void TaskController::ShutdownThreadPoolInternal() {
283 // Prevent racecondition on mShuttingDown and wait.
284 MutexAutoLock
lock(mGraphMutex
);
286 mShuttingDown
= true;
287 mThreadPoolCV
.NotifyAll();
289 for (PoolThread
& thread
: mPoolThreads
) {
290 PR_JoinThread(thread
.mThread
);
294 void TaskController::ShutdownInternal() { sSingleton
= nullptr; }
296 void TaskController::RunPoolThread() {
297 IOInterposer::RegisterCurrentThread();
299 // This is used to hold on to a task to make sure it is released outside the
300 // lock. This is required since it's perfectly feasible for task destructors
301 // to post events themselves.
302 RefPtr
<Task
> lastTask
;
304 nsAutoCString threadName
;
305 threadName
.AppendLiteral("TaskController #");
306 threadName
.AppendInt(static_cast<int64_t>(mThreadPoolIndex
));
307 AUTO_PROFILER_REGISTER_THREAD(threadName
.BeginReading());
309 MutexAutoLock
lock(mGraphMutex
);
311 bool ranTask
= false;
313 if (!mThreadableTasks
.empty()) {
314 for (auto iter
= mThreadableTasks
.begin(); iter
!= mThreadableTasks
.end();
316 // Search for the highest priority dependency of the highest priority
319 // We work with rawptrs to avoid needless refcounting. All our tasks
320 // are always kept alive by the graph. If one is removed from the graph
321 // it is kept alive by mPoolThreads[mThreadPoolIndex].mCurrentTask.
322 Task
* task
= iter
->get();
324 MOZ_ASSERT(!task
->mTaskManager
);
326 mPoolThreads
[mThreadPoolIndex
].mEffectiveTaskPriority
=
330 while ((nextTask
= task
->GetHighestPriorityDependency())) {
334 if (task
->IsMainThreadOnly() || task
->mInProgress
) {
338 mPoolThreads
[mThreadPoolIndex
].mCurrentTask
= task
;
339 mThreadableTasks
.erase(task
->mIterator
);
340 task
->mIterator
= mThreadableTasks
.end();
341 task
->mInProgress
= true;
343 if (!mThreadableTasks
.empty()) {
344 // Ensure at least one additional thread is woken up if there are
345 // more threadable tasks to process. Notifying all threads at once
346 // isn't actually better for performance since they all need the
347 // GraphMutex to proceed anyway.
348 mThreadPoolCV
.Notify();
351 bool taskCompleted
= false;
353 MutexAutoUnlock
unlock(mGraphMutex
);
355 AUTO_PROFILE_FOLLOWING_TASK(task
);
356 taskCompleted
= task
->Run();
360 task
->mInProgress
= false;
362 if (!taskCompleted
) {
363 // Presumably this task was interrupted, leave its dependencies
364 // unresolved and reinsert into the queue.
365 auto insertion
= mThreadableTasks
.insert(
366 mPoolThreads
[mThreadPoolIndex
].mCurrentTask
);
367 MOZ_ASSERT(insertion
.second
);
368 task
->mIterator
= insertion
.first
;
370 task
->mCompleted
= true;
372 task
->mIsInGraph
= false;
374 task
->mDependencies
.clear();
375 // This may have unblocked a main thread task. We could do this only
376 // if there was a main thread task before this one in the dependency
378 mMayHaveMainThreadTask
= true;
379 // Since this could have multiple dependencies thare are restricted
380 // to the main thread. Let's make sure that's awake.
381 EnsureMainThreadTasksScheduled();
383 MaybeInterruptTask(GetHighestPriorityMTTask());
386 // Store last task for release next time we release the lock or enter
388 lastTask
= mPoolThreads
[mThreadPoolIndex
].mCurrentTask
.forget();
393 // Ensure the last task is released before we enter the wait state.
395 MutexAutoUnlock
unlock(mGraphMutex
);
398 // Run another loop iteration, while we were unlocked there was an
399 // opportunity for another task to be posted or shutdown to be initiated.
405 IOInterposer::UnregisterCurrentThread();
406 MOZ_ASSERT(mThreadableTasks
.empty());
410 AUTO_PROFILER_LABEL("TaskController::RunPoolThread", IDLE
);
411 mThreadPoolCV
.Wait();
416 void TaskController::AddTask(already_AddRefed
<Task
>&& aTask
) {
417 RefPtr
<Task
> task(aTask
);
419 if (!task
->IsMainThreadOnly()) {
420 MutexAutoLock
lock(mPoolInitializationMutex
);
421 if (!mThreadPoolInitialized
) {
422 InitializeThreadPool();
426 MutexAutoLock
lock(mGraphMutex
);
428 if (TaskManager
* manager
= task
->GetManager()) {
429 if (manager
->mTaskCount
== 0) {
430 mTaskManagers
.insert(manager
);
432 manager
->DidQueueTask();
434 // Set this here since if this manager's priority modifier doesn't change
435 // we will not reprioritize when iterating over the queue.
436 task
->mPriorityModifier
= manager
->mCurrentPriorityModifier
;
439 if (profiler_is_active_and_unpaused()) {
440 task
->mInsertionTime
= TimeStamp::Now();
444 task
->mIsInGraph
= true;
446 for (const RefPtr
<Task
>& otherTask
: task
->mDependencies
) {
447 MOZ_ASSERT(!otherTask
->mTaskManager
||
448 otherTask
->mTaskManager
== task
->mTaskManager
);
452 LogTask::LogDispatch(task
);
454 std::pair
<std::set
<RefPtr
<Task
>, Task::PriorityCompare
>::iterator
, bool>
456 if (task
->IsMainThreadOnly()) {
457 insertion
= mMainThreadTasks
.insert(std::move(task
));
459 insertion
= mThreadableTasks
.insert(std::move(task
));
461 (*insertion
.first
)->mIterator
= insertion
.first
;
462 MOZ_ASSERT(insertion
.second
);
464 MaybeInterruptTask(*insertion
.first
);
467 void TaskController::WaitForTaskOrMessage() {
468 MutexAutoLock
lock(mGraphMutex
);
469 while (!mMayHaveMainThreadTask
) {
470 AUTO_PROFILER_LABEL("TaskController::WaitForTaskOrMessage", IDLE
);
471 mMainThreadCV
.Wait();
475 void TaskController::ExecuteNextTaskOnlyMainThread() {
476 MOZ_ASSERT(NS_IsMainThread());
477 MutexAutoLock
lock(mGraphMutex
);
478 ExecuteNextTaskOnlyMainThreadInternal(lock
);
481 void TaskController::ProcessPendingMTTask(bool aMayWait
) {
482 MOZ_ASSERT(NS_IsMainThread());
483 MutexAutoLock
lock(mGraphMutex
);
486 // We only ever process one event here. However we may sometimes
487 // not actually process a real event because of suspended tasks.
488 // This loop allows us to wait until we've processed something
491 mMTTaskRunnableProcessedTask
= ExecuteNextTaskOnlyMainThreadInternal(lock
);
493 if (mMTTaskRunnableProcessedTask
|| !aMayWait
) {
497 #ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR
498 // Unlock before calling into the BackgroundHangMonitor API as it uses
501 MutexAutoUnlock
unlock(mGraphMutex
);
502 BackgroundHangMonitor().NotifyWait();
507 // ProcessNextEvent will also have attempted to wait, however we may have
508 // given it a Runnable when all the tasks in our task graph were suspended
509 // but we weren't able to cheaply determine that.
510 AUTO_PROFILER_LABEL("TaskController::ProcessPendingMTTask", IDLE
);
511 mMainThreadCV
.Wait();
514 #ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR
516 MutexAutoUnlock
unlock(mGraphMutex
);
517 BackgroundHangMonitor().NotifyActivity();
522 if (mMayHaveMainThreadTask
) {
523 EnsureMainThreadTasksScheduled();
527 void TaskController::ReprioritizeTask(Task
* aTask
, uint32_t aPriority
) {
528 MutexAutoLock
lock(mGraphMutex
);
529 std::set
<RefPtr
<Task
>, Task::PriorityCompare
>* queue
= &mMainThreadTasks
;
530 if (!aTask
->IsMainThreadOnly()) {
531 queue
= &mThreadableTasks
;
534 MOZ_ASSERT(aTask
->mIterator
!= queue
->end());
535 queue
->erase(aTask
->mIterator
);
537 aTask
->mPriority
= aPriority
;
539 auto insertion
= queue
->insert(aTask
);
540 MOZ_ASSERT(insertion
.second
);
541 aTask
->mIterator
= insertion
.first
;
543 MaybeInterruptTask(aTask
);
546 // Code supporting runnable compatibility.
547 // Task that wraps a runnable.
548 class RunnableTask
: public Task
{
550 RunnableTask(already_AddRefed
<nsIRunnable
>&& aRunnable
, int32_t aPriority
,
551 bool aMainThread
= true)
552 : Task(aMainThread
, aPriority
), mRunnable(aRunnable
) {}
554 virtual bool Run() override
{
560 void SetIdleDeadline(TimeStamp aDeadline
) override
{
561 nsCOMPtr
<nsIIdleRunnable
> idleRunnable
= do_QueryInterface(mRunnable
);
563 idleRunnable
->SetDeadline(aDeadline
);
567 virtual bool GetName(nsACString
& aName
) override
{
568 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
569 if (nsCOMPtr
<nsINamed
> named
= do_QueryInterface(mRunnable
)) {
570 MOZ_ALWAYS_TRUE(NS_SUCCEEDED(named
->GetName(aName
)));
572 aName
.AssignLiteral("non-nsINamed runnable");
574 if (aName
.IsEmpty()) {
575 aName
.AssignLiteral("anonymous runnable");
584 RefPtr
<nsIRunnable
> mRunnable
;
587 void TaskController::DispatchRunnable(already_AddRefed
<nsIRunnable
>&& aRunnable
,
589 TaskManager
* aManager
) {
590 RefPtr
<RunnableTask
> task
= new RunnableTask(std::move(aRunnable
), aPriority
);
592 task
->SetManager(aManager
);
593 TaskController::Get()->AddTask(task
.forget());
596 nsIRunnable
* TaskController::GetRunnableForMTTask(bool aReallyWait
) {
597 MutexAutoLock
lock(mGraphMutex
);
599 while (mMainThreadTasks
.empty()) {
604 AUTO_PROFILER_LABEL("TaskController::GetRunnableForMTTask::Wait", IDLE
);
605 mMainThreadCV
.Wait();
608 return aReallyWait
? mMTBlockingProcessingRunnable
: mMTProcessingRunnable
;
611 bool TaskController::HasMainThreadPendingTasks() {
612 MOZ_ASSERT(NS_IsMainThread());
613 auto resetIdleState
= MakeScopeExit([&idleManager
= mIdleTaskManager
] {
615 idleManager
->State().ClearCachedIdleDeadline();
619 for (bool considerIdle
: {false, true}) {
620 if (considerIdle
&& !mIdleTaskManager
) {
624 MutexAutoLock
lock(mGraphMutex
);
627 mIdleTaskManager
->State().ForgetPendingTaskGuarantee();
628 // Temporarily unlock so we can peek our idle deadline.
629 // XXX We could do this _before_ we take the lock if the API would let us.
630 // We do want to do this before looking at mMainThreadTasks, in case
631 // someone adds one while we're unlocked.
633 MutexAutoUnlock
unlock(mGraphMutex
);
634 mIdleTaskManager
->State().CachePeekedIdleDeadline(unlock
);
638 // Return early if there's no tasks at all.
639 if (mMainThreadTasks
.empty()) {
643 // We can cheaply count how many tasks are suspended.
644 uint64_t totalSuspended
= 0;
645 for (TaskManager
* manager
: mTaskManagers
) {
646 DebugOnly
<bool> modifierChanged
=
648 ->UpdateCachesForCurrentIterationAndReportPriorityModifierChanged(
649 lock
, TaskManager::IterationType::NOT_EVENT_LOOP_TURN
);
650 MOZ_ASSERT(!modifierChanged
);
652 // The idle manager should be suspended unless we're doing the idle pass.
653 MOZ_ASSERT(manager
!= mIdleTaskManager
|| manager
->mCurrentSuspended
||
655 "Why are idle tasks not suspended here?");
657 if (manager
->mCurrentSuspended
) {
658 // XXX - If managers manage off-main-thread tasks this breaks! This
659 // scenario is explicitly not supported.
661 // This is only incremented inside the lock -or- decremented on the main
662 // thread so this is safe.
663 totalSuspended
+= manager
->mTaskCount
;
667 // This would break down if we have a non-suspended task depending on a
668 // suspended task. This is why for the moment we do not allow tasks
669 // to be dependent on tasks managed by another taskmanager.
670 if (mMainThreadTasks
.size() > totalSuspended
) {
671 // If mIdleTaskManager->mTaskCount is 0, we never updated the suspended
672 // state of mIdleTaskManager above, hence shouldn't even check it here.
673 // But in that case idle tasks are not contributing to our suspended task
675 if (mIdleTaskManager
&& mIdleTaskManager
->mTaskCount
&&
676 !mIdleTaskManager
->mCurrentSuspended
) {
677 MOZ_ASSERT(considerIdle
, "Why is mIdleTaskManager not suspended?");
678 // Check whether the idle tasks were really needed to make our "we have
679 // an unsuspended task" decision. If they were, we need to force-enable
680 // idle tasks until we run our next task.
681 if (mMainThreadTasks
.size() - mIdleTaskManager
->mTaskCount
<=
683 mIdleTaskManager
->State().EnforcePendingTaskGuarantee();
692 uint64_t TaskController::PendingMainthreadTaskCountIncludingSuspended() {
693 MutexAutoLock
lock(mGraphMutex
);
694 return mMainThreadTasks
.size();
697 bool TaskController::ExecuteNextTaskOnlyMainThreadInternal(
698 const MutexAutoLock
& aProofOfLock
) {
699 MOZ_ASSERT(NS_IsMainThread());
700 mGraphMutex
.AssertCurrentThreadOwns();
701 // Block to make it easier to jump to our cleanup.
702 bool taskRan
= false;
704 taskRan
= DoExecuteNextTaskOnlyMainThreadInternal(aProofOfLock
);
706 if (mIdleTaskManager
&& mIdleTaskManager
->mTaskCount
&&
707 mIdleTaskManager
->IsSuspended(aProofOfLock
)) {
708 uint32_t activeTasks
= mMainThreadTasks
.size();
709 for (TaskManager
* manager
: mTaskManagers
) {
710 if (manager
->IsSuspended(aProofOfLock
)) {
711 activeTasks
-= manager
->mTaskCount
;
718 // We have only idle (and maybe other suspended) tasks left, so need
719 // to update the idle state. We need to temporarily release the lock
721 MutexAutoUnlock
unlock(mGraphMutex
);
722 mIdleTaskManager
->State().RequestIdleDeadlineIfNeeded(unlock
);
728 if (!mIdleTaskManager
) {
732 if (mIdleTaskManager
->mTaskCount
) {
733 // We have idle tasks that we may not have gotten above because
734 // our idle state is not up to date. We need to update the idle state
735 // and try again. We need to temporarily release the lock while we do
737 MutexAutoUnlock
unlock(mGraphMutex
);
738 mIdleTaskManager
->State().UpdateCachedIdleDeadline(unlock
);
740 MutexAutoUnlock
unlock(mGraphMutex
);
741 mIdleTaskManager
->State().RanOutOfTasks(unlock
);
744 // When we unlocked, someone may have queued a new task on us. So try to
745 // see whether we can run things again.
746 taskRan
= DoExecuteNextTaskOnlyMainThreadInternal(aProofOfLock
);
749 if (mIdleTaskManager
) {
750 // The pending task guarantee is not needed anymore, since we just tried
752 mIdleTaskManager
->State().ForgetPendingTaskGuarantee();
754 if (mMainThreadTasks
.empty()) {
755 ++mRunOutOfMTTasksCounter
;
757 // XXX the IdlePeriodState API demands we have a MutexAutoUnlock for it.
758 // Otherwise we could perhaps just do this after we exit the locked block,
759 // by pushing the lock down into this method. Though it's not clear that
760 // we could check mMainThreadTasks.size() once we unlock, and whether we
761 // could maybe substitute mMayHaveMainThreadTask for that check.
762 MutexAutoUnlock
unlock(mGraphMutex
);
763 mIdleTaskManager
->State().RanOutOfTasks(unlock
);
770 bool TaskController::DoExecuteNextTaskOnlyMainThreadInternal(
771 const MutexAutoLock
& aProofOfLock
) {
772 mGraphMutex
.AssertCurrentThreadOwns();
774 nsCOMPtr
<nsIThread
> mainIThread
;
775 NS_GetMainThread(getter_AddRefs(mainIThread
));
777 nsThread
* mainThread
= static_cast<nsThread
*>(mainIThread
.get());
779 mainThread
->SetRunningEventDelay(TimeDuration(), TimeStamp());
782 uint32_t totalSuspended
= 0;
783 for (TaskManager
* manager
: mTaskManagers
) {
784 bool modifierChanged
=
786 ->UpdateCachesForCurrentIterationAndReportPriorityModifierChanged(
787 aProofOfLock
, TaskManager::IterationType::EVENT_LOOP_TURN
);
788 if (modifierChanged
) {
789 ProcessUpdatedPriorityModifier(manager
);
791 if (manager
->mCurrentSuspended
) {
792 totalSuspended
+= manager
->mTaskCount
;
796 MOZ_ASSERT(mMainThreadTasks
.size() >= totalSuspended
);
798 // This would break down if we have a non-suspended task depending on a
799 // suspended task. This is why for the moment we do not allow tasks
800 // to be dependent on tasks managed by another taskmanager.
801 if (mMainThreadTasks
.size() > totalSuspended
) {
802 for (auto iter
= mMainThreadTasks
.begin(); iter
!= mMainThreadTasks
.end();
804 Task
* task
= iter
->get();
806 if (task
->mTaskManager
&& task
->mTaskManager
->mCurrentSuspended
) {
807 // Even though we may want to run some dependencies of this task, we
808 // will run them at their own priority level and not the priority
809 // level of their dependents.
813 task
= GetFinalDependency(task
);
815 if (!task
->IsMainThreadOnly() || task
->mInProgress
||
816 (task
->mTaskManager
&& task
->mTaskManager
->mCurrentSuspended
)) {
820 mCurrentTasksMT
.push(task
);
821 mMainThreadTasks
.erase(task
->mIterator
);
822 task
->mIterator
= mMainThreadTasks
.end();
823 task
->mInProgress
= true;
824 TaskManager
* manager
= task
->GetManager();
828 MutexAutoUnlock
unlock(mGraphMutex
);
830 manager
->WillRunTask();
831 if (manager
!= mIdleTaskManager
) {
832 // Notify the idle period state that we're running a non-idle task.
833 // This needs to happen while our mutex is not locked!
834 mIdleTaskManager
->State().FlagNotIdle();
836 TimeStamp idleDeadline
=
837 mIdleTaskManager
->State().GetCachedIdleDeadline();
840 "How can we not have a deadline if our manager is enabled?");
841 task
->SetIdleDeadline(idleDeadline
);
844 if (mIdleTaskManager
) {
845 // We found a task to run; we can clear the idle deadline on our idle
846 // task manager. This _must_ be done before we actually run the task,
847 // because running the task could reenter via spinning the event loop
848 // and we want to make sure there's no cached idle deadline at that
849 // point. But we have to make sure we do it after out SetIdleDeadline
850 // call above, in the case when the task is actually an idle task.
851 mIdleTaskManager
->State().ClearCachedIdleDeadline();
854 TimeStamp now
= TimeStamp::Now();
857 if (task
->GetPriority() < uint32_t(EventQueuePriority::InputHigh
) ||
858 task
->mInsertionTime
.IsNull()) {
859 mainThread
->SetRunningEventDelay(TimeDuration(), now
);
861 mainThread
->SetRunningEventDelay(now
- task
->mInsertionTime
, now
);
866 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
870 PerformanceCounterState::Snapshot snapshot
=
871 mPerformanceCounterState
->RunnableWillRun(
872 now
, manager
== mIdleTaskManager
);
875 LogTask::Run
log(task
);
876 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
877 AutoSetMainThreadRunnableName
nameGuard(name
);
879 AUTO_PROFILE_FOLLOWING_TASK(task
);
880 result
= task
->Run();
883 // Task itself should keep manager alive.
885 manager
->DidRunTask();
888 mPerformanceCounterState
->RunnableDidRun(name
, std::move(snapshot
));
891 // Task itself should keep manager alive.
892 if (manager
&& result
&& manager
->mTaskCount
== 0) {
893 mTaskManagers
.erase(manager
);
896 task
->mInProgress
= false;
899 // Presumably this task was interrupted, leave its dependencies
900 // unresolved and reinsert into the queue.
902 mMainThreadTasks
.insert(std::move(mCurrentTasksMT
.top()));
903 MOZ_ASSERT(insertion
.second
);
904 task
->mIterator
= insertion
.first
;
905 manager
->WillRunTask();
907 task
->mCompleted
= true;
909 task
->mIsInGraph
= false;
911 // Clear dependencies to release references.
912 task
->mDependencies
.clear();
914 if (!mThreadableTasks
.empty()) {
915 // We're going to wake up a single thread in our pool. This thread
916 // is responsible for waking up additional threads in the situation
917 // where more than one task became available.
918 mThreadPoolCV
.Notify();
922 mCurrentTasksMT
.pop();
927 mMayHaveMainThreadTask
= false;
928 if (mIdleTaskManager
) {
929 // We did not find a task to run. We still need to clear the cached idle
930 // deadline on our idle state, because that deadline was only relevant to
931 // the execution of this function. Had we found a task, we would have
932 // cleared the deadline before running that task.
933 mIdleTaskManager
->State().ClearCachedIdleDeadline();
938 Task
* TaskController::GetFinalDependency(Task
* aTask
) {
941 while ((nextTask
= aTask
->GetHighestPriorityDependency())) {
948 void TaskController::MaybeInterruptTask(Task
* aTask
) {
949 mGraphMutex
.AssertCurrentThreadOwns();
955 // This optimization prevents many slow lookups in long chains of similar
957 if (!aTask
->mDependencies
.empty()) {
958 Task
* firstDependency
= aTask
->mDependencies
.begin()->get();
959 if (aTask
->GetPriority() <= firstDependency
->GetPriority() &&
960 !firstDependency
->mCompleted
&&
961 aTask
->IsMainThreadOnly() == firstDependency
->IsMainThreadOnly()) {
962 // This task has the same or a higher priority as one of its dependencies,
963 // never any need to interrupt.
968 Task
* finalDependency
= GetFinalDependency(aTask
);
970 if (finalDependency
->mInProgress
) {
971 // No need to wake anything, we can't schedule this task right now anyway.
975 if (aTask
->IsMainThreadOnly()) {
976 mMayHaveMainThreadTask
= true;
978 EnsureMainThreadTasksScheduled();
980 if (mCurrentTasksMT
.empty()) {
984 // We could go through the steps above here and interrupt an off main
985 // thread task in case it has a lower priority.
986 if (!finalDependency
->IsMainThreadOnly()) {
990 if (mCurrentTasksMT
.top()->GetPriority() < aTask
->GetPriority()) {
991 mCurrentTasksMT
.top()->RequestInterrupt(aTask
->GetPriority());
994 Task
* lowestPriorityTask
= nullptr;
995 for (PoolThread
& thread
: mPoolThreads
) {
996 if (!thread
.mCurrentTask
) {
997 mThreadPoolCV
.Notify();
998 // There's a free thread, no need to interrupt anything.
1002 if (!lowestPriorityTask
) {
1003 lowestPriorityTask
= thread
.mCurrentTask
.get();
1007 // This should possibly select the lowest priority task which was started
1008 // the latest. But for now we ignore that optimization.
1009 // This also doesn't guarantee a task is interruptable, so that's an
1010 // avenue for improvements as well.
1011 if (lowestPriorityTask
->GetPriority() > thread
.mEffectiveTaskPriority
) {
1012 lowestPriorityTask
= thread
.mCurrentTask
.get();
1016 if (lowestPriorityTask
->GetPriority() < aTask
->GetPriority()) {
1017 lowestPriorityTask
->RequestInterrupt(aTask
->GetPriority());
1020 // We choose not to interrupt main thread tasks for tasks which may be
1021 // executed off the main thread.
1025 Task
* TaskController::GetHighestPriorityMTTask() {
1026 mGraphMutex
.AssertCurrentThreadOwns();
1028 if (!mMainThreadTasks
.empty()) {
1029 return mMainThreadTasks
.begin()->get();
1034 void TaskController::EnsureMainThreadTasksScheduled() {
1036 mObserver
->OnDispatchedEvent();
1038 if (mExternalCondVar
) {
1039 mExternalCondVar
->Notify();
1041 mMainThreadCV
.Notify();
1044 void TaskController::ProcessUpdatedPriorityModifier(TaskManager
* aManager
) {
1045 mGraphMutex
.AssertCurrentThreadOwns();
1047 MOZ_ASSERT(NS_IsMainThread());
1049 int32_t modifier
= aManager
->mCurrentPriorityModifier
;
1051 std::vector
<RefPtr
<Task
>> storedTasks
;
1052 // Find all relevant tasks.
1053 for (auto iter
= mMainThreadTasks
.begin(); iter
!= mMainThreadTasks
.end();) {
1054 if ((*iter
)->mTaskManager
== aManager
) {
1055 storedTasks
.push_back(*iter
);
1056 iter
= mMainThreadTasks
.erase(iter
);
1062 // Reinsert found tasks with their new priorities.
1063 for (RefPtr
<Task
>& ref
: storedTasks
) {
1064 // Kept alive at first by the vector and then by mMainThreadTasks.
1066 task
->mPriorityModifier
= modifier
;
1067 auto insertion
= mMainThreadTasks
.insert(std::move(ref
));
1068 MOZ_ASSERT(insertion
.second
);
1069 task
->mIterator
= insertion
.first
;
1073 } // namespace mozilla