Bug 1867190 - Initialise the PHC allocate delay later r=glandium
[gecko.git] / xpcom / threads / TaskController.h
blobf51e61f4cd6556f31996c6840481d517703ac4cc
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 #ifndef mozilla_TaskController_h
8 #define mozilla_TaskController_h
10 #include "MainThreadUtils.h"
11 #include "mozilla/CondVar.h"
12 #include "mozilla/IdlePeriodState.h"
13 #include "mozilla/RefPtr.h"
14 #include "mozilla/Mutex.h"
15 #include "mozilla/StaticPtr.h"
16 #include "mozilla/TimeStamp.h"
17 #include "mozilla/EventQueue.h"
18 #include "nsISupportsImpl.h"
20 #include <atomic>
21 #include <vector>
22 #include <set>
23 #include <stack>
25 class nsIRunnable;
26 class nsIThreadObserver;
28 namespace mozilla {
30 class Task;
31 class TaskController;
32 class PerformanceCounter;
33 class PerformanceCounterState;
35 const EventQueuePriority kDefaultPriorityValue = EventQueuePriority::Normal;
37 // This file contains the core classes to access the Gecko scheduler. The
38 // scheduler forms a graph of prioritize tasks, and is responsible for ensuring
39 // the execution of tasks or their dependencies in order of inherited priority.
41 // The core class is the 'Task' class. The task class describes a single unit of
42 // work. Users scheduling work implement this class and are required to
43 // reimplement the 'Run' function in order to do work.
45 // The TaskManager class is reimplemented by users that require
46 // the ability to reprioritize or suspend tasks.
48 // The TaskController is responsible for scheduling the work itself. The AddTask
49 // function is used to schedule work. The ReprioritizeTask function may be used
50 // to change the priority of a task already in the task graph, without
51 // unscheduling it.
53 // The TaskManager is the baseclass used to atomically manage a large set of
54 // tasks. API users reimplementing TaskManager may reimplement a number of
55 // functions that they may use to indicate to the scheduler changes in the state
56 // for any tasks they manage. They may be used to reprioritize or suspend tasks
57 // under their control, and will also be notified before and after tasks under
58 // their control are executed. Their methods will only be called once per event
59 // loop turn, however they may still incur some performance overhead. In
60 // addition to this frequent reprioritizations may incur a significant
61 // performance overhead and are discouraged. A TaskManager may currently only be
62 // used to manage tasks that are bound to the Gecko Main Thread.
63 class TaskManager {
64 public:
65 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TaskManager)
67 TaskManager() : mTaskCount(0) {}
69 // Subclasses implementing task manager will have this function called to
70 // determine whether their associated tasks are currently suspended. This
71 // will only be called once per iteration of the task queue, this means that
72 // suspension of tasks managed by a single TaskManager may be assumed to
73 // occur atomically.
74 virtual bool IsSuspended(const MutexAutoLock& aProofOfLock) { return false; }
76 // Subclasses may implement this in order to supply a priority adjustment
77 // to their managed tasks. This is called once per iteration of the task
78 // queue, and may be assumed to occur atomically for all managed tasks.
79 virtual int32_t GetPriorityModifierForEventLoopTurn(
80 const MutexAutoLock& aProofOfLock) {
81 return 0;
84 void DidQueueTask() { ++mTaskCount; }
85 // This is called when a managed task is about to be executed by the
86 // scheduler. Anyone reimplementing this should ensure to call the parent or
87 // decrement mTaskCount.
88 virtual void WillRunTask() { --mTaskCount; }
89 // This is called when a managed task has finished being executed by the
90 // scheduler.
91 virtual void DidRunTask() {}
92 uint32_t PendingTaskCount() { return mTaskCount; }
94 protected:
95 virtual ~TaskManager() {}
97 private:
98 friend class TaskController;
100 enum class IterationType { NOT_EVENT_LOOP_TURN, EVENT_LOOP_TURN };
101 bool UpdateCachesForCurrentIterationAndReportPriorityModifierChanged(
102 const MutexAutoLock& aProofOfLock, IterationType aIterationType);
104 bool mCurrentSuspended = false;
105 int32_t mCurrentPriorityModifier = 0;
107 std::atomic<uint32_t> mTaskCount;
110 // A Task is the the base class for any unit of work that may be scheduled.
112 // Subclasses may specify their priority and whether they should be bound to
113 // either the Gecko Main thread or off main thread. When not bound to the main
114 // thread tasks may be executed on any available thread excluding the main
115 // thread, but they may also be executed in parallel to any other task they do
116 // not have a dependency relationship with.
118 // Tasks will be run in order of object creation.
119 class Task {
120 public:
121 enum class Kind : uint8_t {
122 // This task should be executed on any available thread excluding the Gecko
123 // Main thread.
124 OffMainThreadOnly,
126 // This task should be executed on the Gecko Main thread.
127 MainThreadOnly
129 // NOTE: "any available thread including the main thread" option is not
130 // supported (See bug 1839102).
133 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Task)
135 Kind GetKind() { return mKind; }
137 // This returns the current task priority with its modifier applied.
138 uint32_t GetPriority() { return mPriority + mPriorityModifier; }
139 uint64_t GetSeqNo() { return mSeqNo; }
141 // Callee needs to assume this may be called on any thread.
142 // aInterruptPriority passes the priority of the higher priority task that
143 // is ready to be executed. The task may safely ignore this function, or
144 // interrupt any work being done. It may return 'false' from its run function
145 // in order to be run automatically in the future, or true if it will
146 // reschedule incomplete work manually.
147 virtual void RequestInterrupt(uint32_t aInterruptPriority) {}
149 // At the moment this -must- be called before the task is added to the
150 // controller. Calling this after tasks have been added to the controller
151 // results in undefined behavior!
152 // At submission, tasks must depend only on tasks managed by the same, or
153 // no idle manager.
154 void AddDependency(Task* aTask) {
155 MOZ_ASSERT(aTask);
156 MOZ_ASSERT(!mIsInGraph);
157 mDependencies.insert(aTask);
160 // This sets the TaskManager for the current task. Calling this after the
161 // task has been added to the TaskController results in undefined behavior.
162 void SetManager(TaskManager* aManager) {
163 MOZ_ASSERT(mKind == Kind::MainThreadOnly);
164 MOZ_ASSERT(!mIsInGraph);
165 mTaskManager = aManager;
167 TaskManager* GetManager() { return mTaskManager; }
169 struct PriorityCompare {
170 bool operator()(const RefPtr<Task>& aTaskA,
171 const RefPtr<Task>& aTaskB) const {
172 uint32_t prioA = aTaskA->GetPriority();
173 uint32_t prioB = aTaskB->GetPriority();
174 return (prioA > prioB) ||
175 (prioA == prioB && (aTaskA->GetSeqNo() < aTaskB->GetSeqNo()));
179 // Tell the task about its idle deadline. Will only be called for
180 // tasks managed by an IdleTaskManager, right before the task runs.
181 virtual void SetIdleDeadline(TimeStamp aDeadline) {}
183 virtual PerformanceCounter* GetPerformanceCounter() const { return nullptr; }
185 // Get a name for this task. This returns false if the task has no name.
186 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
187 virtual bool GetName(nsACString& aName) = 0;
188 #else
189 virtual bool GetName(nsACString& aName) { return false; }
190 #endif
192 protected:
193 Task(Kind aKind,
194 uint32_t aPriority = static_cast<uint32_t>(kDefaultPriorityValue))
195 : mKind(aKind), mSeqNo(sCurrentTaskSeqNo++), mPriority(aPriority) {}
197 Task(Kind aKind, EventQueuePriority aPriority = kDefaultPriorityValue)
198 : mKind(aKind),
199 mSeqNo(sCurrentTaskSeqNo++),
200 mPriority(static_cast<uint32_t>(aPriority)) {}
202 virtual ~Task() {}
204 friend class TaskController;
206 enum class TaskResult {
207 Complete,
208 Incomplete,
211 // When this returns TaskResult::Incomplete, it will be rescheduled at the
212 // current 'mPriority' level.
213 virtual TaskResult Run() = 0;
215 private:
216 Task* GetHighestPriorityDependency();
218 // Iterator pointing to this task's position in
219 // mThreadableTasks/mMainThreadTasks if, and only if this task is currently
220 // scheduled to be executed. This allows fast access to the task's position
221 // in the set, allowing for fast removal.
222 // This is safe, and remains valid unless the task is removed from the set.
223 // See also iterator invalidation in:
224 // https://en.cppreference.com/w/cpp/container
226 // Or the spec:
227 // "All Associative Containers: The insert and emplace members shall not
228 // affect the validity of iterators and references to the container
229 // [26.2.6/9]" "All Associative Containers: The erase members shall invalidate
230 // only iterators and references to the erased elements [26.2.6/9]"
231 std::set<RefPtr<Task>, PriorityCompare>::iterator mIterator;
232 std::set<RefPtr<Task>, PriorityCompare> mDependencies;
234 RefPtr<TaskManager> mTaskManager;
236 // Access to these variables is protected by the GraphMutex.
237 Kind mKind;
238 bool mCompleted = false;
239 bool mInProgress = false;
240 #ifdef DEBUG
241 bool mIsInGraph = false;
242 #endif
244 static std::atomic<uint64_t> sCurrentTaskSeqNo;
245 int64_t mSeqNo;
246 uint32_t mPriority;
247 // Modifier currently being applied to this task by its taskmanager.
248 int32_t mPriorityModifier = 0;
249 // Time this task was inserted into the task graph, this is used by the
250 // profiler.
251 mozilla::TimeStamp mInsertionTime;
254 struct PoolThread {
255 PRThread* mThread;
256 RefPtr<Task> mCurrentTask;
257 // This may be higher than mCurrentTask's priority due to priority
258 // propagation. This is -only- valid when mCurrentTask != nullptr.
259 uint32_t mEffectiveTaskPriority;
262 // A task manager implementation for priority levels that should only
263 // run during idle periods.
264 class IdleTaskManager : public TaskManager {
265 public:
266 explicit IdleTaskManager(already_AddRefed<nsIIdlePeriod>&& aIdlePeriod)
267 : mIdlePeriodState(std::move(aIdlePeriod)), mProcessedTaskCount(0) {}
269 IdlePeriodState& State() { return mIdlePeriodState; }
271 bool IsSuspended(const MutexAutoLock& aProofOfLock) override {
272 TimeStamp idleDeadline = State().GetCachedIdleDeadline();
273 return !idleDeadline;
276 void DidRunTask() override {
277 TaskManager::DidRunTask();
278 ++mProcessedTaskCount;
281 uint64_t ProcessedTaskCount() { return mProcessedTaskCount; }
283 private:
284 // Tracking of our idle state of various sorts.
285 IdlePeriodState mIdlePeriodState;
287 std::atomic<uint64_t> mProcessedTaskCount;
290 // The TaskController is the core class of the scheduler. It is used to
291 // schedule tasks to be executed, as well as to reprioritize tasks that have
292 // already been scheduled. The core functions to do this are AddTask and
293 // ReprioritizeTask.
294 class TaskController {
295 public:
296 TaskController();
298 static TaskController* Get() {
299 MOZ_ASSERT(sSingleton.get());
300 return sSingleton.get();
303 static void Initialize();
305 void SetThreadObserver(nsIThreadObserver* aObserver) {
306 MutexAutoLock lock(mGraphMutex);
307 mObserver = aObserver;
309 void SetConditionVariable(CondVar* aExternalCondVar) {
310 MutexAutoLock lock(mGraphMutex);
311 mExternalCondVar = aExternalCondVar;
314 void SetIdleTaskManager(IdleTaskManager* aIdleTaskManager) {
315 mIdleTaskManager = aIdleTaskManager;
317 IdleTaskManager* GetIdleTaskManager() { return mIdleTaskManager.get(); }
319 uint64_t RunOutOfMTTasksCount() { return mRunOutOfMTTasksCounter; }
321 // Initialization and shutdown code.
322 void SetPerformanceCounterState(
323 PerformanceCounterState* aPerformanceCounterState);
325 static void Shutdown();
327 // This adds a task to the TaskController graph.
328 // This may be called on any thread.
329 void AddTask(already_AddRefed<Task>&& aTask);
331 // This wait function is the theoretical function you would need if our main
332 // thread needs to also process OS messages or something along those lines.
333 void WaitForTaskOrMessage();
335 // This gets the next (highest priority) task that is only allowed to execute
336 // on the main thread.
337 void ExecuteNextTaskOnlyMainThread();
339 // Process all pending main thread tasks.
340 void ProcessPendingMTTask(bool aMayWait = false);
342 // This allows reprioritization of a task already in the task graph.
343 // This may be called on any thread.
344 void ReprioritizeTask(Task* aTask, uint32_t aPriority);
346 void DispatchRunnable(already_AddRefed<nsIRunnable>&& aRunnable,
347 uint32_t aPriority, TaskManager* aManager = nullptr);
349 nsIRunnable* GetRunnableForMTTask(bool aReallyWait);
351 bool HasMainThreadPendingTasks();
353 uint64_t PendingMainthreadTaskCountIncludingSuspended();
355 // Let users know whether the last main thread task runnable did work.
356 bool MTTaskRunnableProcessedTask() {
357 MOZ_ASSERT(NS_IsMainThread());
358 return mMTTaskRunnableProcessedTask;
361 static int32_t GetPoolThreadCount();
362 static size_t GetThreadStackSize();
364 private:
365 friend void ThreadFuncPoolThread(void* aIndex);
366 static StaticAutoPtr<TaskController> sSingleton;
368 void InitializeThreadPool();
370 // This gets the next (highest priority) task that is only allowed to execute
371 // on the main thread, if any, and executes it.
372 // Returns true if it succeeded.
373 bool ExecuteNextTaskOnlyMainThreadInternal(const MutexAutoLock& aProofOfLock);
375 // The guts of ExecuteNextTaskOnlyMainThreadInternal, which get idle handling
376 // wrapped around them. Returns whether a task actually ran.
377 bool DoExecuteNextTaskOnlyMainThreadInternal(
378 const MutexAutoLock& aProofOfLock);
380 Task* GetFinalDependency(Task* aTask);
381 void MaybeInterruptTask(Task* aTask);
382 Task* GetHighestPriorityMTTask();
384 void EnsureMainThreadTasksScheduled();
386 void ProcessUpdatedPriorityModifier(TaskManager* aManager);
388 void ShutdownThreadPoolInternal();
390 void RunPoolThread();
392 // This protects access to the task graph.
393 Mutex mGraphMutex MOZ_UNANNOTATED;
395 // This protects thread pool initialization. We cannot do this from within
396 // the GraphMutex, since thread creation on Windows can generate events on
397 // the main thread that need to be handled.
398 Mutex mPoolInitializationMutex =
399 Mutex("TaskController::mPoolInitializationMutex");
400 // Created under the PoolInitialization mutex, then never extended, and
401 // only freed when the object is freed. mThread is set at creation time;
402 // mCurrentTask and mEffectiveTaskPriority are only accessed from the
403 // thread, so no locking is needed to access this.
404 std::vector<PoolThread> mPoolThreads;
406 CondVar mThreadPoolCV;
407 CondVar mMainThreadCV;
409 // Variables below are protected by mGraphMutex.
411 std::stack<RefPtr<Task>> mCurrentTasksMT;
413 // A list of all tasks ordered by priority.
414 std::set<RefPtr<Task>, Task::PriorityCompare> mThreadableTasks;
415 std::set<RefPtr<Task>, Task::PriorityCompare> mMainThreadTasks;
417 // TaskManagers currently active.
418 // We can use a raw pointer since tasks always hold on to their TaskManager.
419 std::set<TaskManager*> mTaskManagers;
421 // This ensures we keep running the main thread if we processed a task there.
422 bool mMayHaveMainThreadTask = true;
423 bool mShuttingDown = false;
425 // This stores whether the last main thread task runnable did work.
426 // Accessed only on MainThread
427 bool mMTTaskRunnableProcessedTask = false;
429 // Whether our thread pool is initialized. We use this currently to avoid
430 // starting the threads in processes where it's never used. This is protected
431 // by mPoolInitializationMutex.
432 bool mThreadPoolInitialized = false;
434 // Whether we have scheduled a runnable on the main thread event loop.
435 // This is used for nsIRunnable compatibility.
436 RefPtr<nsIRunnable> mMTProcessingRunnable;
437 RefPtr<nsIRunnable> mMTBlockingProcessingRunnable;
439 // XXX - Thread observer to notify when a new event has been dispatched
440 // Set immediately, then simply accessed from any thread
441 nsIThreadObserver* mObserver = nullptr;
442 // XXX - External condvar to notify when we have received an event
443 CondVar* mExternalCondVar = nullptr;
444 // Idle task manager so we can properly do idle state stuff.
445 RefPtr<IdleTaskManager> mIdleTaskManager;
447 // How many times the main thread was empty.
448 std::atomic<uint64_t> mRunOutOfMTTasksCounter;
450 // Our tracking of our performance counter and long task state,
451 // shared with nsThread.
452 // Set once when MainThread is created, never changed, only accessed from
453 // DoExecuteNextTaskOnlyMainThreadInternal()
454 PerformanceCounterState* mPerformanceCounterState = nullptr;
457 } // namespace mozilla
459 #endif // mozilla_TaskController_h