Bug 1755481: correct documentation of `nsIClipboard::getData`. r=mccr8
[gecko.git] / xpcom / threads / TaskController.cpp
blob5c6c4dafabfefb76c4371f8ed185bc878b285898
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/VsyncTaskManager.h"
18 #include "mozilla/IOInterposer.h"
19 #include "mozilla/ProfilerRunnable.h"
20 #include "mozilla/StaticMutex.h"
21 #include "mozilla/SchedulerGroup.h"
22 #include "mozilla/ScopeExit.h"
23 #include "mozilla/Unused.h"
24 #include "nsIThreadInternal.h"
25 #include "nsQueryObject.h"
26 #include "nsThread.h"
27 #include "prenv.h"
28 #include "prsystem.h"
30 namespace mozilla {
32 std::unique_ptr<TaskController> TaskController::sSingleton;
33 thread_local size_t mThreadPoolIndex = -1;
34 std::atomic<uint64_t> Task::sCurrentTaskSeqNo = 0;
36 const int32_t kMinimumPoolThreadCount = 2;
37 const int32_t kMaximumPoolThreadCount = 8;
39 /* static */
40 int32_t TaskController::GetPoolThreadCount() {
41 if (PR_GetEnv("MOZ_TASKCONTROLLER_THREADCOUNT")) {
42 return strtol(PR_GetEnv("MOZ_TASKCONTROLLER_THREADCOUNT"), nullptr, 0);
45 int32_t numCores = std::max<int32_t>(1, PR_GetNumberOfProcessors());
47 return std::clamp<int32_t>(numCores, kMinimumPoolThreadCount,
48 kMaximumPoolThreadCount);
51 #if defined(MOZ_COLLECTING_RUNNABLE_TELEMETRY)
52 # define AUTO_PROFILE_FOLLOWING_TASK(task) \
53 nsAutoCString name; \
54 (task)->GetName(name); \
55 AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_NONSENSITIVE("Task", OTHER, name); \
56 AUTO_PROFILE_FOLLOWING_RUNNABLE(name);
57 #else
58 # define AUTO_PROFILE_FOLLOWING_TASK(task)
59 #endif
61 bool TaskManager::
62 UpdateCachesForCurrentIterationAndReportPriorityModifierChanged(
63 const MutexAutoLock& aProofOfLock, IterationType aIterationType) {
64 mCurrentSuspended = IsSuspended(aProofOfLock);
66 if (aIterationType == IterationType::EVENT_LOOP_TURN && !mCurrentSuspended) {
67 int32_t oldModifier = mCurrentPriorityModifier;
68 mCurrentPriorityModifier =
69 GetPriorityModifierForEventLoopTurn(aProofOfLock);
71 if (mCurrentPriorityModifier != oldModifier) {
72 return true;
75 return false;
78 Task* Task::GetHighestPriorityDependency() {
79 Task* currentTask = this;
81 while (!currentTask->mDependencies.empty()) {
82 auto iter = currentTask->mDependencies.begin();
84 while (iter != currentTask->mDependencies.end()) {
85 if ((*iter)->mCompleted) {
86 auto oldIter = iter;
87 iter++;
88 // Completed tasks are removed here to prevent needlessly keeping them
89 // alive or iterating over them in the future.
90 currentTask->mDependencies.erase(oldIter);
91 continue;
94 currentTask = iter->get();
95 break;
99 return currentTask == this ? nullptr : currentTask;
102 TaskController* TaskController::Get() {
103 MOZ_ASSERT(sSingleton.get());
104 return sSingleton.get();
107 bool TaskController::Initialize() {
108 MOZ_ASSERT(!sSingleton);
109 sSingleton = std::make_unique<TaskController>();
110 return sSingleton->InitializeInternal();
113 void ThreadFuncPoolThread(void* aIndex) {
114 mThreadPoolIndex = *reinterpret_cast<int32_t*>(aIndex);
115 delete reinterpret_cast<int32_t*>(aIndex);
116 TaskController::Get()->RunPoolThread();
119 bool TaskController::InitializeInternal() {
120 InputTaskManager::Init();
121 VsyncTaskManager::Init();
122 mMTProcessingRunnable = NS_NewRunnableFunction(
123 "TaskController::ExecutePendingMTTasks()",
124 []() { TaskController::Get()->ProcessPendingMTTask(); });
125 mMTBlockingProcessingRunnable = NS_NewRunnableFunction(
126 "TaskController::ExecutePendingMTTasks()",
127 []() { TaskController::Get()->ProcessPendingMTTask(true); });
129 return true;
132 // We want our default stack size limit to be approximately 2MB, to be safe for
133 // JS helper tasks that can use a lot of stack, but expect most threads to use
134 // much less. On Linux, however, requesting a stack of 2MB or larger risks the
135 // kernel allocating an entire 2MB huge page for it on first access, which we do
136 // not want. To avoid this possibility, we subtract 2 standard VM page sizes
137 // from our default.
138 constexpr PRUint32 sBaseStackSize = 2048 * 1024 - 2 * 4096;
140 // TSan enforces a minimum stack size that's just slightly larger than our
141 // default helper stack size. It does this to store blobs of TSan-specific data
142 // on each thread's stack. Unfortunately, that means that even though we'll
143 // actually receive a larger stack than we requested, the effective usable space
144 // of that stack is significantly less than what we expect. To offset TSan
145 // stealing our stack space from underneath us, double the default.
147 // Similarly, ASan requires more stack space due to red-zones.
148 #if defined(MOZ_TSAN) || defined(MOZ_ASAN)
149 constexpr PRUint32 sStackSize = 2 * sBaseStackSize;
150 #else
151 constexpr PRUint32 sStackSize = sBaseStackSize;
152 #endif
154 void TaskController::InitializeThreadPool() {
155 mPoolInitializationMutex.AssertCurrentThreadOwns();
156 MOZ_ASSERT(!mThreadPoolInitialized);
157 mThreadPoolInitialized = true;
159 int32_t poolSize = GetPoolThreadCount();
160 for (int32_t i = 0; i < poolSize; i++) {
161 int32_t* index = new int32_t(i);
162 mPoolThreads.push_back(
163 {PR_CreateThread(PR_USER_THREAD, ThreadFuncPoolThread, index,
164 PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD,
165 PR_JOINABLE_THREAD, sStackSize),
166 nullptr});
170 /* static */
171 size_t TaskController::GetThreadStackSize() { return sStackSize; }
173 void TaskController::SetPerformanceCounterState(
174 PerformanceCounterState* aPerformanceCounterState) {
175 mPerformanceCounterState = aPerformanceCounterState;
178 /* static */
179 void TaskController::Shutdown() {
180 InputTaskManager::Cleanup();
181 VsyncTaskManager::Cleanup();
182 if (sSingleton) {
183 sSingleton->ShutdownThreadPoolInternal();
184 sSingleton->ShutdownInternal();
186 MOZ_ASSERT(!sSingleton);
189 void TaskController::ShutdownThreadPoolInternal() {
191 // Prevent racecondition on mShuttingDown and wait.
192 MutexAutoLock lock(mGraphMutex);
194 mShuttingDown = true;
195 mThreadPoolCV.NotifyAll();
197 for (PoolThread& thread : mPoolThreads) {
198 PR_JoinThread(thread.mThread);
202 void TaskController::ShutdownInternal() { sSingleton = nullptr; }
204 void TaskController::RunPoolThread() {
205 IOInterposer::RegisterCurrentThread();
207 // This is used to hold on to a task to make sure it is released outside the
208 // lock. This is required since it's perfectly feasible for task destructors
209 // to post events themselves.
210 RefPtr<Task> lastTask;
212 nsAutoCString threadName;
213 threadName.AppendLiteral("TaskController #");
214 threadName.AppendInt(static_cast<int64_t>(mThreadPoolIndex));
215 AUTO_PROFILER_REGISTER_THREAD(threadName.BeginReading());
217 MutexAutoLock lock(mGraphMutex);
218 while (true) {
219 bool ranTask = false;
221 if (!mThreadableTasks.empty()) {
222 for (auto iter = mThreadableTasks.begin(); iter != mThreadableTasks.end();
223 ++iter) {
224 // Search for the highest priority dependency of the highest priority
225 // task.
227 // We work with rawptrs to avoid needless refcounting. All our tasks
228 // are always kept alive by the graph. If one is removed from the graph
229 // it is kept alive by mPoolThreads[mThreadPoolIndex].mCurrentTask.
230 Task* task = iter->get();
232 MOZ_ASSERT(!task->mTaskManager);
234 mPoolThreads[mThreadPoolIndex].mEffectiveTaskPriority =
235 task->GetPriority();
237 Task* nextTask;
238 while ((nextTask = task->GetHighestPriorityDependency())) {
239 task = nextTask;
242 if (task->IsMainThreadOnly() || task->mInProgress) {
243 continue;
246 mPoolThreads[mThreadPoolIndex].mCurrentTask = task;
247 mThreadableTasks.erase(task->mIterator);
248 task->mIterator = mThreadableTasks.end();
249 task->mInProgress = true;
251 bool taskCompleted = false;
253 MutexAutoUnlock unlock(mGraphMutex);
254 lastTask = nullptr;
255 AUTO_PROFILE_FOLLOWING_TASK(task);
256 taskCompleted = task->Run();
257 ranTask = true;
260 task->mInProgress = false;
262 if (!taskCompleted) {
263 // Presumably this task was interrupted, leave its dependencies
264 // unresolved and reinsert into the queue.
265 auto insertion = mThreadableTasks.insert(
266 mPoolThreads[mThreadPoolIndex].mCurrentTask);
267 MOZ_ASSERT(insertion.second);
268 task->mIterator = insertion.first;
269 } else {
270 task->mCompleted = true;
271 #ifdef DEBUG
272 task->mIsInGraph = false;
273 #endif
274 task->mDependencies.clear();
275 // This may have unblocked a main thread task. We could do this only
276 // if there was a main thread task before this one in the dependency
277 // chain.
278 mMayHaveMainThreadTask = true;
279 // Since this could have multiple dependencies thare are restricted
280 // to the main thread. Let's make sure that's awake.
281 EnsureMainThreadTasksScheduled();
283 MaybeInterruptTask(GetHighestPriorityMTTask());
286 // Store last task for release next time we release the lock or enter
287 // wait state.
288 lastTask = mPoolThreads[mThreadPoolIndex].mCurrentTask.forget();
289 break;
293 // Ensure the last task is released before we enter the wait state.
294 if (lastTask) {
295 MutexAutoUnlock unlock(mGraphMutex);
296 lastTask = nullptr;
298 // Run another loop iteration, while we were unlocked there was an
299 // opportunity for another task to be posted or shutdown to be initiated.
300 continue;
303 if (!ranTask) {
304 if (mShuttingDown) {
305 IOInterposer::UnregisterCurrentThread();
306 MOZ_ASSERT(mThreadableTasks.empty());
307 return;
310 AUTO_PROFILER_LABEL("TaskController::RunPoolThread", IDLE);
311 mThreadPoolCV.Wait();
316 void TaskController::AddTask(already_AddRefed<Task>&& aTask) {
317 RefPtr<Task> task(aTask);
319 if (!task->IsMainThreadOnly()) {
320 MutexAutoLock lock(mPoolInitializationMutex);
321 if (!mThreadPoolInitialized) {
322 InitializeThreadPool();
323 mThreadPoolInitialized = true;
327 MutexAutoLock lock(mGraphMutex);
329 if (TaskManager* manager = task->GetManager()) {
330 if (manager->mTaskCount == 0) {
331 mTaskManagers.insert(manager);
333 manager->DidQueueTask();
335 // Set this here since if this manager's priority modifier doesn't change
336 // we will not reprioritize when iterating over the queue.
337 task->mPriorityModifier = manager->mCurrentPriorityModifier;
340 task->mInsertionTime = TimeStamp::Now();
342 #ifdef DEBUG
343 task->mIsInGraph = true;
345 for (const RefPtr<Task>& otherTask : task->mDependencies) {
346 MOZ_ASSERT(!otherTask->mTaskManager ||
347 otherTask->mTaskManager == task->mTaskManager);
349 #endif
351 LogTask::LogDispatch(task);
353 std::pair<std::set<RefPtr<Task>, Task::PriorityCompare>::iterator, bool>
354 insertion;
355 if (task->IsMainThreadOnly()) {
356 insertion = mMainThreadTasks.insert(std::move(task));
357 } else {
358 insertion = mThreadableTasks.insert(std::move(task));
360 (*insertion.first)->mIterator = insertion.first;
361 MOZ_ASSERT(insertion.second);
363 MaybeInterruptTask(*insertion.first);
366 void TaskController::WaitForTaskOrMessage() {
367 MutexAutoLock lock(mGraphMutex);
368 while (!mMayHaveMainThreadTask) {
369 AUTO_PROFILER_LABEL("TaskController::WaitForTaskOrMessage", IDLE);
370 mMainThreadCV.Wait();
374 void TaskController::ExecuteNextTaskOnlyMainThread() {
375 MOZ_ASSERT(NS_IsMainThread());
376 MutexAutoLock lock(mGraphMutex);
377 ExecuteNextTaskOnlyMainThreadInternal(lock);
380 void TaskController::ProcessPendingMTTask(bool aMayWait) {
381 MOZ_ASSERT(NS_IsMainThread());
382 MutexAutoLock lock(mGraphMutex);
384 for (;;) {
385 // We only ever process one event here. However we may sometimes
386 // not actually process a real event because of suspended tasks.
387 // This loop allows us to wait until we've processed something
388 // in that scenario.
390 mMTTaskRunnableProcessedTask = ExecuteNextTaskOnlyMainThreadInternal(lock);
392 if (mMTTaskRunnableProcessedTask || !aMayWait) {
393 break;
396 BackgroundHangMonitor().NotifyWait();
399 // ProcessNextEvent will also have attempted to wait, however we may have
400 // given it a Runnable when all the tasks in our task graph were suspended
401 // but we weren't able to cheaply determine that.
402 AUTO_PROFILER_LABEL("TaskController::ProcessPendingMTTask", IDLE);
403 mMainThreadCV.Wait();
406 BackgroundHangMonitor().NotifyActivity();
409 if (mMayHaveMainThreadTask) {
410 EnsureMainThreadTasksScheduled();
414 void TaskController::ReprioritizeTask(Task* aTask, uint32_t aPriority) {
415 MutexAutoLock lock(mGraphMutex);
416 std::set<RefPtr<Task>, Task::PriorityCompare>* queue = &mMainThreadTasks;
417 if (!aTask->IsMainThreadOnly()) {
418 queue = &mThreadableTasks;
421 MOZ_ASSERT(aTask->mIterator != queue->end());
422 queue->erase(aTask->mIterator);
424 aTask->mPriority = aPriority;
426 auto insertion = queue->insert(aTask);
427 MOZ_ASSERT(insertion.second);
428 aTask->mIterator = insertion.first;
430 MaybeInterruptTask(aTask);
433 // Code supporting runnable compatibility.
434 // Task that wraps a runnable.
435 class RunnableTask : public Task {
436 public:
437 RunnableTask(already_AddRefed<nsIRunnable>&& aRunnable, int32_t aPriority,
438 bool aMainThread = true)
439 : Task(aMainThread, aPriority), mRunnable(aRunnable) {}
441 virtual bool Run() override {
442 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
443 MOZ_ASSERT(NS_IsMainThread());
444 // If we're on the main thread, we want to record our current
445 // runnable's name in a static so that BHR can record it.
446 Array<char, nsThread::kRunnableNameBufSize> restoreRunnableName;
447 restoreRunnableName[0] = '\0';
448 auto clear = MakeScopeExit([&] {
449 MOZ_ASSERT(NS_IsMainThread());
450 nsThread::sMainThreadRunnableName = restoreRunnableName;
452 nsAutoCString name;
453 nsThread::GetLabeledRunnableName(mRunnable, name,
454 EventQueuePriority(GetPriority()));
456 restoreRunnableName = nsThread::sMainThreadRunnableName;
458 // Copy the name into sMainThreadRunnableName's buffer, and append a
459 // terminating null.
460 uint32_t length = std::min((uint32_t)nsThread::kRunnableNameBufSize - 1,
461 (uint32_t)name.Length());
462 memcpy(nsThread::sMainThreadRunnableName.begin(), name.BeginReading(),
463 length);
464 nsThread::sMainThreadRunnableName[length] = '\0';
465 #endif
467 mRunnable->Run();
468 mRunnable = nullptr;
469 return true;
472 void SetIdleDeadline(TimeStamp aDeadline) override {
473 nsCOMPtr<nsIIdleRunnable> idleRunnable = do_QueryInterface(mRunnable);
474 if (idleRunnable) {
475 idleRunnable->SetDeadline(aDeadline);
479 PerformanceCounter* GetPerformanceCounter() const override {
480 return nsThread::GetPerformanceCounterBase(mRunnable);
483 virtual bool GetName(nsACString& aName) override {
484 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
485 nsThread::GetLabeledRunnableName(mRunnable, aName,
486 EventQueuePriority(GetPriority()));
487 return true;
488 #else
489 return false;
490 #endif
493 private:
494 RefPtr<nsIRunnable> mRunnable;
497 void TaskController::DispatchRunnable(already_AddRefed<nsIRunnable>&& aRunnable,
498 uint32_t aPriority,
499 TaskManager* aManager) {
500 RefPtr<RunnableTask> task = new RunnableTask(std::move(aRunnable), aPriority);
502 task->SetManager(aManager);
503 TaskController::Get()->AddTask(task.forget());
506 nsIRunnable* TaskController::GetRunnableForMTTask(bool aReallyWait) {
507 MutexAutoLock lock(mGraphMutex);
509 while (mMainThreadTasks.empty()) {
510 if (!aReallyWait) {
511 return nullptr;
514 AUTO_PROFILER_LABEL("TaskController::GetRunnableForMTTask::Wait", IDLE);
515 mMainThreadCV.Wait();
518 return aReallyWait ? mMTBlockingProcessingRunnable : mMTProcessingRunnable;
521 bool TaskController::HasMainThreadPendingTasks() {
522 auto resetIdleState = MakeScopeExit([&idleManager = mIdleTaskManager] {
523 if (idleManager) {
524 idleManager->State().ClearCachedIdleDeadline();
528 for (bool considerIdle : {false, true}) {
529 if (considerIdle && !mIdleTaskManager) {
530 continue;
533 MutexAutoLock lock(mGraphMutex);
535 if (considerIdle) {
536 mIdleTaskManager->State().ForgetPendingTaskGuarantee();
537 // Temporarily unlock so we can peek our idle deadline.
538 // XXX We could do this _before_ we take the lock if the API would let us.
539 // We do want to do this before looking at mMainThreadTasks, in case
540 // someone adds one while we're unlocked.
542 MutexAutoUnlock unlock(mGraphMutex);
543 mIdleTaskManager->State().CachePeekedIdleDeadline(unlock);
547 // Return early if there's no tasks at all.
548 if (mMainThreadTasks.empty()) {
549 return false;
552 // We can cheaply count how many tasks are suspended.
553 uint64_t totalSuspended = 0;
554 for (TaskManager* manager : mTaskManagers) {
555 DebugOnly<bool> modifierChanged =
556 manager
557 ->UpdateCachesForCurrentIterationAndReportPriorityModifierChanged(
558 lock, TaskManager::IterationType::NOT_EVENT_LOOP_TURN);
559 MOZ_ASSERT(!modifierChanged);
561 // The idle manager should be suspended unless we're doing the idle pass.
562 MOZ_ASSERT(manager != mIdleTaskManager || manager->mCurrentSuspended ||
563 considerIdle,
564 "Why are idle tasks not suspended here?");
566 if (manager->mCurrentSuspended) {
567 // XXX - If managers manage off-main-thread tasks this breaks! This
568 // scenario is explicitly not supported.
570 // This is only incremented inside the lock -or- decremented on the main
571 // thread so this is safe.
572 totalSuspended += manager->mTaskCount;
576 // This would break down if we have a non-suspended task depending on a
577 // suspended task. This is why for the moment we do not allow tasks
578 // to be dependent on tasks managed by another taskmanager.
579 if (mMainThreadTasks.size() > totalSuspended) {
580 // If mIdleTaskManager->mTaskCount is 0, we never updated the suspended
581 // state of mIdleTaskManager above, hence shouldn't even check it here.
582 // But in that case idle tasks are not contributing to our suspended task
583 // count anyway.
584 if (mIdleTaskManager && mIdleTaskManager->mTaskCount &&
585 !mIdleTaskManager->mCurrentSuspended) {
586 MOZ_ASSERT(considerIdle, "Why is mIdleTaskManager not suspended?");
587 // Check whether the idle tasks were really needed to make our "we have
588 // an unsuspended task" decision. If they were, we need to force-enable
589 // idle tasks until we run our next task.
590 if (mMainThreadTasks.size() - mIdleTaskManager->mTaskCount <=
591 totalSuspended) {
592 mIdleTaskManager->State().EnforcePendingTaskGuarantee();
595 return true;
598 return false;
601 bool TaskController::ExecuteNextTaskOnlyMainThreadInternal(
602 const MutexAutoLock& aProofOfLock) {
603 // Block to make it easier to jump to our cleanup.
604 bool taskRan = false;
605 do {
606 taskRan = DoExecuteNextTaskOnlyMainThreadInternal(aProofOfLock);
607 if (taskRan) {
608 if (mIdleTaskManager && mIdleTaskManager->mTaskCount &&
609 mIdleTaskManager->IsSuspended(aProofOfLock)) {
610 uint32_t activeTasks = mMainThreadTasks.size();
611 for (TaskManager* manager : mTaskManagers) {
612 if (manager->IsSuspended(aProofOfLock)) {
613 activeTasks -= manager->mTaskCount;
614 } else {
615 break;
619 if (!activeTasks) {
620 // We have only idle (and maybe other suspended) tasks left, so need
621 // to update the idle state. We need to temporarily release the lock
622 // while we do that.
623 MutexAutoUnlock unlock(mGraphMutex);
624 mIdleTaskManager->State().RequestIdleDeadlineIfNeeded(unlock);
627 break;
630 if (!mIdleTaskManager) {
631 break;
634 if (mIdleTaskManager->mTaskCount) {
635 // We have idle tasks that we may not have gotten above because
636 // our idle state is not up to date. We need to update the idle state
637 // and try again. We need to temporarily release the lock while we do
638 // that.
639 MutexAutoUnlock unlock(mGraphMutex);
640 mIdleTaskManager->State().UpdateCachedIdleDeadline(unlock);
641 } else {
642 MutexAutoUnlock unlock(mGraphMutex);
643 mIdleTaskManager->State().RanOutOfTasks(unlock);
646 // When we unlocked, someone may have queued a new task on us. So try to
647 // see whether we can run things again.
648 taskRan = DoExecuteNextTaskOnlyMainThreadInternal(aProofOfLock);
649 } while (false);
651 if (mIdleTaskManager) {
652 // The pending task guarantee is not needed anymore, since we just tried
653 // running a task
654 mIdleTaskManager->State().ForgetPendingTaskGuarantee();
656 if (mMainThreadTasks.empty()) {
657 // XXX the IdlePeriodState API demands we have a MutexAutoUnlock for it.
658 // Otherwise we could perhaps just do this after we exit the locked block,
659 // by pushing the lock down into this method. Though it's not clear that
660 // we could check mMainThreadTasks.size() once we unlock, and whether we
661 // could maybe substitute mMayHaveMainThreadTask for that check.
662 MutexAutoUnlock unlock(mGraphMutex);
663 mIdleTaskManager->State().RanOutOfTasks(unlock);
667 return taskRan;
670 bool TaskController::DoExecuteNextTaskOnlyMainThreadInternal(
671 const MutexAutoLock& aProofOfLock) {
672 nsCOMPtr<nsIThread> mainIThread;
673 NS_GetMainThread(getter_AddRefs(mainIThread));
675 nsThread* mainThread = static_cast<nsThread*>(mainIThread.get());
676 if (mainThread) {
677 mainThread->SetRunningEventDelay(TimeDuration(), TimeStamp());
680 uint32_t totalSuspended = 0;
681 for (TaskManager* manager : mTaskManagers) {
682 bool modifierChanged =
683 manager
684 ->UpdateCachesForCurrentIterationAndReportPriorityModifierChanged(
685 aProofOfLock, TaskManager::IterationType::EVENT_LOOP_TURN);
686 if (modifierChanged) {
687 ProcessUpdatedPriorityModifier(manager);
689 if (manager->mCurrentSuspended) {
690 totalSuspended += manager->mTaskCount;
694 MOZ_ASSERT(mMainThreadTasks.size() >= totalSuspended);
696 // This would break down if we have a non-suspended task depending on a
697 // suspended task. This is why for the moment we do not allow tasks
698 // to be dependent on tasks managed by another taskmanager.
699 if (mMainThreadTasks.size() > totalSuspended) {
700 for (auto iter = mMainThreadTasks.begin(); iter != mMainThreadTasks.end();
701 iter++) {
702 Task* task = iter->get();
704 if (task->mTaskManager && task->mTaskManager->mCurrentSuspended) {
705 // Even though we may want to run some dependencies of this task, we
706 // will run them at their own priority level and not the priority
707 // level of their dependents.
708 continue;
711 task = GetFinalDependency(task);
713 if (!task->IsMainThreadOnly() || task->mInProgress ||
714 (task->mTaskManager && task->mTaskManager->mCurrentSuspended)) {
715 continue;
718 mCurrentTasksMT.push(task);
719 mMainThreadTasks.erase(task->mIterator);
720 task->mIterator = mMainThreadTasks.end();
721 task->mInProgress = true;
722 TaskManager* manager = task->GetManager();
723 bool result = false;
726 MutexAutoUnlock unlock(mGraphMutex);
727 if (manager) {
728 manager->WillRunTask();
729 if (manager != mIdleTaskManager) {
730 // Notify the idle period state that we're running a non-idle task.
731 // This needs to happen while our mutex is not locked!
732 mIdleTaskManager->State().FlagNotIdle();
733 } else {
734 TimeStamp idleDeadline =
735 mIdleTaskManager->State().GetCachedIdleDeadline();
736 MOZ_ASSERT(
737 idleDeadline,
738 "How can we not have a deadline if our manager is enabled?");
739 task->SetIdleDeadline(idleDeadline);
742 if (mIdleTaskManager) {
743 // We found a task to run; we can clear the idle deadline on our idle
744 // task manager. This _must_ be done before we actually run the task,
745 // because running the task could reenter via spinning the event loop
746 // and we want to make sure there's no cached idle deadline at that
747 // point. But we have to make sure we do it after out SetIdleDeadline
748 // call above, in the case when the task is actually an idle task.
749 mIdleTaskManager->State().ClearCachedIdleDeadline();
752 TimeStamp now = TimeStamp::Now();
754 if (mainThread) {
755 if (task->GetPriority() < uint32_t(EventQueuePriority::InputHigh)) {
756 mainThread->SetRunningEventDelay(TimeDuration(), now);
757 } else {
758 mainThread->SetRunningEventDelay(now - task->mInsertionTime, now);
762 PerformanceCounterState::Snapshot snapshot =
763 mPerformanceCounterState->RunnableWillRun(
764 task->GetPerformanceCounter(), now,
765 manager == mIdleTaskManager);
768 LogTask::Run log(task);
769 AUTO_PROFILE_FOLLOWING_TASK(task);
770 result = task->Run();
773 // Task itself should keep manager alive.
774 if (manager) {
775 manager->DidRunTask();
778 mPerformanceCounterState->RunnableDidRun(std::move(snapshot));
781 // Task itself should keep manager alive.
782 if (manager && result && manager->mTaskCount == 0) {
783 mTaskManagers.erase(manager);
786 task->mInProgress = false;
788 if (!result) {
789 // Presumably this task was interrupted, leave its dependencies
790 // unresolved and reinsert into the queue.
791 auto insertion =
792 mMainThreadTasks.insert(std::move(mCurrentTasksMT.top()));
793 MOZ_ASSERT(insertion.second);
794 task->mIterator = insertion.first;
795 manager->WillRunTask();
796 } else {
797 task->mCompleted = true;
798 #ifdef DEBUG
799 task->mIsInGraph = false;
800 #endif
801 // Clear dependencies to release references.
802 task->mDependencies.clear();
804 if (!mThreadableTasks.empty()) {
805 // Since this could have multiple dependencies thare are not
806 // restricted to the main thread. Let's wake up our thread pool.
807 // There is a cost to this, it's possible we will want to wake up
808 // only as many threads as we have unblocked tasks, but we currently
809 // have no way to determine that easily.
810 mThreadPoolCV.NotifyAll();
814 mCurrentTasksMT.pop();
815 return true;
819 mMayHaveMainThreadTask = false;
820 if (mIdleTaskManager) {
821 // We did not find a task to run. We still need to clear the cached idle
822 // deadline on our idle state, because that deadline was only relevant to
823 // the execution of this function. Had we found a task, we would have
824 // cleared the deadline before running that task.
825 mIdleTaskManager->State().ClearCachedIdleDeadline();
827 return false;
830 Task* TaskController::GetFinalDependency(Task* aTask) {
831 Task* nextTask;
833 while ((nextTask = aTask->GetHighestPriorityDependency())) {
834 aTask = nextTask;
837 return aTask;
840 void TaskController::MaybeInterruptTask(Task* aTask) {
841 mGraphMutex.AssertCurrentThreadOwns();
843 if (!aTask) {
844 return;
847 // This optimization prevents many slow lookups in long chains of similar
848 // priority.
849 if (!aTask->mDependencies.empty()) {
850 Task* firstDependency = aTask->mDependencies.begin()->get();
851 if (aTask->GetPriority() <= firstDependency->GetPriority() &&
852 !firstDependency->mCompleted &&
853 aTask->IsMainThreadOnly() == firstDependency->IsMainThreadOnly()) {
854 // This task has the same or a higher priority as one of its dependencies,
855 // never any need to interrupt.
856 return;
860 Task* finalDependency = GetFinalDependency(aTask);
862 if (finalDependency->mInProgress) {
863 // No need to wake anything, we can't schedule this task right now anyway.
864 return;
867 if (aTask->IsMainThreadOnly()) {
868 mMayHaveMainThreadTask = true;
870 EnsureMainThreadTasksScheduled();
872 if (mCurrentTasksMT.empty()) {
873 return;
876 // We could go through the steps above here and interrupt an off main
877 // thread task in case it has a lower priority.
878 if (!finalDependency->IsMainThreadOnly()) {
879 return;
882 if (mCurrentTasksMT.top()->GetPriority() < aTask->GetPriority()) {
883 mCurrentTasksMT.top()->RequestInterrupt(aTask->GetPriority());
885 } else {
886 Task* lowestPriorityTask = nullptr;
887 for (PoolThread& thread : mPoolThreads) {
888 if (!thread.mCurrentTask) {
889 mThreadPoolCV.Notify();
890 // There's a free thread, no need to interrupt anything.
891 return;
894 if (!lowestPriorityTask) {
895 lowestPriorityTask = thread.mCurrentTask.get();
896 continue;
899 // This should possibly select the lowest priority task which was started
900 // the latest. But for now we ignore that optimization.
901 // This also doesn't guarantee a task is interruptable, so that's an
902 // avenue for improvements as well.
903 if (lowestPriorityTask->GetPriority() > thread.mEffectiveTaskPriority) {
904 lowestPriorityTask = thread.mCurrentTask.get();
908 if (lowestPriorityTask->GetPriority() < aTask->GetPriority()) {
909 lowestPriorityTask->RequestInterrupt(aTask->GetPriority());
912 // We choose not to interrupt main thread tasks for tasks which may be
913 // executed off the main thread.
917 Task* TaskController::GetHighestPriorityMTTask() {
918 mGraphMutex.AssertCurrentThreadOwns();
920 if (!mMainThreadTasks.empty()) {
921 return mMainThreadTasks.begin()->get();
923 return nullptr;
926 void TaskController::EnsureMainThreadTasksScheduled() {
927 if (mObserver) {
928 mObserver->OnDispatchedEvent();
930 if (mExternalCondVar) {
931 mExternalCondVar->Notify();
933 mMainThreadCV.Notify();
936 void TaskController::ProcessUpdatedPriorityModifier(TaskManager* aManager) {
937 mGraphMutex.AssertCurrentThreadOwns();
939 MOZ_ASSERT(NS_IsMainThread());
941 int32_t modifier = aManager->mCurrentPriorityModifier;
943 std::vector<RefPtr<Task>> storedTasks;
944 // Find all relevant tasks.
945 for (auto iter = mMainThreadTasks.begin(); iter != mMainThreadTasks.end();) {
946 if ((*iter)->mTaskManager == aManager) {
947 storedTasks.push_back(*iter);
948 iter = mMainThreadTasks.erase(iter);
949 } else {
950 iter++;
954 // Reinsert found tasks with their new priorities.
955 for (RefPtr<Task>& ref : storedTasks) {
956 // Kept alive at first by the vector and then by mMainThreadTasks.
957 Task* task = ref;
958 task->mPriorityModifier = modifier;
959 auto insertion = mMainThreadTasks.insert(std::move(ref));
960 MOZ_ASSERT(insertion.second);
961 task->mIterator = insertion.first;
965 } // namespace mozilla