WebTaskScheduler.h (11529B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim:expandtab:shiftwidth=2:tabstop=2: 3 */ 4 /* This Source Code Form is subject to the terms of the Mozilla Public 5 * License, v. 2.0. If a copy of the MPL was not distributed with this 6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 7 8 #ifndef mozilla_dom_WebTaskScheduler_h 9 #define mozilla_dom_WebTaskScheduler_h 10 11 #include "TaskSignal.h" 12 #include "mozilla/Variant.h" 13 #include "mozilla/dom/AbortFollower.h" 14 #include "mozilla/dom/Promise.h" 15 #include "mozilla/dom/TimeoutHandler.h" 16 #include "mozilla/dom/WebTaskSchedulingBinding.h" 17 #include "nsClassHashtable.h" 18 #include "nsPIDOMWindow.h" 19 #include "nsThreadUtils.h" 20 #include "nsWrapperCache.h" 21 22 namespace mozilla::dom { 23 24 // Keep tracks of the number of same-event-loop-high-priority-queues 25 // (User_blocking or User_visible) that have at least one task scheduled. 26 constinit extern uint32_t 27 gNumNormalOrHighPriorityQueuesHaveTaskScheduledMainThread; 28 29 // https://wicg.github.io/scheduling-apis/#scheduling-state 30 class WebTaskSchedulingState { 31 public: 32 NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WebTaskSchedulingState) 33 NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(WebTaskSchedulingState) 34 35 void Reset() { 36 mAbortSource = nullptr; 37 mPrioritySource = nullptr; 38 } 39 40 void SetAbortSource(AbortSignal* aAbortSource) { 41 mAbortSource = aAbortSource; 42 } 43 44 AbortSignal* GetAbortSource() { return mAbortSource; } 45 TaskSignal* GetPrioritySource() { return mPrioritySource; } 46 47 void SetPrioritySource(already_AddRefed<TaskSignal> aPrioritySource) { 48 mPrioritySource = aPrioritySource; 49 MOZ_ASSERT(mPrioritySource->IsTaskSignal()); 50 } 51 52 private: 53 ~WebTaskSchedulingState() = default; 54 55 RefPtr<AbortSignal> mAbortSource; 56 RefPtr<TaskSignal> mPrioritySource; 57 }; 58 59 class WebTaskQueueHashKey : public PLDHashEntryHdr { 60 public: 61 enum { ALLOW_MEMMOVE = false }; 62 63 typedef const WebTaskQueueHashKey& KeyType; 64 typedef const WebTaskQueueHashKey* KeyTypePointer; 65 66 using StaticPriorityTaskQueueKey = uint32_t; 67 using DynamicPriorityTaskQueueKey = RefPtr<TaskSignal>; 68 69 // When WebTaskQueueTypeKey is RefPtr<TaskSignal>, this 70 // class holds a strong reference to a cycle collectable 71 // objects. 72 using WebTaskQueueTypeKey = 73 mozilla::Variant<StaticPriorityTaskQueueKey, DynamicPriorityTaskQueueKey>; 74 75 WebTaskQueueHashKey(StaticPriorityTaskQueueKey aKey, bool aIsContinuation) 76 : mKey(aKey), mIsContinuation(aIsContinuation) {} 77 78 WebTaskQueueHashKey(DynamicPriorityTaskQueueKey aKey, bool aIsContinuation) 79 : mKey(aKey), mIsContinuation(aIsContinuation) {} 80 81 explicit WebTaskQueueHashKey(KeyTypePointer aKey) 82 : mKey(aKey->mKey), mIsContinuation(aKey->mIsContinuation) {} 83 84 explicit WebTaskQueueHashKey(KeyType aKey) 85 : mKey(aKey.mKey), mIsContinuation(aKey.mIsContinuation) {} 86 87 WebTaskQueueHashKey(WebTaskQueueHashKey&& aToMove) = default; 88 89 ~WebTaskQueueHashKey() = default; 90 91 KeyType GetKey() const { return *this; } 92 93 bool KeyEquals(KeyTypePointer aKey) const { 94 return aKey->mKey == mKey && aKey->mIsContinuation == mIsContinuation; 95 } 96 97 // https://wicg.github.io/scheduling-apis/#scheduler-task-queue-effective-priority 98 uint8_t EffectivePriority() const { 99 switch (Priority()) { 100 case TaskPriority::Background: 101 return mIsContinuation ? 1 : 0; 102 case TaskPriority::User_visible: 103 return mIsContinuation ? 3 : 2; 104 case TaskPriority::User_blocking: 105 return mIsContinuation ? 5 : 4; 106 default: 107 MOZ_ASSERT_UNREACHABLE("Unexpected priority"); 108 return 0; 109 } 110 } 111 112 TaskPriority Priority() const { 113 return mKey.match( 114 [&](const StaticPriorityTaskQueueKey& aStaticKey) { 115 return static_cast<TaskPriority>(aStaticKey); 116 }, 117 [&](const DynamicPriorityTaskQueueKey& aDynamicKey) { 118 return aDynamicKey->Priority(); 119 }); 120 } 121 122 static KeyTypePointer KeyToPointer(KeyType& aKey) { return &aKey; } 123 124 static PLDHashNumber HashKey(KeyTypePointer aKey) { 125 const WebTaskQueueTypeKey& key = aKey->mKey; 126 return key.match( 127 [&](const StaticPriorityTaskQueueKey& aStaticKey) { 128 return mozilla::HashGeneric(aStaticKey, aKey->mIsContinuation); 129 }, 130 [&](const DynamicPriorityTaskQueueKey& aDynamicKey) { 131 return mozilla::HashGeneric(aDynamicKey.get(), aKey->mIsContinuation); 132 }); 133 } 134 135 WebTaskQueueTypeKey& GetTypeKey() { return mKey; } 136 const WebTaskQueueTypeKey& GetTypeKey() const { return mKey; } 137 138 private: 139 WebTaskQueueTypeKey mKey; 140 const bool mIsContinuation; 141 }; 142 143 class WebTask : public LinkedListElement<RefPtr<WebTask>>, 144 public AbortFollower, 145 public SupportsWeakPtr { 146 friend class WebTaskScheduler; 147 148 public: 149 MOZ_CAN_RUN_SCRIPT bool Run(); 150 151 NS_DECL_CYCLE_COLLECTING_ISUPPORTS 152 153 NS_DECL_CYCLE_COLLECTION_CLASS(WebTask) 154 WebTask(uint32_t aEnqueueOrder, 155 const Maybe<SchedulerPostTaskCallback&>& aCallback, 156 WebTaskSchedulingState* aSchedulingState, Promise* aPromise, 157 WebTaskScheduler* aWebTaskScheduler, 158 const WebTaskQueueHashKey& aHashKey); 159 160 void RunAbortAlgorithm() override; 161 162 bool HasScheduled() const { return mHasScheduled; } 163 164 uint32_t EnqueueOrder() const { return mEnqueueOrder; } 165 166 void ClearWebTaskScheduler() { mScheduler = nullptr; } 167 168 const WebTaskQueueHashKey& TaskQueueHashKey() const { 169 return mWebTaskQueueHashKey; 170 } 171 172 TaskPriority Priority() const { return mWebTaskQueueHashKey.Priority(); } 173 174 private: 175 void SetHasScheduled() { 176 MOZ_ASSERT(!mHasScheduled); 177 mHasScheduled = true; 178 } 179 180 uint32_t mEnqueueOrder; 181 182 RefPtr<SchedulerPostTaskCallback> mCallback; 183 RefPtr<Promise> mPromise; 184 185 bool mHasScheduled; 186 187 RefPtr<WebTaskSchedulingState> mSchedulingState; 188 189 // WebTaskScheduler owns WebTaskQueue, and WebTaskQueue owns WebTask, so it's 190 // okay to use a raw pointer 191 WebTaskScheduler* mScheduler; 192 193 // Depending on whether this task was scheduled with static priority 194 // or dynamic priority, it could hold a reference reference to TaskSignal 195 // (cycle collectable object). 196 WebTaskQueueHashKey mWebTaskQueueHashKey; 197 198 ~WebTask() = default; 199 }; 200 201 class WebTaskQueue { 202 public: 203 static constexpr int EffectivePriorityCount = 6; 204 205 explicit WebTaskQueue(WebTaskScheduler* aScheduler) : mScheduler(aScheduler) { 206 MOZ_ASSERT(aScheduler); 207 } 208 209 WebTaskQueue(WebTaskQueue&& aWebTaskQueue) = default; 210 211 ~WebTaskQueue(); 212 213 TaskPriority Priority() const { return mPriority; } 214 void SetPriority(TaskPriority aNewPriority) { mPriority = aNewPriority; } 215 216 LinkedList<RefPtr<WebTask>>& Tasks() { return mTasks; } 217 const LinkedList<RefPtr<WebTask>>& Tasks() const { return mTasks; } 218 219 void AddTask(WebTask* aTask) { mTasks.insertBack(aTask); } 220 221 bool IsEmpty() const { return mTasks.isEmpty(); } 222 223 // TODO: To optimize it, we could have the scheduled and unscheduled 224 // tasks stored separately. 225 WebTask* GetFirstScheduledTask() { 226 for (const auto& task : mTasks) { 227 if (task->HasScheduled()) { 228 return task; 229 } 230 } 231 return nullptr; 232 } 233 234 bool HasScheduledTasks() const { 235 if (mTasks.isEmpty()) { 236 return false; 237 } 238 239 for (const auto& task : mTasks) { 240 if (task->HasScheduled()) { 241 return true; 242 } 243 } 244 return false; 245 } 246 247 private: 248 TaskPriority mPriority = TaskPriority::User_visible; 249 LinkedList<RefPtr<WebTask>> mTasks; 250 251 // WebTaskScheduler owns WebTaskQueue as a hashtable value, so using a raw 252 // pointer points to WebTaskScheduler is ok. 253 WebTaskScheduler* mScheduler; 254 }; 255 256 class WebTaskSchedulerMainThread; 257 class WebTaskSchedulerWorker; 258 259 class WebTaskScheduler : public nsWrapperCache, 260 public SupportsWeakPtr, 261 public LinkedListElement<WebTaskScheduler> { 262 friend class DelayedWebTaskHandler; 263 264 public: 265 NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WebTaskScheduler) 266 NS_DECL_CYCLE_COLLECTION_NATIVE_WRAPPERCACHE_CLASS(WebTaskScheduler) 267 268 static already_AddRefed<WebTaskSchedulerMainThread> CreateForMainThread( 269 nsGlobalWindowInner* aWindow); 270 271 static already_AddRefed<WebTaskSchedulerWorker> CreateForWorker( 272 WorkerPrivate* aWorkerPrivate); 273 274 explicit WebTaskScheduler(nsIGlobalObject* aParent); 275 276 already_AddRefed<Promise> PostTask(SchedulerPostTaskCallback& aCallback, 277 const SchedulerPostTaskOptions& aOptions); 278 279 already_AddRefed<Promise> YieldImpl(); 280 281 nsIGlobalObject* GetParentObject() const { return mParent; } 282 283 virtual JSObject* WrapObject(JSContext* cx, 284 JS::Handle<JSObject*> aGivenProto) override; 285 286 WebTask* GetNextTask(bool aIsMainThread); 287 288 virtual void Disconnect(); 289 290 void RunTaskSignalPriorityChange(TaskSignal* aTaskSignal); 291 292 void DeleteEntryFromWebTaskQueueMap(const WebTaskQueueHashKey& aKey) { 293 DebugOnly<bool> result = mWebTaskQueues.Remove(aKey); 294 MOZ_ASSERT(result); 295 } 296 297 void NotifyTaskWillBeRunOrAborted(const WebTask* aWebTask); 298 virtual void IncreaseNumNormalOrHighPriorityQueuesHaveTaskScheduled() = 0; 299 virtual void DecreaseNumNormalOrHighPriorityQueuesHaveTaskScheduled() = 0; 300 301 protected: 302 virtual ~WebTaskScheduler() = default; 303 nsCOMPtr<nsIGlobalObject> mParent; 304 305 private: 306 struct SelectedTaskQueueData { 307 WebTaskQueueHashKey mSelectedQueueHashKey; 308 WebTaskQueue& mSelectedTaskQueue; 309 }; 310 311 already_AddRefed<WebTask> CreateTask( 312 AbortSignal* aAbortSignal, TaskSignal* aTaskSignal, 313 const Optional<TaskPriority>& aPriority, const bool aIsContinuation, 314 const Maybe<SchedulerPostTaskCallback&>& aCallback, 315 WebTaskSchedulingState* aSchedulingState, Promise* aPromise); 316 317 bool DispatchTask(WebTask* aTask, EventQueuePriority aPriority); 318 319 SelectedTaskQueueData SelectTaskQueue(TaskSignal* aSignal, 320 const Optional<TaskPriority>& aPriority, 321 const bool aIsContinuation); 322 323 virtual nsresult SetTimeoutForDelayedTask(WebTask* aTask, uint64_t aDelay, 324 EventQueuePriority aPriority) = 0; 325 virtual bool DispatchEventLoopRunnable(EventQueuePriority aPriority) = 0; 326 327 EventQueuePriority GetEventQueuePriority(const TaskPriority& aPriority, 328 bool aIsContinuation) const; 329 330 nsTHashMap<WebTaskQueueHashKey, WebTaskQueue>& GetWebTaskQueues() { 331 return mWebTaskQueues; 332 } 333 334 nsTHashMap<WebTaskQueueHashKey, WebTaskQueue> mWebTaskQueues; 335 }; 336 337 class DelayedWebTaskHandler final : public TimeoutHandler { 338 public: 339 DelayedWebTaskHandler(JSContext* aCx, WebTaskScheduler* aScheduler, 340 WebTask* aTask, EventQueuePriority aPriority) 341 : TimeoutHandler(aCx), mScheduler(aScheduler), mWebTask(aTask) {} 342 343 NS_DECL_CYCLE_COLLECTING_ISUPPORTS 344 NS_DECL_CYCLE_COLLECTION_CLASS(DelayedWebTaskHandler) 345 346 MOZ_CAN_RUN_SCRIPT bool Call(const char* /* unused */) override { 347 if (mScheduler && mWebTask && mWebTask->isInList()) { 348 MOZ_ASSERT(!mWebTask->HasScheduled()); 349 if (!mScheduler->DispatchTask(mWebTask, mPriority)) { 350 return false; 351 } 352 } 353 return true; 354 } 355 356 private: 357 ~DelayedWebTaskHandler() override = default; 358 WeakPtr<WebTaskScheduler> mScheduler; 359 // WebTask gets added to WebTaskQueue, and WebTaskQueue keeps its alive. 360 WeakPtr<WebTask> mWebTask; 361 EventQueuePriority mPriority; 362 }; 363 } // namespace mozilla::dom 364 #endif