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