no bug - Import translations from android-l10n r=release a=l10n CLOSED TREE
[gecko.git] / xpcom / threads / TaskController.h
blobdfca618340ddce175689dedc5e8ab415a2eb0e6c
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 "mozilla/UniquePtr.h"
19 #include "nsISupportsImpl.h"
20 #include "nsThreadUtils.h" // for MOZ_COLLECTING_RUNNABLE_TELEMETRY
22 #include <atomic>
23 #include <vector>
24 #include <set>
25 #include <stack>
27 class nsIRunnable;
28 class nsIThreadObserver;
30 namespace mozilla {
32 class Task;
33 class TaskController;
34 class PerformanceCounter;
35 class PerformanceCounterState;
36 struct PoolThread;
38 const EventQueuePriority kDefaultPriorityValue = EventQueuePriority::Normal;
40 // This file contains the core classes to access the Gecko scheduler. The
41 // scheduler forms a graph of prioritize tasks, and is responsible for ensuring
42 // the execution of tasks or their dependencies in order of inherited priority.
44 // The core class is the 'Task' class. The task class describes a single unit of
45 // work. Users scheduling work implement this class and are required to
46 // reimplement the 'Run' function in order to do work.
48 // The TaskManager class is reimplemented by users that require
49 // the ability to reprioritize or suspend tasks.
51 // The TaskController is responsible for scheduling the work itself. The AddTask
52 // function is used to schedule work. The ReprioritizeTask function may be used
53 // to change the priority of a task already in the task graph, without
54 // unscheduling it.
56 // The TaskManager is the baseclass used to atomically manage a large set of
57 // tasks. API users reimplementing TaskManager may reimplement a number of
58 // functions that they may use to indicate to the scheduler changes in the state
59 // for any tasks they manage. They may be used to reprioritize or suspend tasks
60 // under their control, and will also be notified before and after tasks under
61 // their control are executed. Their methods will only be called once per event
62 // loop turn, however they may still incur some performance overhead. In
63 // addition to this frequent reprioritizations may incur a significant
64 // performance overhead and are discouraged. A TaskManager may currently only be
65 // used to manage tasks that are bound to the Gecko Main Thread.
66 class TaskManager {
67 public:
68 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TaskManager)
70 TaskManager() : mTaskCount(0) {}
72 // Subclasses implementing task manager will have this function called to
73 // determine whether their associated tasks are currently suspended. This
74 // will only be called once per iteration of the task queue, this means that
75 // suspension of tasks managed by a single TaskManager may be assumed to
76 // occur atomically.
77 virtual bool IsSuspended(const MutexAutoLock& aProofOfLock) { return false; }
79 // Subclasses may implement this in order to supply a priority adjustment
80 // to their managed tasks. This is called once per iteration of the task
81 // queue, and may be assumed to occur atomically for all managed tasks.
82 virtual int32_t GetPriorityModifierForEventLoopTurn(
83 const MutexAutoLock& aProofOfLock) {
84 return 0;
87 void DidQueueTask() { ++mTaskCount; }
88 // This is called when a managed task is about to be executed by the
89 // scheduler. Anyone reimplementing this should ensure to call the parent or
90 // decrement mTaskCount.
91 virtual void WillRunTask() { --mTaskCount; }
92 // This is called when a managed task has finished being executed by the
93 // scheduler.
94 virtual void DidRunTask() {}
95 uint32_t PendingTaskCount() { return mTaskCount; }
97 protected:
98 virtual ~TaskManager() {}
100 private:
101 friend class TaskController;
103 enum class IterationType { NOT_EVENT_LOOP_TURN, EVENT_LOOP_TURN };
104 bool UpdateCachesForCurrentIterationAndReportPriorityModifierChanged(
105 const MutexAutoLock& aProofOfLock, IterationType aIterationType);
107 bool mCurrentSuspended = false;
108 int32_t mCurrentPriorityModifier = 0;
110 std::atomic<uint32_t> mTaskCount;
113 // A Task is the the base class for any unit of work that may be scheduled.
115 // Subclasses may specify their priority and whether they should be bound to
116 // either the Gecko Main thread or off main thread. When not bound to the main
117 // thread tasks may be executed on any available thread excluding the main
118 // thread, but they may also be executed in parallel to any other task they do
119 // not have a dependency relationship with.
121 // Tasks will be run in order of object creation.
122 class Task {
123 public:
124 enum class Kind : uint8_t {
125 // This task should be executed on any available thread excluding the Gecko
126 // Main thread.
127 OffMainThreadOnly,
129 // This task should be executed on the Gecko Main thread.
130 MainThreadOnly
132 // NOTE: "any available thread including the main thread" option is not
133 // supported (See bug 1839102).
136 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Task)
138 Kind GetKind() { return mKind; }
140 // This returns the current task priority with its modifier applied.
141 uint32_t GetPriority() { return mPriority + mPriorityModifier; }
142 uint64_t GetSeqNo() { return mSeqNo; }
144 // Callee needs to assume this may be called on any thread.
145 // aInterruptPriority passes the priority of the higher priority task that
146 // is ready to be executed. The task may safely ignore this function, or
147 // interrupt any work being done. It may return 'false' from its run function
148 // in order to be run automatically in the future, or true if it will
149 // reschedule incomplete work manually.
150 virtual void RequestInterrupt(uint32_t aInterruptPriority) {}
152 // At the moment this -must- be called before the task is added to the
153 // controller. Calling this after tasks have been added to the controller
154 // results in undefined behavior!
155 // At submission, tasks must depend only on tasks managed by the same, or
156 // no idle manager.
157 void AddDependency(Task* aTask) {
158 MOZ_ASSERT(aTask);
159 MOZ_ASSERT(!mIsInGraph);
160 mDependencies.insert(aTask);
163 // This sets the TaskManager for the current task. Calling this after the
164 // task has been added to the TaskController results in undefined behavior.
165 void SetManager(TaskManager* aManager) {
166 MOZ_ASSERT(mKind == Kind::MainThreadOnly);
167 MOZ_ASSERT(!mIsInGraph);
168 mTaskManager = aManager;
170 TaskManager* GetManager() { return mTaskManager; }
172 struct PriorityCompare {
173 bool operator()(const RefPtr<Task>& aTaskA,
174 const RefPtr<Task>& aTaskB) const {
175 uint32_t prioA = aTaskA->GetPriority();
176 uint32_t prioB = aTaskB->GetPriority();
177 return (prioA > prioB) ||
178 (prioA == prioB && (aTaskA->GetSeqNo() < aTaskB->GetSeqNo()));
182 // Tell the task about its idle deadline. Will only be called for
183 // tasks managed by an IdleTaskManager, right before the task runs.
184 virtual void SetIdleDeadline(TimeStamp aDeadline) {}
186 virtual PerformanceCounter* GetPerformanceCounter() const { return nullptr; }
188 // Get a name for this task. This returns false if the task has no name.
189 #ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
190 virtual bool GetName(nsACString& aName) = 0;
191 #else
192 virtual bool GetName(nsACString& aName) { return false; }
193 #endif
195 protected:
196 Task(Kind aKind,
197 uint32_t aPriority = static_cast<uint32_t>(kDefaultPriorityValue))
198 : mKind(aKind), mSeqNo(sCurrentTaskSeqNo++), mPriority(aPriority) {}
200 Task(Kind aKind, EventQueuePriority aPriority = kDefaultPriorityValue)
201 : mKind(aKind),
202 mSeqNo(sCurrentTaskSeqNo++),
203 mPriority(static_cast<uint32_t>(aPriority)) {}
205 virtual ~Task() {}
207 friend class TaskController;
209 enum class TaskResult {
210 Complete,
211 Incomplete,
214 // When this returns TaskResult::Incomplete, it will be rescheduled at the
215 // current 'mPriority' level.
216 virtual TaskResult Run() = 0;
218 private:
219 Task* GetHighestPriorityDependency();
221 // Iterator pointing to this task's position in
222 // mThreadableTasks/mMainThreadTasks if, and only if this task is currently
223 // scheduled to be executed. This allows fast access to the task's position
224 // in the set, allowing for fast removal.
225 // This is safe, and remains valid unless the task is removed from the set.
226 // See also iterator invalidation in:
227 // https://en.cppreference.com/w/cpp/container
229 // Or the spec:
230 // "All Associative Containers: The insert and emplace members shall not
231 // affect the validity of iterators and references to the container
232 // [26.2.6/9]" "All Associative Containers: The erase members shall invalidate
233 // only iterators and references to the erased elements [26.2.6/9]"
234 std::set<RefPtr<Task>, PriorityCompare>::iterator mIterator;
235 std::set<RefPtr<Task>, PriorityCompare> mDependencies;
237 RefPtr<TaskManager> mTaskManager;
239 // Access to these variables is protected by the GraphMutex.
240 Kind mKind;
241 bool mCompleted = false;
242 bool mInProgress = false;
243 #ifdef DEBUG
244 bool mIsInGraph = false;
245 #endif
247 static std::atomic<uint64_t> sCurrentTaskSeqNo;
248 int64_t mSeqNo;
249 uint32_t mPriority;
250 // Modifier currently being applied to this task by its taskmanager.
251 int32_t mPriorityModifier = 0;
252 // Time this task was inserted into the task graph, this is used by the
253 // profiler.
254 mozilla::TimeStamp mInsertionTime;
257 // A task manager implementation for priority levels that should only
258 // run during idle periods.
259 class IdleTaskManager : public TaskManager {
260 public:
261 explicit IdleTaskManager(already_AddRefed<nsIIdlePeriod>&& aIdlePeriod)
262 : mIdlePeriodState(std::move(aIdlePeriod)), mProcessedTaskCount(0) {}
264 IdlePeriodState& State() { return mIdlePeriodState; }
266 bool IsSuspended(const MutexAutoLock& aProofOfLock) override {
267 TimeStamp idleDeadline = State().GetCachedIdleDeadline();
268 return !idleDeadline;
271 void DidRunTask() override {
272 TaskManager::DidRunTask();
273 ++mProcessedTaskCount;
276 uint64_t ProcessedTaskCount() { return mProcessedTaskCount; }
278 private:
279 // Tracking of our idle state of various sorts.
280 IdlePeriodState mIdlePeriodState;
282 std::atomic<uint64_t> mProcessedTaskCount;
285 // The TaskController is the core class of the scheduler. It is used to
286 // schedule tasks to be executed, as well as to reprioritize tasks that have
287 // already been scheduled. The core functions to do this are AddTask and
288 // ReprioritizeTask.
289 class TaskController {
290 public:
291 explicit TaskController();
293 static TaskController* Get() {
294 MOZ_ASSERT(sSingleton.get());
295 return sSingleton.get();
298 static void Initialize();
300 void SetThreadObserver(nsIThreadObserver* aObserver) {
301 MutexAutoLock lock(mGraphMutex);
302 mObserver = aObserver;
304 void SetConditionVariable(CondVar* aExternalCondVar) {
305 MutexAutoLock lock(mGraphMutex);
306 mExternalCondVar = aExternalCondVar;
309 void SetIdleTaskManager(IdleTaskManager* aIdleTaskManager) {
310 mIdleTaskManager = aIdleTaskManager;
312 IdleTaskManager* GetIdleTaskManager() { return mIdleTaskManager.get(); }
314 uint64_t RunOutOfMTTasksCount() { return mRunOutOfMTTasksCounter; }
316 // Initialization and shutdown code.
317 void SetPerformanceCounterState(
318 PerformanceCounterState* aPerformanceCounterState);
320 static void Shutdown();
322 static Task::TaskResult RunTask(Task*);
324 // This adds a task to the TaskController graph.
325 // This may be called on any thread.
326 void AddTask(already_AddRefed<Task>&& aTask);
328 // This wait function is the theoretical function you would need if our main
329 // thread needs to also process OS messages or something along those lines.
330 void WaitForTaskOrMessage();
332 // This gets the next (highest priority) task that is only allowed to execute
333 // on the main thread.
334 void ExecuteNextTaskOnlyMainThread();
336 // Process all pending main thread tasks.
337 void ProcessPendingMTTask(bool aMayWait = false);
339 // This allows reprioritization of a task already in the task graph.
340 // This may be called on any thread.
341 void ReprioritizeTask(Task* aTask, uint32_t aPriority);
343 void DispatchRunnable(already_AddRefed<nsIRunnable>&& aRunnable,
344 uint32_t aPriority, TaskManager* aManager = nullptr);
346 nsIRunnable* GetRunnableForMTTask(bool aReallyWait);
348 bool HasMainThreadPendingTasks();
350 uint64_t PendingMainthreadTaskCountIncludingSuspended();
352 // Let users know whether the last main thread task runnable did work.
353 bool MTTaskRunnableProcessedTask() {
354 MOZ_ASSERT(NS_IsMainThread());
355 return mMTTaskRunnableProcessedTask;
358 static int32_t GetPoolThreadCount();
359 static size_t GetThreadStackSize();
361 #ifdef MOZ_MEMORY
362 // To be called once during startup.
363 static void SetupIdleMemoryCleanup();
365 // Used internally to update prefs (can't be private, though).
366 void UpdateIdleMemoryCleanupPrefs();
368 // If needed, schedule a round of idle processing for moz_jemalloc's
369 // idle purge.
370 void MayScheduleIdleMemoryCleanup();
371 #endif
373 private:
374 friend void ThreadFuncPoolThread(void* aIndex);
375 static StaticAutoPtr<TaskController> sSingleton;
377 void InitializeThreadPool();
379 // This gets the next (highest priority) task that is only allowed to execute
380 // on the main thread, if any, and executes it.
381 // Returns true if it succeeded.
382 bool ExecuteNextTaskOnlyMainThreadInternal(const MutexAutoLock& aProofOfLock);
384 // The guts of ExecuteNextTaskOnlyMainThreadInternal, which get idle handling
385 // wrapped around them. Returns whether a task actually ran.
386 bool DoExecuteNextTaskOnlyMainThreadInternal(
387 const MutexAutoLock& aProofOfLock);
389 Task* GetFinalDependency(Task* aTask);
390 void MaybeInterruptTask(Task* aTask, const MutexAutoLock& aProofOfLock);
391 Task* GetHighestPriorityMTTask();
393 void DispatchThreadableTasks(const MutexAutoLock& aProofOfLock);
394 bool MaybeDispatchOneThreadableTask(const MutexAutoLock& aProofOfLock);
395 PoolThread* SelectThread(const MutexAutoLock& aProofOfLock);
397 struct TaskToRun {
398 RefPtr<Task> mTask;
399 uint32_t mEffectiveTaskPriority = 0;
401 TaskToRun TakeThreadableTaskToRun(const MutexAutoLock& aProofOfLock);
403 void EnsureMainThreadTasksScheduled();
405 void ProcessUpdatedPriorityModifier(TaskManager* aManager);
407 void ShutdownThreadPoolInternal();
409 void RunPoolThread(PoolThread* aThread);
410 friend struct PoolThread;
412 // This protects access to the task graph.
413 Mutex mGraphMutex MOZ_UNANNOTATED;
415 // This protects thread pool initialization. We cannot do this from within
416 // the GraphMutex, since thread creation on Windows can generate events on
417 // the main thread that need to be handled.
418 Mutex mPoolInitializationMutex =
419 Mutex("TaskController::mPoolInitializationMutex");
421 // Created under the PoolInitialization mutex, then never extended, and
422 // only freed when the object is freed. mThread is set at creation time;
423 // mCurrentTask and mEffectiveTaskPriority are only accessed from the
424 // thread, so no locking is needed to access this.
425 std::vector<UniquePtr<PoolThread>> mPoolThreads;
427 CondVar mMainThreadCV;
429 // Variables below are protected by mGraphMutex.
431 std::stack<RefPtr<Task>> mCurrentTasksMT;
433 // A list of all tasks ordered by priority.
434 std::set<RefPtr<Task>, Task::PriorityCompare> mThreadableTasks;
435 std::set<RefPtr<Task>, Task::PriorityCompare> mMainThreadTasks;
437 // TaskManagers currently active.
438 // We can use a raw pointer since tasks always hold on to their TaskManager.
439 std::set<TaskManager*> mTaskManagers;
441 // Number of pool threads that are currently idle.
442 size_t mIdleThreadCount = 0;
444 // This ensures we keep running the main thread if we processed a task there.
445 bool mMayHaveMainThreadTask = true;
446 bool mShuttingDown = false;
448 #ifdef MOZ_MEMORY
449 // Flag if we should trigger deferred idle purging in mozjemalloc.
450 bool mIsLazyPurgeEnabled;
451 #endif
453 // This stores whether the last main thread task runnable did work.
454 // Accessed only on MainThread
455 bool mMTTaskRunnableProcessedTask = false;
457 // Whether our thread pool is initialized. We use this currently to avoid
458 // starting the threads in processes where it's never used. This is protected
459 // by mPoolInitializationMutex.
460 bool mThreadPoolInitialized = false;
462 // Whether we have scheduled a runnable on the main thread event loop.
463 // This is used for nsIRunnable compatibility.
464 RefPtr<nsIRunnable> mMTProcessingRunnable;
465 RefPtr<nsIRunnable> mMTBlockingProcessingRunnable;
467 // XXX - Thread observer to notify when a new event has been dispatched
468 // Set immediately, then simply accessed from any thread
469 nsIThreadObserver* mObserver = nullptr;
470 // XXX - External condvar to notify when we have received an event
471 CondVar* mExternalCondVar = nullptr;
472 // Idle task manager so we can properly do idle state stuff.
473 RefPtr<IdleTaskManager> mIdleTaskManager;
475 // How many times the main thread was empty.
476 std::atomic<uint64_t> mRunOutOfMTTasksCounter;
478 // Our tracking of our performance counter and long task state,
479 // shared with nsThread.
480 // Set once when MainThread is created, never changed, only accessed from
481 // DoExecuteNextTaskOnlyMainThreadInternal()
482 PerformanceCounterState* mPerformanceCounterState = nullptr;
485 } // namespace mozilla
487 #endif // mozilla_TaskController_h