JSExecutionManager.h (6933B)
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 #ifndef mozilla_dom_workers_jsexecutionmanager_h__ 8 #define mozilla_dom_workers_jsexecutionmanager_h__ 9 10 #include <stdint.h> 11 12 #include <deque> 13 14 #include "MainThreadUtils.h" 15 #include "mozilla/Attributes.h" 16 #include "mozilla/CondVar.h" 17 #include "mozilla/Mutex.h" 18 #include "mozilla/RefPtr.h" 19 #include "nsISupports.h" 20 21 class nsIGlobalObject; 22 namespace mozilla { 23 24 class ErrorResult; 25 26 namespace dom { 27 class WorkerPrivate; 28 29 // The code in this file is responsible for throttling JS execution. It does 30 // this by introducing a JSExecutionManager class. An execution manager may be 31 // applied to any number of worker threads or a DocGroup on the main thread. 32 // 33 // JS environments associated with a JS execution manager may only execute on a 34 // certain amount of CPU cores in parallel. 35 // 36 // Whenever the main thread, or a worker thread begins executing JS it should 37 // make sure AutoRequestJSThreadExecution is present on the stack, in practice 38 // this is done by it being part of AutoEntryScript. 39 // 40 // Whenever the main thread may end up blocking on the activity of a worker 41 // thread, it should make sure to have an AutoYieldJSThreadExecution object 42 // on the stack. 43 // 44 // Whenever a worker thread may end up blocking on the main thread or the 45 // activity of another worker thread, it should make sure to have an 46 // AutoYieldJSThreadExecution object on the stack. 47 // 48 // Failure to do this may result in a deadlock. When a deadlock occurs due 49 // to these circumstances we will crash after 20 seconds. 50 // 51 // For the main thread this class should only be used in the case of an 52 // emergency surrounding exploitability of SharedArrayBuffers. A single 53 // execution manager will then be shared between all Workers and the main 54 // thread doc group containing the SharedArrayBuffer and ensure all this code 55 // only runs in a serialized manner. On the main thread we therefore may have 56 // 1 execution manager per DocGroup, as this is the granularity at which 57 // SharedArrayBuffers may be present. 58 59 class AutoRequestJSThreadExecution; 60 class AutoYieldJSThreadExecution; 61 62 // This class is used to regulate JS execution when for whatever reason we wish 63 // to throttle execution of multiple JS environments to occur with a certain 64 // maximum of synchronously executing threads. This should be used through 65 // the stack helper classes. 66 class JSExecutionManager { 67 public: 68 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(JSExecutionManager) 69 70 explicit JSExecutionManager(int32_t aMaxRunning = 1) 71 : mMaxRunning(aMaxRunning) {} 72 73 enum class RequestState { Granted, ExecutingAlready }; 74 75 static void Initialize(); 76 static void Shutdown(); 77 78 static JSExecutionManager* GetSABSerializationManager(); 79 80 private: 81 friend class AutoRequestJSThreadExecution; 82 friend class AutoYieldJSThreadExecution; 83 ~JSExecutionManager() = default; 84 85 // Methods used by Auto*JSThreadExecution 86 87 // Request execution permission, returns ExecutingAlready if execution was 88 // already granted or does not apply to this thread. 89 RequestState RequestJSThreadExecution(); 90 91 // Yield JS execution, this asserts that permission is actually granted. 92 void YieldJSThreadExecution(); 93 94 // Yield JS execution if permission was granted. This returns false if no 95 // permission is granted. This method is needed because an execution manager 96 // may have been set in between the ctor and dtor of 97 // AutoYieldJSThreadExecution. 98 bool YieldJSThreadExecutionIfGranted(); 99 100 RequestState RequestJSThreadExecutionMainThread(); 101 102 // Execution manager currently managing the main thread. 103 // MainThread access only. 104 static JSExecutionManager* mCurrentMTManager; 105 106 // Workers waiting to be given permission for execution. 107 // Guarded by mExecutionQueueMutex. 108 std::deque<WorkerPrivate*> mExecutionQueue 109 MOZ_GUARDED_BY(mExecutionQueueMutex); 110 111 // Number of threads currently executing concurrently for this manager. 112 // Guarded by mExecutionQueueMutex. 113 int32_t mRunning MOZ_GUARDED_BY(mExecutionQueueMutex) = 0; 114 115 // Number of threads allowed to run concurrently for environments managed 116 // by this manager. 117 // Guarded by mExecutionQueueMutex. 118 int32_t mMaxRunning MOZ_GUARDED_BY(mExecutionQueueMutex) = 1; 119 120 // Mutex that guards the execution queue and associated state. 121 Mutex mExecutionQueueMutex = 122 Mutex{"JSExecutionManager::sExecutionQueueMutex"}; 123 124 // ConditionVariables that blocked threads wait for. 125 CondVar mExecutionQueueCondVar = 126 CondVar{mExecutionQueueMutex, "JSExecutionManager::sExecutionQueueMutex"}; 127 128 // Whether the main thread is currently executing for this manager. 129 // MainThread access only. 130 bool mMainThreadIsExecuting = false; 131 132 // Whether the main thread is currently awaiting permission to execute. Main 133 // thread execution is always prioritized. 134 // Guarded by mExecutionQueueMutex. 135 bool mMainThreadAwaitingExecution MOZ_GUARDED_BY(mExecutionQueueMutex) = 136 false; 137 }; 138 139 // Helper for managing execution requests and allowing re-entrant permission 140 // requests. 141 class MOZ_STACK_CLASS AutoRequestJSThreadExecution { 142 public: 143 explicit AutoRequestJSThreadExecution(nsIGlobalObject* aGlobalObject, 144 bool aIsMainThread); 145 146 ~AutoRequestJSThreadExecution() { 147 if (mExecutionGrantingManager) { 148 mExecutionGrantingManager->YieldJSThreadExecution(); 149 } 150 if (mIsMainThread) { 151 if (mOldGrantingManager) { 152 mOldGrantingManager->RequestJSThreadExecution(); 153 } 154 JSExecutionManager::mCurrentMTManager = mOldGrantingManager; 155 } 156 } 157 158 private: 159 // The manager we obtained permission from. nullptr if permission was already 160 // granted. 161 RefPtr<JSExecutionManager> mExecutionGrantingManager; 162 // The manager we had permission from before, and where permission should be 163 // re-requested upon destruction. 164 RefPtr<JSExecutionManager> mOldGrantingManager; 165 166 // We store this for performance reasons. 167 bool mIsMainThread; 168 }; 169 170 // Class used to wrap code which essentially exits JS execution and may block 171 // on other threads. 172 class MOZ_STACK_CLASS AutoYieldJSThreadExecution { 173 public: 174 AutoYieldJSThreadExecution(); 175 176 ~AutoYieldJSThreadExecution() { 177 if (mExecutionGrantingManager) { 178 mExecutionGrantingManager->RequestJSThreadExecution(); 179 if (NS_IsMainThread()) { 180 JSExecutionManager::mCurrentMTManager = mExecutionGrantingManager; 181 } 182 } 183 } 184 185 private: 186 // Set to the granting manager if we were granted permission here. 187 RefPtr<JSExecutionManager> mExecutionGrantingManager; 188 }; 189 190 } // namespace dom 191 } // namespace mozilla 192 193 #endif // mozilla_dom_workers_jsexecutionmanager_h__