# Global Metrics

path: .metrics.nexits.average
old: 0.6666666666666666
new: 0.8648648648648649
path: .metrics.nexits.sum
old: 4.0
new: 32.0
path: .metrics.cognitive.average
old: 0.16666666666666666
new: 4.594594594594595
path: .metrics.cognitive.sum
old: 1.0
new: 170.0
path: .metrics.mi.mi_sei
old: -6.5029402404496395
new: -72.44936525545216
path: .metrics.mi.mi_original
old: 32.47183840675838
new: -26.675408948963252
path: .metrics.mi.mi_visual_studio
old: 18.989379185238818
new: 0.0
path: .metrics.loc.lloc
old: 5.0
new: 304.0
path: .metrics.loc.cloc
old: 16.0
new: 127.0
path: .metrics.loc.sloc
old: 229.0
new: 955.0
path: .metrics.loc.blank
old: 42.0
new: 160.0
path: .metrics.loc.ploc
old: 171.0
new: 668.0
path: .metrics.nargs.average
old: 0.5
new: 0.5405405405405406
path: .metrics.nargs.sum
old: 3.0
new: 20.0
path: .metrics.halstead.time
old: 5795.057307265363
new: 95622.15583520064
path: .metrics.halstead.bugs
old: 0.7386390070274477
new: 4.787396918087377
path: .metrics.halstead.estimated_program_length
old: 1429.2441800947695
new: 2751.7158656869374
path: .metrics.halstead.length
old: 748.0
new: 2991.0
path: .metrics.halstead.N1
old: 402.0
new: 1816.0
path: .metrics.halstead.level
old: 0.05476118040766656
new: 0.014657210401891257
path: .metrics.halstead.volume
old: 5712.1952161666495
new: 25227.973028861445
path: .metrics.halstead.vocabulary
old: 199.0
new: 346.0
path: .metrics.halstead.n1
old: 19.0
new: 36.0
path: .metrics.halstead.N2
old: 346.0
new: 1175.0
path: .metrics.halstead.difficulty
old: 18.261111111111113
new: 68.2258064516129
path: .metrics.halstead.effort
old: 104311.03153077654
new: 1721198.8050336114
path: .metrics.halstead.n2
old: 180.0
new: 310.0
path: .metrics.halstead.purity_ratio
old: 1.910754251463596
new: 0.9199986177488924
path: .metrics.cyclomatic.average
old: 1.0909090909090908
new: 4.083333333333333
path: .metrics.cyclomatic.sum
old: 24.0
new: 147.0
path: .metrics.nom.closures
old: 0.0
new: 4.0
path: .metrics.nom.functions
old: 6.0
new: 33.0
path: .metrics.nom.total
old: 6.0
new: 37.0

# Spaces Data

## Minimal test - lines (33, 955)

path: .spaces[0].metrics.cognitive.sum
old: 0.0
new: 170.0
path: .spaces[0].metrics.cognitive.average
old: null
new: 4.594594594594595
path: .spaces[0].metrics.loc.blank
old: 0.0
new: 158.0
path: .spaces[0].metrics.loc.ploc
old: 1.0
new: 644.0
path: .spaces[0].metrics.loc.lloc
old: 0.0
new: 304.0
path: .spaces[0].metrics.loc.cloc
old: 0.0
new: 121.0
path: .spaces[0].metrics.loc.sloc
old: 1.0
new: 923.0
path: .spaces[0].metrics.nargs.average
old: null
new: 0.5405405405405406
path: .spaces[0].metrics.nargs.sum
old: 0.0
new: 20.0
path: .spaces[0].metrics.nom.functions
old: 0.0
new: 33.0
path: .spaces[0].metrics.nom.total
old: 0.0
new: 37.0
path: .spaces[0].metrics.nom.closures
old: 0.0
new: 4.0
path: .spaces[0].metrics.nexits.average
old: null
new: 0.8648648648648649
path: .spaces[0].metrics.nexits.sum
old: 0.0
new: 32.0
path: .spaces[0].metrics.halstead.n2
old: 1.0
new: 289.0
path: .spaces[0].metrics.halstead.bugs
old: 0.0
new: 4.881475472096358
path: .spaces[0].metrics.halstead.length
old: 1.0
new: 2960.0
path: .spaces[0].metrics.halstead.estimated_program_length
old: null
new: 2548.6708222946195
path: .spaces[0].metrics.halstead.N1
old: 0.0
new: 1808.0
path: .spaces[0].metrics.halstead.effort
old: 0.0
new: 1772182.930940365
path: .spaces[0].metrics.halstead.purity_ratio
old: null
new: 0.8610374399643985
path: .spaces[0].metrics.halstead.vocabulary
old: 1.0
new: 325.0
path: .spaces[0].metrics.halstead.volume
old: 0.0
new: 24699.115887430817
path: .spaces[0].metrics.halstead.N2
old: 1.0
new: 1152.0
path: .spaces[0].metrics.halstead.n1
old: 0.0
new: 36.0
path: .spaces[0].metrics.halstead.difficulty
old: 0.0
new: 71.75086505190312
path: .spaces[0].metrics.halstead.level
old: null
new: 0.013937114197530864
path: .spaces[0].metrics.halstead.time
old: 0.0
new: 98454.60727446472
path: .spaces[0].metrics.cyclomatic.average
old: 1.0
new: 4.171428571428572
path: .spaces[0].metrics.cyclomatic.sum
old: 1.0
new: 146.0
path: .spaces[0].metrics.mi.mi_original
old: null
new: -25.78311178425659
path: .spaces[0].metrics.mi.mi_visual_studio
old: null
new: 0.0
path: .spaces[0].metrics.mi.mi_sei
old: null
new: -71.43423738401863

### Code

    namespace mozilla {

    std::unique_ptr TaskController::sSingleton;
    thread_local size_t mThreadPoolIndex = -1;
    std::atomic Task::sCurrentTaskSeqNo = 0;

    const int32_t kMaximumPoolThreadCount = 8;

    static int32_t GetPoolThreadCount() {
      if (PR_GetEnv("MOZ_TASKCONTROLLER_THREADCOUNT")) {
        return strtol(PR_GetEnv("MOZ_TASKCONTROLLER_THREADCOUNT"), nullptr, 0);
      }

      int32_t numCores = std::max(1, PR_GetNumberOfProcessors());

      if (numCores == 1) {
        return 1;
      }
      if (numCores == 2) {
        return 2;
      }
      return std::min(kMaximumPoolThreadCount, numCores - 1);
    }

    #if defined(MOZ_GECKO_PROFILER) && defined(MOZ_COLLECTING_RUNNABLE_TELEMETRY)
    #  define AUTO_PROFILE_FOLLOWING_TASK(task)                                  \
        nsAutoCString name;                                                      \
        (task)->GetName(name);                                                   \
        AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_NONSENSITIVE("Task", OTHER, name); \
        AUTO_PROFILER_MARKER_TEXT("Runnable", OTHER, {}, name);
    #else
    #  define AUTO_PROFILE_FOLLOWING_TASK(task)
    #endif

    bool TaskManager::
        UpdateCachesForCurrentIterationAndReportPriorityModifierChanged(
            const MutexAutoLock& aProofOfLock, IterationType aIterationType) {
      mCurrentSuspended = IsSuspended(aProofOfLock);

      if (aIterationType == IterationType::EVENT_LOOP_TURN) {
        int32_t oldModifier = mCurrentPriorityModifier;
        mCurrentPriorityModifier =
            GetPriorityModifierForEventLoopTurn(aProofOfLock);

        if (mCurrentPriorityModifier != oldModifier) {
          return true;
        }
      }
      return false;
    }

    Task* Task::GetHighestPriorityDependency() {
      Task* currentTask = this;

      while (!currentTask->mDependencies.empty()) {
        auto iter = currentTask->mDependencies.begin();

        while (iter != currentTask->mDependencies.end()) {
          if ((*iter)->mCompleted) {
            auto oldIter = iter;
            iter++;
            // Completed tasks are removed here to prevent needlessly keeping them
            // alive or iterating over them in the future.
            currentTask->mDependencies.erase(oldIter);
            continue;
          }

          currentTask = iter->get();
          break;
        }
      }

      return currentTask == this ? nullptr : currentTask;
    }

    TaskController* TaskController::Get() {
      MOZ_ASSERT(sSingleton.get());
      return sSingleton.get();
    }

    bool TaskController::Initialize() {
      MOZ_ASSERT(!sSingleton);
      sSingleton = std::make_unique();
      return sSingleton->InitializeInternal();
    }

    void ThreadFuncPoolThread(void* aIndex) {
      mThreadPoolIndex = *reinterpret_cast(aIndex);
      delete reinterpret_cast(aIndex);
      TaskController::Get()->RunPoolThread();
    }

    #ifdef XP_WIN
    static SetThreadDescriptionPtr sSetThreadDescriptionFunc = nullptr;
    #endif

    bool TaskController::InitializeInternal() {
      InputTaskManager::Init();
      mMTProcessingRunnable = NS_NewRunnableFunction(
          "TaskController::ExecutePendingMTTasks()",
          []() { TaskController::Get()->ProcessPendingMTTask(); });
      mMTBlockingProcessingRunnable = NS_NewRunnableFunction(
          "TaskController::ExecutePendingMTTasks()",
          []() { TaskController::Get()->ProcessPendingMTTask(true); });

    #ifdef XP_WIN
      sSetThreadDescriptionFunc =
          reinterpret_cast(::GetProcAddress(
              ::GetModuleHandle(L"Kernel32.dll"), "SetThreadDescription"));
    #endif

      return true;
    }

    // Ample stack size allocated for applications like ImageLib's AV1 decoder.
    const PRUint32 sStackSize = 512u * 1024u;

    void TaskController::InitializeThreadPool() {
      mPoolInitializationMutex.AssertCurrentThreadOwns();
      MOZ_ASSERT(!mThreadPoolInitialized);
      mThreadPoolInitialized = true;

      int32_t poolSize = GetPoolThreadCount();
      for (int32_t i = 0; i < poolSize; i++) {
        int32_t* index = new int32_t(i);
        mPoolThreads.push_back(
            {PR_CreateThread(PR_USER_THREAD, ThreadFuncPoolThread, index,
                             PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD,
                             PR_JOINABLE_THREAD, 512u * 1024u),
             nullptr});
      }
    }

    void TaskController::SetPerformanceCounterState(
        PerformanceCounterState* aPerformanceCounterState) {
      mPerformanceCounterState = aPerformanceCounterState;
    }

    /* static */
    void TaskController::Shutdown() {
      InputTaskManager::Cleanup();
      if (sSingleton) {
        sSingleton->ShutdownThreadPoolInternal();
        sSingleton->ShutdownInternal();
      }
      MOZ_ASSERT(!sSingleton);
    }

    void TaskController::ShutdownThreadPoolInternal() {
      {
        // Prevent racecondition on mShuttingDown and wait.
        MutexAutoLock lock(mGraphMutex);

        mShuttingDown = true;
        mThreadPoolCV.NotifyAll();
      }
      for (PoolThread& thread : mPoolThreads) {
        PR_JoinThread(thread.mThread);
      }
    }

    void TaskController::ShutdownInternal() { sSingleton = nullptr; }

    void TaskController::RunPoolThread() {
      IOInterposer::RegisterCurrentThread();

      // This is used to hold on to a task to make sure it is released outside the
      // lock. This is required since it's perfectly feasible for task destructors
      // to post events themselves.
      RefPtr lastTask;

    #ifdef XP_WIN
      nsAutoString threadWName;
      threadWName.AppendLiteral(u"TaskController Thread #");
      threadWName.AppendInt(static_cast(mThreadPoolIndex));

      if (sSetThreadDescriptionFunc) {
        sSetThreadDescriptionFunc(
            ::GetCurrentThread(),
            reinterpret_cast(threadWName.BeginReading()));
      }
    #endif
      nsAutoCString threadName;
      threadName.AppendLiteral("TaskController Thread #");
      threadName.AppendInt(static_cast(mThreadPoolIndex));
      PROFILER_REGISTER_THREAD(threadName.BeginReading());

      MutexAutoLock lock(mGraphMutex);
      while (true) {
        bool ranTask = false;

        if (!mThreadableTasks.empty()) {
          for (auto iter = mThreadableTasks.begin(); iter != mThreadableTasks.end();
               ++iter) {
            // Search for the highest priority dependency of the highest priority
            // task.

            // We work with rawptrs to avoid needless refcounting. All our tasks
            // are always kept alive by the graph. If one is removed from the graph
            // it is kept alive by mPoolThreads[mThreadPoolIndex].mCurrentTask.
            Task* task = iter->get();

            MOZ_ASSERT(!task->mTaskManager);

            mPoolThreads[mThreadPoolIndex].mEffectiveTaskPriority =
                task->GetPriority();

            Task* nextTask;
            while ((nextTask = task->GetHighestPriorityDependency())) {
              task = nextTask;
            }

            if (task->IsMainThreadOnly() || task->mInProgress) {
              continue;
            }

            mPoolThreads[mThreadPoolIndex].mCurrentTask = task;
            mThreadableTasks.erase(task->mIterator);
            task->mIterator = mThreadableTasks.end();
            task->mInProgress = true;

            bool taskCompleted = false;
            {
              MutexAutoUnlock unlock(mGraphMutex);
              lastTask = nullptr;
              AUTO_PROFILE_FOLLOWING_TASK(task);
              taskCompleted = task->Run();
              ranTask = true;
            }

            task->mInProgress = false;

            if (!taskCompleted) {
              // Presumably this task was interrupted, leave its dependencies
              // unresolved and reinsert into the queue.
              auto insertion = mThreadableTasks.insert(
                  mPoolThreads[mThreadPoolIndex].mCurrentTask);
              MOZ_ASSERT(insertion.second);
              task->mIterator = insertion.first;
            } else {
              task->mCompleted = true;
    #ifdef DEBUG
              task->mIsInGraph = false;
    #endif
              task->mDependencies.clear();
              // This may have unblocked a main thread task. We could do this only
              // if there was a main thread task before this one in the dependency
              // chain.
              mMayHaveMainThreadTask = true;
              // Since this could have multiple dependencies thare are restricted
              // to the main thread. Let's make sure that's awake.
              EnsureMainThreadTasksScheduled();

              MaybeInterruptTask(GetHighestPriorityMTTask());
            }

            // Store last task for release next time we release the lock or enter
            // wait state.
            lastTask = mPoolThreads[mThreadPoolIndex].mCurrentTask.forget();
            break;
          }
        }

        // Ensure the last task is released before we enter the wait state.
        if (lastTask) {
          MutexAutoUnlock unlock(mGraphMutex);
          lastTask = nullptr;

          // Run another loop iteration, while we were unlocked there was an
          // opportunity for another task to be posted or shutdown to be initiated.
          continue;
        }

        if (!ranTask) {
          if (mShuttingDown) {
            IOInterposer::UnregisterCurrentThread();
            MOZ_ASSERT(mThreadableTasks.empty());
            return;
          }

          AUTO_PROFILER_LABEL("TaskController::RunPoolThread", IDLE);
          mThreadPoolCV.Wait();
        }
      }
    }

    void TaskController::AddTask(already_AddRefed&& aTask) {
      RefPtr task(aTask);

      if (!task->IsMainThreadOnly()) {
        MutexAutoLock lock(mPoolInitializationMutex);
        if (!mThreadPoolInitialized) {
          InitializeThreadPool();
          mThreadPoolInitialized = true;
        }
      }

      MutexAutoLock lock(mGraphMutex);

      if (TaskManager* manager = task->GetManager()) {
        if (manager->mTaskCount == 0) {
          mTaskManagers.insert(manager);
        }
        manager->DidQueueTask();

        // Set this here since if this manager's priority modifier doesn't change
        // we will not reprioritize when iterating over the queue.
        task->mPriorityModifier = manager->mCurrentPriorityModifier;
      }

    #ifdef MOZ_GECKO_PROFILER
      task->mInsertionTime = TimeStamp::Now();
    #endif

    #ifdef DEBUG
      task->mIsInGraph = true;

      for (const RefPtr& otherTask : task->mDependencies) {
        MOZ_ASSERT(!otherTask->mTaskManager ||
                   otherTask->mTaskManager == task->mTaskManager);
      }
    #endif

      LogTask::LogDispatch(task);

      std::pair, Task::PriorityCompare>::iterator, bool>
          insertion;
      if (task->IsMainThreadOnly()) {
        insertion = mMainThreadTasks.insert(std::move(task));
      } else {
        insertion = mThreadableTasks.insert(std::move(task));
      }
      (*insertion.first)->mIterator = insertion.first;
      MOZ_ASSERT(insertion.second);

      MaybeInterruptTask(*insertion.first);
    }

    void TaskController::WaitForTaskOrMessage() {
      MutexAutoLock lock(mGraphMutex);
      while (!mMayHaveMainThreadTask) {
        AUTO_PROFILER_LABEL("TaskController::WaitForTaskOrMessage", IDLE);
        mMainThreadCV.Wait();
      }
    }

    void TaskController::ExecuteNextTaskOnlyMainThread() {
      MOZ_ASSERT(NS_IsMainThread());
      MutexAutoLock lock(mGraphMutex);
      ExecuteNextTaskOnlyMainThreadInternal(lock);
    }

    void TaskController::ProcessPendingMTTask(bool aMayWait) {
      MOZ_ASSERT(NS_IsMainThread());
      MutexAutoLock lock(mGraphMutex);

      for (;;) {
        // We only ever process one event here. However we may sometimes
        // not actually process a real event because of suspended tasks.
        // This loop allows us to wait until we've processed something
        // in that scenario.

        mMTTaskRunnableProcessedTask = ExecuteNextTaskOnlyMainThreadInternal(lock);

        if (mMTTaskRunnableProcessedTask || !aMayWait) {
          break;
        }

        BackgroundHangMonitor().NotifyWait();

        {
          // ProcessNextEvent will also have attempted to wait, however we may have
          // given it a Runnable when all the tasks in our task graph were suspended
          // but we weren't able to cheaply determine that.
          AUTO_PROFILER_LABEL("TaskController::ProcessPendingMTTask", IDLE);
          mMainThreadCV.Wait();
        }

        BackgroundHangMonitor().NotifyActivity();
      }

      if (mMayHaveMainThreadTask) {
        EnsureMainThreadTasksScheduled();
      }
    }

    void TaskController::ReprioritizeTask(Task* aTask, uint32_t aPriority) {
      MutexAutoLock lock(mGraphMutex);
      std::set, Task::PriorityCompare>* queue = &mMainThreadTasks;
      if (!aTask->IsMainThreadOnly()) {
        queue = &mThreadableTasks;
      }

      MOZ_ASSERT(aTask->mIterator != queue->end());
      queue->erase(aTask->mIterator);

      aTask->mPriority = aPriority;

      auto insertion = queue->insert(aTask);
      MOZ_ASSERT(insertion.second);
      aTask->mIterator = insertion.first;

      MaybeInterruptTask(aTask);
    }

    // Code supporting runnable compatibility.
    // Task that wraps a runnable.
    class RunnableTask : public Task {
     public:
      RunnableTask(already_AddRefed&& aRunnable, int32_t aPriority,
                   bool aMainThread = true)
          : Task(aMainThread, aPriority), mRunnable(aRunnable) {}

      virtual bool Run() override {
    #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
        MOZ_ASSERT(NS_IsMainThread());
        // If we're on the main thread, we want to record our current
        // runnable's name in a static so that BHR can record it.
        Array restoreRunnableName;
        restoreRunnableName[0] = '\0';
        auto clear = MakeScopeExit([&] {
          MOZ_ASSERT(NS_IsMainThread());
          nsThread::sMainThreadRunnableName = restoreRunnableName;
        });
        nsAutoCString name;
        nsThread::GetLabeledRunnableName(mRunnable, name,
                                         EventQueuePriority(GetPriority()));

        restoreRunnableName = nsThread::sMainThreadRunnableName;

        // Copy the name into sMainThreadRunnableName's buffer, and append a
        // terminating null.
        uint32_t length = std::min((uint32_t)nsThread::kRunnableNameBufSize - 1,
                                   (uint32_t)name.Length());
        memcpy(nsThread::sMainThreadRunnableName.begin(), name.BeginReading(),
               length);
        nsThread::sMainThreadRunnableName[length] = '\0';
    #endif

        mRunnable->Run();
        mRunnable = nullptr;
        return true;
      }

      void SetIdleDeadline(TimeStamp aDeadline) override {
        nsCOMPtr idleRunnable = do_QueryInterface(mRunnable);
        if (idleRunnable) {
          idleRunnable->SetDeadline(aDeadline);
        }
      }

      PerformanceCounter* GetPerformanceCounter() const override {
        return nsThread::GetPerformanceCounterBase(mRunnable);
      }

      virtual bool GetName(nsACString& aName) override {
    #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
        nsThread::GetLabeledRunnableName(mRunnable, aName,
                                         EventQueuePriority(GetPriority()));
        return true;
    #else
        return false;
    #endif
      }

     private:
      RefPtr mRunnable;
    };

    void TaskController::DispatchRunnable(already_AddRefed&& aRunnable,
                                          uint32_t aPriority,
                                          TaskManager* aManager) {
      RefPtr task = new RunnableTask(std::move(aRunnable), aPriority);

      task->SetManager(aManager);
      TaskController::Get()->AddTask(task.forget());
    }

    nsIRunnable* TaskController::GetRunnableForMTTask(bool aReallyWait) {
      MutexAutoLock lock(mGraphMutex);

      while (mMainThreadTasks.empty()) {
        if (!aReallyWait) {
          return nullptr;
        }

        AUTO_PROFILER_LABEL("TaskController::GetRunnableForMTTask::Wait", IDLE);
        mMainThreadCV.Wait();
      }

      return aReallyWait ? mMTBlockingProcessingRunnable : mMTProcessingRunnable;
    }

    bool TaskController::HasMainThreadPendingTasks() {
      auto resetIdleState = MakeScopeExit([&idleManager = mIdleTaskManager] {
        if (idleManager) {
          idleManager->State().ClearCachedIdleDeadline();
        }
      });

      for (bool considerIdle : {false, true}) {
        if (considerIdle && !mIdleTaskManager) {
          continue;
        }

        MutexAutoLock lock(mGraphMutex);

        if (considerIdle) {
          mIdleTaskManager->State().ForgetPendingTaskGuarantee();
          // Temporarily unlock so we can peek our idle deadline.
          // XXX We could do this _before_ we take the lock if the API would let us.
          // We do want to do this before looking at mMainThreadTasks, in case
          // someone adds one while we're unlocked.
          {
            MutexAutoUnlock unlock(mGraphMutex);
            mIdleTaskManager->State().CachePeekedIdleDeadline(unlock);
          }
        }

        // Return early if there's no tasks at all.
        if (mMainThreadTasks.empty()) {
          return false;
        }

        // We can cheaply count how many tasks are suspended.
        uint64_t totalSuspended = 0;
        for (TaskManager* manager : mTaskManagers) {
          DebugOnly modifierChanged =
              manager
                  ->UpdateCachesForCurrentIterationAndReportPriorityModifierChanged(
                      lock, TaskManager::IterationType::NOT_EVENT_LOOP_TURN);
          MOZ_ASSERT(!modifierChanged);

          // The idle manager should be suspended unless we're doing the idle pass.
          MOZ_ASSERT(manager != mIdleTaskManager || manager->mCurrentSuspended ||
                         considerIdle,
                     "Why are idle tasks not suspended here?");

          if (manager->mCurrentSuspended) {
            // XXX - If managers manage off-main-thread tasks this breaks! This
            // scenario is explicitly not supported.
            //
            // This is only incremented inside the lock -or- decremented on the main
            // thread so this is safe.
            totalSuspended += manager->mTaskCount;
          }
        }

        // Thi would break down if we have a non-suspended task depending on a
        // suspended task. This is why for the moment we do not allow tasks
        // to be dependent on tasks managed by another taskmanager.
        if (mMainThreadTasks.size() > totalSuspended) {
          // If mIdleTaskManager->mTaskCount is 0, we never updated the suspended
          // state of mIdleTaskManager above, hence shouldn't even check it here.
          // But in that case idle tasks are not contributing to our suspended task
          // count anyway.
          if (mIdleTaskManager && mIdleTaskManager->mTaskCount &&
              !mIdleTaskManager->mCurrentSuspended) {
            MOZ_ASSERT(considerIdle, "Why is mIdleTaskManager not suspended?");
            // Check whether the idle tasks were really needed to make our "we have
            // an unsuspended task" decision.  If they were, we need to force-enable
            // idle tasks until we run our next task.
            if (mMainThreadTasks.size() - mIdleTaskManager->mTaskCount <=
                totalSuspended) {
              mIdleTaskManager->State().EnforcePendingTaskGuarantee();
            }
          }
          return true;
        }
      }
      return false;
    }

    bool TaskController::ExecuteNextTaskOnlyMainThreadInternal(
        const MutexAutoLock& aProofOfLock) {
      // Block to make it easier to jump to our cleanup.
      bool taskRan = false;
      do {
        taskRan = DoExecuteNextTaskOnlyMainThreadInternal(aProofOfLock);
        if (taskRan) {
          break;
        }

        if (!mIdleTaskManager) {
          break;
        }

        if (mIdleTaskManager->mTaskCount) {
          // We have idle tasks that we may not have gotten above because
          // our idle state is not up to date.  We need to update the idle state
          // and try again.  We need to temporarily release the lock while we do
          // that.
          MutexAutoUnlock unlock(mGraphMutex);
          mIdleTaskManager->State().UpdateCachedIdleDeadline(unlock);
        } else {
          MutexAutoUnlock unlock(mGraphMutex);
          mIdleTaskManager->State().RanOutOfTasks(unlock);
        }

        // When we unlocked, someone may have queued a new task on us.  So try to
        // see whether we can run things again.
        taskRan = DoExecuteNextTaskOnlyMainThreadInternal(aProofOfLock);
      } while (false);

      if (mIdleTaskManager) {
        // The pending task guarantee is not needed anymore, since we just tried
        // running a task
        mIdleTaskManager->State().ForgetPendingTaskGuarantee();

        if (mMainThreadTasks.empty()) {
          // XXX the IdlePeriodState API demands we have a MutexAutoUnlock for it.
          // Otherwise we could perhaps just do this after we exit the locked block,
          // by pushing the lock down into this method.  Though it's not clear that
          // we could check mMainThreadTasks.size() once we unlock, and whether we
          // could maybe substitute mMayHaveMainThreadTask for that check.
          MutexAutoUnlock unlock(mGraphMutex);
          mIdleTaskManager->State().RanOutOfTasks(unlock);
        }
      }

      return taskRan;
    }

    bool TaskController::DoExecuteNextTaskOnlyMainThreadInternal(
        const MutexAutoLock& aProofOfLock) {
    #ifdef MOZ_GECKO_PROFILER
      nsCOMPtr mainIThread;
      NS_GetMainThread(getter_AddRefs(mainIThread));

      nsThread* mainThread = static_cast(mainIThread.get());
      if (mainThread) {
        mainThread->SetRunningEventDelay(TimeDuration(), TimeStamp());
      }
    #endif

      uint32_t totalSuspended = 0;
      for (TaskManager* manager : mTaskManagers) {
        bool modifierChanged =
            manager
                ->UpdateCachesForCurrentIterationAndReportPriorityModifierChanged(
                    aProofOfLock, TaskManager::IterationType::EVENT_LOOP_TURN);
        if (modifierChanged) {
          ProcessUpdatedPriorityModifier(manager);
        }
        if (manager->mCurrentSuspended) {
          totalSuspended += manager->mTaskCount;
        }
      }

      MOZ_ASSERT(mMainThreadTasks.size() >= totalSuspended);

      // This would break down if we have a non-suspended task depending on a
      // suspended task. This is why for the moment we do not allow tasks
      // to be dependent on tasks managed by another taskmanager.
      if (mMainThreadTasks.size() > totalSuspended) {
        for (auto iter = mMainThreadTasks.begin(); iter != mMainThreadTasks.end();
             iter++) {
          Task* task = iter->get();

          if (task->mTaskManager && task->mTaskManager->mCurrentSuspended) {
            // Even though we may want to run some dependencies of this task, we
            // will run them at their own priority level and not the priority
            // level of their dependents.
            continue;
          }

          task = GetFinalDependency(task);

          if (!task->IsMainThreadOnly() || task->mInProgress ||
              (task->mTaskManager && task->mTaskManager->mCurrentSuspended)) {
            continue;
          }

          mCurrentTasksMT.push(task);
          mMainThreadTasks.erase(task->mIterator);
          task->mIterator = mMainThreadTasks.end();
          task->mInProgress = true;
          TaskManager* manager = task->GetManager();
          bool result = false;

          {
            MutexAutoUnlock unlock(mGraphMutex);
            if (manager) {
              manager->WillRunTask();
              if (manager != mIdleTaskManager) {
                // Notify the idle period state that we're running a non-idle task.
                // This needs to happen while our mutex is not locked!
                mIdleTaskManager->State().FlagNotIdle();
              } else {
                TimeStamp idleDeadline =
                    mIdleTaskManager->State().GetCachedIdleDeadline();
                MOZ_ASSERT(
                    idleDeadline,
                    "How can we not have a deadline if our manager is enabled?");
                task->SetIdleDeadline(idleDeadline);
              }
            }
            if (mIdleTaskManager) {
              // We found a task to run; we can clear the idle deadline on our idle
              // task manager.  This _must_ be done before we actually run the task,
              // because running the task could reenter via spinning the event loop
              // and we want to make sure there's no cached idle deadline at that
              // point.  But we have to make sure we do it after out SetIdleDeadline
              // call above, in the case when the task is actually an idle task.
              mIdleTaskManager->State().ClearCachedIdleDeadline();
            }

            TimeStamp now = TimeStamp::Now();

    #ifdef MOZ_GECKO_PROFILER
            if (mainThread) {
              if (task->GetPriority() < uint32_t(EventQueuePriority::InputHigh)) {
                mainThread->SetRunningEventDelay(TimeDuration(), now);
              } else {
                mainThread->SetRunningEventDelay(now - task->mInsertionTime, now);
              }
            }
    #endif

            PerformanceCounterState::Snapshot snapshot =
                mPerformanceCounterState->RunnableWillRun(
                    task->GetPerformanceCounter(), now,
                    manager == mIdleTaskManager);

            {
              LogTask::Run log(task);
              AUTO_PROFILE_FOLLOWING_TASK(task);
              result = task->Run();
            }

            // Task itself should keep manager alive.
            if (manager) {
              manager->DidRunTask();
            }

            mPerformanceCounterState->RunnableDidRun(std::move(snapshot));
          }

          // Task itself should keep manager alive.
          if (manager && result && manager->mTaskCount == 0) {
            mTaskManagers.erase(manager);
          }

          task->mInProgress = false;

          if (!result) {
            // Presumably this task was interrupted, leave its dependencies
            // unresolved and reinsert into the queue.
            auto insertion =
                mMainThreadTasks.insert(std::move(mCurrentTasksMT.top()));
            MOZ_ASSERT(insertion.second);
            task->mIterator = insertion.first;
            manager->WillRunTask();
          } else {
            task->mCompleted = true;
    #ifdef DEBUG
            task->mIsInGraph = false;
    #endif
            // Clear dependencies to release references.
            task->mDependencies.clear();

            if (!mThreadableTasks.empty()) {
              // Since this could have multiple dependencies thare are not
              // restricted to the main thread. Let's wake up our thread pool.
              // There is a cost to this, it's possible we will want to wake up
              // only as many threads as we have unblocked tasks, but we currently
              // have no way to determine that easily.
              mThreadPoolCV.NotifyAll();
            }
          }

          mCurrentTasksMT.pop();
          return true;
        }
      }

      mMayHaveMainThreadTask = false;
      if (mIdleTaskManager) {
        // We did not find a task to run.  We still need to clear the cached idle
        // deadline on our idle state, because that deadline was only relevant to
        // the execution of this function.  Had we found a task, we would have
        // cleared the deadline before running that task.
        mIdleTaskManager->State().ClearCachedIdleDeadline();
      }
      return false;
    }

    Task* TaskController::GetFinalDependency(Task* aTask) {
      Task* nextTask;

      while ((nextTask = aTask->GetHighestPriorityDependency())) {
        aTask = nextTask;
      }

      return aTask;
    }

    void TaskController::MaybeInterruptTask(Task* aTask) {
      mGraphMutex.AssertCurrentThreadOwns();

      if (!aTask) {
        return;
      }

      // This optimization prevents many slow lookups in long chains of similar
      // priority.
      if (!aTask->mDependencies.empty()) {
        Task* firstDependency = aTask->mDependencies.begin()->get();
        if (aTask->GetPriority() <= firstDependency->GetPriority() &&
            !firstDependency->mCompleted &&
            aTask->IsMainThreadOnly() == firstDependency->IsMainThreadOnly()) {
          // This task has the same or a higher priority as one of its dependencies,
          // never any need to interrupt.
          return;
        }
      }

      Task* finalDependency = GetFinalDependency(aTask);

      if (finalDependency->mInProgress) {
        // No need to wake anything, we can't schedule this task right now anyway.
        return;
      }

      if (aTask->IsMainThreadOnly()) {
        mMayHaveMainThreadTask = true;

        EnsureMainThreadTasksScheduled();

        if (mCurrentTasksMT.empty()) {
          return;
        }

        // We could go through the steps above here and interrupt an off main
        // thread task in case it has a lower priority.
        if (!finalDependency->IsMainThreadOnly()) {
          return;
        }

        if (mCurrentTasksMT.top()->GetPriority() < aTask->GetPriority()) {
          mCurrentTasksMT.top()->RequestInterrupt(aTask->GetPriority());
        }
      } else {
        Task* lowestPriorityTask = nullptr;
        for (PoolThread& thread : mPoolThreads) {
          if (!thread.mCurrentTask) {
            mThreadPoolCV.Notify();
            // There's a free thread, no need to interrupt anything.
            return;
          }

          if (!lowestPriorityTask) {
            lowestPriorityTask = thread.mCurrentTask.get();
            continue;
          }

          // This should possibly select the lowest priority task which was started
          // the latest. But for now we ignore that optimization.
          // This also doesn't guarantee a task is interruptable, so that's an
          // avenue for improvements as well.
          if (lowestPriorityTask->GetPriority() > thread.mEffectiveTaskPriority) {
            lowestPriorityTask = thread.mCurrentTask.get();
          }
        }

        if (lowestPriorityTask->GetPriority() < aTask->GetPriority()) {
          lowestPriorityTask->RequestInterrupt(aTask->GetPriority());
        }

        // We choose not to interrupt main thread tasks for tasks which may be
        // executed off the main thread.
      }
    }

    Task* TaskController::GetHighestPriorityMTTask() {
      mGraphMutex.AssertCurrentThreadOwns();

      if (!mMainThreadTasks.empty()) {
        return mMainThreadTasks.begin()->get();
      }
      return nullptr;
    }

    void TaskController::EnsureMainThreadTasksScheduled() {
      if (mObserver) {
        mObserver->OnDispatchedEvent();
      }
      if (mExternalCondVar) {
        mExternalCondVar->Notify();
      }
      mMainThreadCV.Notify();
    }

    void TaskController::ProcessUpdatedPriorityModifier(TaskManager* aManager) {
      mGraphMutex.AssertCurrentThreadOwns();

      MOZ_ASSERT(NS_IsMainThread());

      int32_t modifier = aManager->mCurrentPriorityModifier;

      std::vector> storedTasks;
      // Find all relevant tasks.
      for (auto iter = mMainThreadTasks.begin(); iter != mMainThreadTasks.end();) {
        if ((*iter)->mTaskManager == aManager) {
          storedTasks.push_back(*iter);
          iter = mMainThreadTasks.erase(iter);
        } else {
          iter++;
        }
      }

      // Reinsert found tasks with their new priorities.
      for (RefPtr& ref : storedTasks) {
        // Kept alive at first by the vector and then by mMainThreadTasks.
        Task* task = ref;
        task->mPriorityModifier = modifier;
        auto insertion = mMainThreadTasks.insert(std::move(ref));
        MOZ_ASSERT(insertion.second);
        task->mIterator = insertion.first;
      }
    }

    }  // namespace mozilla