summaryrefslogtreecommitdiffstats
path: root/dom/workers/JSExecutionManager.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dom/workers/JSExecutionManager.cpp251
1 files changed, 251 insertions, 0 deletions
diff --git a/dom/workers/JSExecutionManager.cpp b/dom/workers/JSExecutionManager.cpp
new file mode 100644
index 0000000000..4a67a3a3d2
--- /dev/null
+++ b/dom/workers/JSExecutionManager.cpp
@@ -0,0 +1,251 @@
+/* -*- 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/. */
+
+#include "mozilla/dom/JSExecutionManager.h"
+
+#include "WorkerCommon.h"
+#include "WorkerPrivate.h"
+
+#include "mozilla/dom/DocGroup.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/StaticPtr.h"
+
+namespace mozilla::dom {
+
+JSExecutionManager* JSExecutionManager::mCurrentMTManager;
+
+const uint32_t kTimeSliceExpirationMS = 50;
+
+using RequestState = JSExecutionManager::RequestState;
+
+static StaticRefPtr<JSExecutionManager> sSABSerializationManager;
+
+void JSExecutionManager::Initialize() {
+ if (StaticPrefs::dom_workers_serialized_sab_access()) {
+ sSABSerializationManager = MakeRefPtr<JSExecutionManager>(1);
+ }
+}
+
+void JSExecutionManager::Shutdown() { sSABSerializationManager = nullptr; }
+
+JSExecutionManager* JSExecutionManager::GetSABSerializationManager() {
+ return sSABSerializationManager.get();
+}
+
+RequestState JSExecutionManager::RequestJSThreadExecution() {
+ if (NS_IsMainThread()) {
+ return RequestJSThreadExecutionMainThread();
+ }
+
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+
+ if (!workerPrivate || workerPrivate->GetExecutionGranted()) {
+ return RequestState::ExecutingAlready;
+ }
+
+ MutexAutoLock lock(mExecutionQueueMutex);
+ MOZ_ASSERT(mMaxRunning >= mRunning);
+
+ if ((mExecutionQueue.size() + (mMainThreadAwaitingExecution ? 1 : 0)) <
+ size_t(mMaxRunning - mRunning)) {
+ // There's slots ready for things to run, execute right away.
+ workerPrivate->SetExecutionGranted(true);
+ workerPrivate->ScheduleTimeSliceExpiration(kTimeSliceExpirationMS);
+
+ mRunning++;
+ return RequestState::Granted;
+ }
+
+ mExecutionQueue.push_back(workerPrivate);
+
+ TimeStamp waitStart = TimeStamp::Now();
+
+ while (mRunning >= mMaxRunning || (workerPrivate != mExecutionQueue.front() ||
+ mMainThreadAwaitingExecution)) {
+ // If there is no slots available, the main thread is awaiting permission
+ // or we are not first in line for execution, wait until notified.
+ mExecutionQueueCondVar.Wait(TimeDuration::FromMilliseconds(500));
+ if ((TimeStamp::Now() - waitStart) > TimeDuration::FromSeconds(20)) {
+ // Crash so that these types of situations are actually caught in the
+ // crash reporter.
+ MOZ_CRASH();
+ }
+ }
+
+ workerPrivate->SetExecutionGranted(true);
+ workerPrivate->ScheduleTimeSliceExpiration(kTimeSliceExpirationMS);
+
+ mExecutionQueue.pop_front();
+ mRunning++;
+ if (mRunning < mMaxRunning) {
+ // If a thread woke up before that wasn't first in line it will have gone
+ // back to sleep, if there's more slots available, wake it now.
+ mExecutionQueueCondVar.NotifyAll();
+ }
+
+ return RequestState::Granted;
+}
+
+void JSExecutionManager::YieldJSThreadExecution() {
+ if (NS_IsMainThread()) {
+ MOZ_ASSERT(mMainThreadIsExecuting);
+ mMainThreadIsExecuting = false;
+
+ MutexAutoLock lock(mExecutionQueueMutex);
+ mRunning--;
+ mExecutionQueueCondVar.NotifyAll();
+ return;
+ }
+
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+
+ if (!workerPrivate) {
+ return;
+ }
+
+ MOZ_ASSERT(workerPrivate->GetExecutionGranted());
+
+ workerPrivate->CancelTimeSliceExpiration();
+
+ MutexAutoLock lock(mExecutionQueueMutex);
+ mRunning--;
+ mExecutionQueueCondVar.NotifyAll();
+ workerPrivate->SetExecutionGranted(false);
+}
+
+bool JSExecutionManager::YieldJSThreadExecutionIfGranted() {
+ if (NS_IsMainThread()) {
+ if (mMainThreadIsExecuting) {
+ YieldJSThreadExecution();
+ return true;
+ }
+ return false;
+ }
+
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+
+ if (workerPrivate && workerPrivate->GetExecutionGranted()) {
+ YieldJSThreadExecution();
+ return true;
+ }
+
+ return false;
+}
+
+RequestState JSExecutionManager::RequestJSThreadExecutionMainThread() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mMainThreadIsExecuting) {
+ return RequestState::ExecutingAlready;
+ }
+
+ MutexAutoLock lock(mExecutionQueueMutex);
+ MOZ_ASSERT(mMaxRunning >= mRunning);
+
+ if ((mMaxRunning - mRunning) > 0) {
+ // If there's any slots available run, the main thread always takes
+ // precedence over any worker threads.
+ mRunning++;
+ mMainThreadIsExecuting = true;
+ return RequestState::Granted;
+ }
+
+ mMainThreadAwaitingExecution = true;
+
+ TimeStamp waitStart = TimeStamp::Now();
+
+ while (mRunning >= mMaxRunning) {
+ if ((TimeStamp::Now() - waitStart) > TimeDuration::FromSeconds(20)) {
+ // Crash so that these types of situations are actually caught in the
+ // crash reporter.
+ MOZ_CRASH();
+ }
+ mExecutionQueueCondVar.Wait(TimeDuration::FromMilliseconds(500));
+ }
+
+ mMainThreadAwaitingExecution = false;
+ mMainThreadIsExecuting = true;
+
+ mRunning++;
+ if (mRunning < mMaxRunning) {
+ // If a thread woke up before that wasn't first in line it will have gone
+ // back to sleep, if there's more slots available, wake it now.
+ mExecutionQueueCondVar.NotifyAll();
+ }
+
+ return RequestState::Granted;
+}
+
+AutoRequestJSThreadExecution::AutoRequestJSThreadExecution(
+ nsIGlobalObject* aGlobalObject, bool aIsMainThread) {
+ JSExecutionManager* manager = nullptr;
+
+ mIsMainThread = aIsMainThread;
+ if (mIsMainThread) {
+ mOldGrantingManager = JSExecutionManager::mCurrentMTManager;
+
+ nsPIDOMWindowInner* innerWindow = nullptr;
+ if (aGlobalObject) {
+ innerWindow = aGlobalObject->AsInnerWindow();
+ }
+
+ DocGroup* docGroup = nullptr;
+ if (innerWindow) {
+ docGroup = innerWindow->GetDocGroup();
+ }
+
+ if (docGroup) {
+ manager = docGroup->GetExecutionManager();
+ }
+
+ if (JSExecutionManager::mCurrentMTManager == manager) {
+ return;
+ }
+
+ if (JSExecutionManager::mCurrentMTManager) {
+ JSExecutionManager::mCurrentMTManager->YieldJSThreadExecution();
+ JSExecutionManager::mCurrentMTManager = nullptr;
+ }
+ } else {
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+
+ if (workerPrivate) {
+ manager = workerPrivate->GetExecutionManager();
+ }
+ }
+
+ if (manager &&
+ (manager->RequestJSThreadExecution() == RequestState::Granted)) {
+ if (NS_IsMainThread()) {
+ // Make sure we restore permission on destruction if needed.
+ JSExecutionManager::mCurrentMTManager = manager;
+ }
+ mExecutionGrantingManager = std::move(manager);
+ }
+}
+
+AutoYieldJSThreadExecution::AutoYieldJSThreadExecution() {
+ JSExecutionManager* manager = nullptr;
+
+ if (NS_IsMainThread()) {
+ manager = JSExecutionManager::mCurrentMTManager;
+ } else {
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+
+ if (workerPrivate) {
+ manager = workerPrivate->GetExecutionManager();
+ }
+ }
+
+ if (manager && manager->YieldJSThreadExecutionIfGranted()) {
+ mExecutionGrantingManager = std::move(manager);
+ if (NS_IsMainThread()) {
+ JSExecutionManager::mCurrentMTManager = nullptr;
+ }
+ }
+}
+
+} // namespace mozilla::dom