JSExecutionManager.cpp (7199B)
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/. */ 6 7 #include "mozilla/dom/JSExecutionManager.h" 8 9 #include "WorkerCommon.h" 10 #include "WorkerPrivate.h" 11 #include "mozilla/StaticPrefs_dom.h" 12 #include "mozilla/StaticPtr.h" 13 #include "mozilla/dom/DocGroup.h" 14 15 namespace mozilla::dom { 16 17 JSExecutionManager* JSExecutionManager::mCurrentMTManager; 18 19 const uint32_t kTimeSliceExpirationMS = 50; 20 21 using RequestState = JSExecutionManager::RequestState; 22 23 static StaticRefPtr<JSExecutionManager> sSABSerializationManager; 24 25 void JSExecutionManager::Initialize() { 26 if (StaticPrefs::dom_workers_serialized_sab_access()) { 27 sSABSerializationManager = MakeRefPtr<JSExecutionManager>(1); 28 } 29 } 30 31 void JSExecutionManager::Shutdown() { sSABSerializationManager = nullptr; } 32 33 JSExecutionManager* JSExecutionManager::GetSABSerializationManager() { 34 return sSABSerializationManager.get(); 35 } 36 37 RequestState JSExecutionManager::RequestJSThreadExecution() { 38 if (NS_IsMainThread()) { 39 return RequestJSThreadExecutionMainThread(); 40 } 41 42 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); 43 44 if (!workerPrivate || workerPrivate->GetExecutionGranted()) { 45 return RequestState::ExecutingAlready; 46 } 47 48 MutexAutoLock lock(mExecutionQueueMutex); 49 MOZ_ASSERT(mMaxRunning >= mRunning); 50 51 if ((mExecutionQueue.size() + (mMainThreadAwaitingExecution ? 1 : 0)) < 52 size_t(mMaxRunning - mRunning)) { 53 // There's slots ready for things to run, execute right away. 54 workerPrivate->SetExecutionGranted(true); 55 workerPrivate->ScheduleTimeSliceExpiration(kTimeSliceExpirationMS); 56 57 mRunning++; 58 return RequestState::Granted; 59 } 60 61 mExecutionQueue.push_back(workerPrivate); 62 63 TimeStamp waitStart = TimeStamp::Now(); 64 65 while (mRunning >= mMaxRunning || (workerPrivate != mExecutionQueue.front() || 66 mMainThreadAwaitingExecution)) { 67 // If there is no slots available, the main thread is awaiting permission 68 // or we are not first in line for execution, wait until notified. 69 mExecutionQueueCondVar.Wait(TimeDuration::FromMilliseconds(500)); 70 if ((TimeStamp::Now() - waitStart) > TimeDuration::FromSeconds(20)) { 71 // Crash so that these types of situations are actually caught in the 72 // crash reporter. 73 MOZ_CRASH(); 74 } 75 } 76 77 workerPrivate->SetExecutionGranted(true); 78 workerPrivate->ScheduleTimeSliceExpiration(kTimeSliceExpirationMS); 79 80 mExecutionQueue.pop_front(); 81 mRunning++; 82 if (mRunning < mMaxRunning) { 83 // If a thread woke up before that wasn't first in line it will have gone 84 // back to sleep, if there's more slots available, wake it now. 85 mExecutionQueueCondVar.NotifyAll(); 86 } 87 88 return RequestState::Granted; 89 } 90 91 void JSExecutionManager::YieldJSThreadExecution() { 92 if (NS_IsMainThread()) { 93 MOZ_ASSERT(mMainThreadIsExecuting); 94 mMainThreadIsExecuting = false; 95 96 MutexAutoLock lock(mExecutionQueueMutex); 97 mRunning--; 98 mExecutionQueueCondVar.NotifyAll(); 99 return; 100 } 101 102 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); 103 104 if (!workerPrivate) { 105 return; 106 } 107 108 MOZ_ASSERT(workerPrivate->GetExecutionGranted()); 109 110 workerPrivate->CancelTimeSliceExpiration(); 111 112 MutexAutoLock lock(mExecutionQueueMutex); 113 mRunning--; 114 mExecutionQueueCondVar.NotifyAll(); 115 workerPrivate->SetExecutionGranted(false); 116 } 117 118 bool JSExecutionManager::YieldJSThreadExecutionIfGranted() { 119 if (NS_IsMainThread()) { 120 if (mMainThreadIsExecuting) { 121 YieldJSThreadExecution(); 122 return true; 123 } 124 return false; 125 } 126 127 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); 128 129 if (workerPrivate && workerPrivate->GetExecutionGranted()) { 130 YieldJSThreadExecution(); 131 return true; 132 } 133 134 return false; 135 } 136 137 RequestState JSExecutionManager::RequestJSThreadExecutionMainThread() { 138 MOZ_ASSERT(NS_IsMainThread()); 139 140 if (mMainThreadIsExecuting) { 141 return RequestState::ExecutingAlready; 142 } 143 144 MutexAutoLock lock(mExecutionQueueMutex); 145 MOZ_ASSERT(mMaxRunning >= mRunning); 146 147 if ((mMaxRunning - mRunning) > 0) { 148 // If there's any slots available run, the main thread always takes 149 // precedence over any worker threads. 150 mRunning++; 151 mMainThreadIsExecuting = true; 152 return RequestState::Granted; 153 } 154 155 mMainThreadAwaitingExecution = true; 156 157 TimeStamp waitStart = TimeStamp::Now(); 158 159 while (mRunning >= mMaxRunning) { 160 if ((TimeStamp::Now() - waitStart) > TimeDuration::FromSeconds(20)) { 161 // Crash so that these types of situations are actually caught in the 162 // crash reporter. 163 MOZ_CRASH(); 164 } 165 mExecutionQueueCondVar.Wait(TimeDuration::FromMilliseconds(500)); 166 } 167 168 mMainThreadAwaitingExecution = false; 169 mMainThreadIsExecuting = true; 170 171 mRunning++; 172 if (mRunning < mMaxRunning) { 173 // If a thread woke up before that wasn't first in line it will have gone 174 // back to sleep, if there's more slots available, wake it now. 175 mExecutionQueueCondVar.NotifyAll(); 176 } 177 178 return RequestState::Granted; 179 } 180 181 AutoRequestJSThreadExecution::AutoRequestJSThreadExecution( 182 nsIGlobalObject* aGlobalObject, bool aIsMainThread) { 183 JSExecutionManager* manager = nullptr; 184 185 mIsMainThread = aIsMainThread; 186 if (mIsMainThread) { 187 mOldGrantingManager = JSExecutionManager::mCurrentMTManager; 188 189 nsPIDOMWindowInner* innerWindow = nullptr; 190 if (aGlobalObject) { 191 innerWindow = aGlobalObject->GetAsInnerWindow(); 192 } 193 194 DocGroup* docGroup = nullptr; 195 if (innerWindow) { 196 docGroup = innerWindow->GetDocGroup(); 197 } 198 199 if (docGroup) { 200 manager = docGroup->GetExecutionManager(); 201 } 202 203 if (JSExecutionManager::mCurrentMTManager == manager) { 204 return; 205 } 206 207 if (JSExecutionManager::mCurrentMTManager) { 208 JSExecutionManager::mCurrentMTManager->YieldJSThreadExecution(); 209 JSExecutionManager::mCurrentMTManager = nullptr; 210 } 211 } else { 212 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); 213 214 if (workerPrivate) { 215 manager = workerPrivate->GetExecutionManager(); 216 } 217 } 218 219 if (manager && 220 (manager->RequestJSThreadExecution() == RequestState::Granted)) { 221 if (NS_IsMainThread()) { 222 // Make sure we restore permission on destruction if needed. 223 JSExecutionManager::mCurrentMTManager = manager; 224 } 225 mExecutionGrantingManager = std::move(manager); 226 } 227 } 228 229 AutoYieldJSThreadExecution::AutoYieldJSThreadExecution() { 230 JSExecutionManager* manager = nullptr; 231 232 if (NS_IsMainThread()) { 233 manager = JSExecutionManager::mCurrentMTManager; 234 } else { 235 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); 236 237 if (workerPrivate) { 238 manager = workerPrivate->GetExecutionManager(); 239 } 240 } 241 242 if (manager && manager->YieldJSThreadExecutionIfGranted()) { 243 mExecutionGrantingManager = std::move(manager); 244 if (NS_IsMainThread()) { 245 JSExecutionManager::mCurrentMTManager = nullptr; 246 } 247 } 248 } 249 250 } // namespace mozilla::dom