Bug 1688832: part 5) Add `static` `AccessibleCaretManager::GetSelection`, `::GetFrame...
[gecko.git] / xpcom / threads / TaskController.cpp
blobc5d4e445c1bb74ba3dd9b97ab167295746a6dfd2
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"
11 #include <algorithm>
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"
24 #include "nsThread.h"
25 #include "prenv.h"
26 #include "prsystem.h"
28 #ifdef XP_WIN
29 typedef HRESULT(WINAPI* SetThreadDescriptionPtr)(HANDLE hThread,
30 PCWSTR lpThreadDescription);
31 #endif
33 namespace mozilla {
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());
48 if (numCores == 1) {
49 return 1;
51 if (numCores == 2) {
52 return 2;
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) \
59 nsAutoCString name; \
60 (task)->GetName(name); \
61 AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_NONSENSITIVE("Task", OTHER, name); \
62 AUTO_PROFILER_MARKER_TEXT("Runnable", OTHER, {}, name);
63 #else
64 # define AUTO_PROFILE_FOLLOWING_TASK(task)
65 #endif
67 bool TaskManager::
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) {
78 return true;
81 return false;
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) {
92 auto oldIter = iter;
93 iter++;
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);
97 continue;
100 currentTask = iter->get();
101 break;
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();
125 #ifdef XP_WIN
126 static SetThreadDescriptionPtr sSetThreadDescriptionFunc = nullptr;
127 #endif
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); });
138 #ifdef XP_WIN
139 sSetThreadDescriptionFunc =
140 reinterpret_cast<SetThreadDescriptionPtr>(::GetProcAddress(
141 ::GetModuleHandle(L"Kernel32.dll"), "SetThreadDescription"));
142 #endif
144 return true;
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),
162 nullptr});
166 void TaskController::SetPerformanceCounterState(
167 PerformanceCounterState* aPerformanceCounterState) {
168 mPerformanceCounterState = aPerformanceCounterState;
171 /* static */
172 void TaskController::Shutdown() {
173 InputTaskManager::Cleanup();
174 if (sSingleton) {
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;
204 #ifdef XP_WIN
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()));
214 #endif
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);
221 while (true) {
222 bool ranTask = false;
224 if (!mThreadableTasks.empty()) {
225 for (auto iter = mThreadableTasks.begin(); iter != mThreadableTasks.end();
226 ++iter) {
227 // Search for the highest priority dependency of the highest priority
228 // task.
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 =
238 task->GetPriority();
240 Task* nextTask;
241 while ((nextTask = task->GetHighestPriorityDependency())) {
242 task = nextTask;
245 if (task->IsMainThreadOnly() || task->mInProgress) {
246 continue;
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);
257 lastTask = nullptr;
258 AUTO_PROFILE_FOLLOWING_TASK(task);
259 taskCompleted = task->Run();
260 ranTask = true;
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;
272 } else {
273 task->mCompleted = true;
274 #ifdef DEBUG
275 task->mIsInGraph = false;
276 #endif
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
280 // chain.
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
290 // wait state.
291 lastTask = mPoolThreads[mThreadPoolIndex].mCurrentTask.forget();
292 break;
296 // Ensure the last task is released before we enter the wait state.
297 if (lastTask) {
298 MutexAutoUnlock unlock(mGraphMutex);
299 lastTask = nullptr;
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.
303 continue;
306 if (!ranTask) {
307 if (mShuttingDown) {
308 IOInterposer::UnregisterCurrentThread();
309 MOZ_ASSERT(mThreadableTasks.empty());
310 return;
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();
345 #endif
347 #ifdef DEBUG
348 task->mIsInGraph = true;
350 for (const RefPtr<Task>& otherTask : task->mDependencies) {
351 MOZ_ASSERT(!otherTask->mTaskManager ||
352 otherTask->mTaskManager == task->mTaskManager);
354 #endif
356 LogTask::LogDispatch(task);
358 std::pair<std::set<RefPtr<Task>, Task::PriorityCompare>::iterator, bool>
359 insertion;
360 if (task->IsMainThreadOnly()) {
361 insertion = mMainThreadTasks.insert(std::move(task));
362 } else {
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);
389 for (;;) {
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
393 // in that scenario.
395 mMTTaskRunnableProcessedTask = ExecuteNextTaskOnlyMainThreadInternal(lock);
397 if (mMTTaskRunnableProcessedTask || !aMayWait) {
398 break;
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 {
441 public:
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;
457 nsAutoCString name;
458 nsThread::GetLabeledRunnableName(mRunnable, name,
459 EventQueuePriority(GetPriority()));
461 restoreRunnableName = nsThread::sMainThreadRunnableName;
463 // Copy the name into sMainThreadRunnableName's buffer, and append a
464 // terminating null.
465 uint32_t length = std::min((uint32_t)nsThread::kRunnableNameBufSize - 1,
466 (uint32_t)name.Length());
467 memcpy(nsThread::sMainThreadRunnableName.begin(), name.BeginReading(),
468 length);
469 nsThread::sMainThreadRunnableName[length] = '\0';
470 #endif
472 mRunnable->Run();
473 mRunnable = nullptr;
474 return true;
477 void SetIdleDeadline(TimeStamp aDeadline) override {
478 nsCOMPtr<nsIIdleRunnable> idleRunnable = do_QueryInterface(mRunnable);
479 if (idleRunnable) {
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()));
492 return true;
493 #else
494 return false;
495 #endif
498 private:
499 RefPtr<nsIRunnable> mRunnable;
502 void TaskController::DispatchRunnable(already_AddRefed<nsIRunnable>&& aRunnable,
503 uint32_t aPriority,
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()) {
515 if (!aReallyWait) {
516 return nullptr;
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] {
528 if (idleManager) {
529 idleManager->State().ClearCachedIdleDeadline();
533 for (bool considerIdle : {false, true}) {
534 if (considerIdle && !mIdleTaskManager) {
535 continue;
538 MutexAutoLock lock(mGraphMutex);
540 if (considerIdle) {
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()) {
554 return false;
557 // We can cheaply count how many tasks are suspended.
558 uint64_t totalSuspended = 0;
559 for (TaskManager* manager : mTaskManagers) {
560 DebugOnly<bool> modifierChanged =
561 manager
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 ||
568 considerIdle,
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
588 // count anyway.
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 <=
596 totalSuspended) {
597 mIdleTaskManager->State().EnforcePendingTaskGuarantee();
600 return true;
603 return false;
606 bool TaskController::ExecuteNextTaskOnlyMainThreadInternal(
607 const MutexAutoLock& aProofOfLock) {
608 // Block to make it easier to jump to our cleanup.
609 bool taskRan = false;
610 do {
611 taskRan = DoExecuteNextTaskOnlyMainThreadInternal(aProofOfLock);
612 if (taskRan) {
613 break;
616 if (!mIdleTaskManager) {
617 break;
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
624 // that.
625 MutexAutoUnlock unlock(mGraphMutex);
626 mIdleTaskManager->State().UpdateCachedIdleDeadline(unlock);
627 } else {
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);
635 } while (false);
637 if (mIdleTaskManager) {
638 // The pending task guarantee is not needed anymore, since we just tried
639 // running a task
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);
653 return taskRan;
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 =
666 manager
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();
684 iter++) {
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.
691 continue;
694 task = GetFinalDependency(task);
696 if (!task->IsMainThreadOnly() || task->mInProgress ||
697 (task->mTaskManager && task->mTaskManager->mCurrentSuspended)) {
698 continue;
701 mCurrentTasksMT.push(task);
702 mMainThreadTasks.erase(task->mIterator);
703 task->mIterator = mMainThreadTasks.end();
704 task->mInProgress = true;
705 TaskManager* manager = task->GetManager();
706 bool result = false;
709 MutexAutoUnlock unlock(mGraphMutex);
710 if (manager) {
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();
716 } else {
717 TimeStamp idleDeadline =
718 mIdleTaskManager->State().GetCachedIdleDeadline();
719 MOZ_ASSERT(
720 idleDeadline,
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);
740 } else {
741 mainThread->SetRunningEventDelay(now - task->mInsertionTime, now);
743 #endif
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.
757 if (manager) {
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;
771 if (!result) {
772 // Presumably this task was interrupted, leave its dependencies
773 // unresolved and reinsert into the queue.
774 auto insertion =
775 mMainThreadTasks.insert(std::move(mCurrentTasksMT.top()));
776 MOZ_ASSERT(insertion.second);
777 task->mIterator = insertion.first;
778 manager->WillRunTask();
779 } else {
780 task->mCompleted = true;
781 #ifdef DEBUG
782 task->mIsInGraph = false;
783 #endif
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();
798 return true;
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();
810 return false;
813 Task* TaskController::GetFinalDependency(Task* aTask) {
814 Task* nextTask;
816 while ((nextTask = aTask->GetHighestPriorityDependency())) {
817 aTask = nextTask;
820 return aTask;
823 void TaskController::MaybeInterruptTask(Task* aTask) {
824 mGraphMutex.AssertCurrentThreadOwns();
826 if (!aTask) {
827 return;
830 // This optimization prevents many slow lookups in long chains of similar
831 // priority.
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.
839 return;
843 Task* finalDependency = GetFinalDependency(aTask);
845 if (finalDependency->mInProgress) {
846 // No need to wake anything, we can't schedule this task right now anyway.
847 return;
850 if (aTask->IsMainThreadOnly()) {
851 mMayHaveMainThreadTask = true;
853 EnsureMainThreadTasksScheduled();
855 if (mCurrentTasksMT.empty()) {
856 return;
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()) {
862 return;
865 if (mCurrentTasksMT.top()->GetPriority() < aTask->GetPriority()) {
866 mCurrentTasksMT.top()->RequestInterrupt(aTask->GetPriority());
868 } else {
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.
874 return;
877 if (!lowestPriorityTask) {
878 lowestPriorityTask = thread.mCurrentTask.get();
879 continue;
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();
906 return nullptr;
909 void TaskController::EnsureMainThreadTasksScheduled() {
910 if (mObserver) {
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);
932 } else {
933 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.
940 Task* task = ref;
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