mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-27 06:43:32 +00:00
Bug 1563335 - Part 1: Implement mechanism to throttle JS execution. r=smaug,asuth
Differential Revision: https://phabricator.services.mozilla.com/D59321 --HG-- extra : moz-landing-system : lando
This commit is contained in:
parent
e7da41700a
commit
1bc21ff19c
@ -6,6 +6,7 @@
|
||||
|
||||
#include "mozilla/dom/DocGroup.h"
|
||||
#include "mozilla/dom/DOMTypes.h"
|
||||
#include "mozilla/dom/JSExecutionManager.h"
|
||||
#include "mozilla/dom/TabGroup.h"
|
||||
#include "mozilla/AbstractThread.h"
|
||||
#include "mozilla/PerformanceUtils.h"
|
||||
@ -42,6 +43,10 @@ nsresult DocGroup::GetKey(nsIPrincipal* aPrincipal, nsACString& aKey) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
void DocGroup::SetExecutionManager(JSExecutionManager* aManager) {
|
||||
mExecutionManager = aManager;
|
||||
}
|
||||
|
||||
void DocGroup::RemoveDocument(Document* aDocument) {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(mDocuments.Contains(aDocument));
|
||||
|
@ -55,6 +55,9 @@ class DocGroup final {
|
||||
|
||||
PerformanceCounter* GetPerformanceCounter() { return mPerformanceCounter; }
|
||||
|
||||
JSExecutionManager* GetExecutionManager() const { return mExecutionManager; }
|
||||
void SetExecutionManager(JSExecutionManager*);
|
||||
|
||||
RefPtr<PerformanceInfoPromise> ReportPerformanceInfo();
|
||||
|
||||
TabGroup* GetTabGroup() { return mTabGroup; }
|
||||
@ -130,6 +133,11 @@ class DocGroup final {
|
||||
RefPtr<mozilla::ThrottledEventQueue> mIframePostMessageQueue;
|
||||
nsTHashtable<nsUint64HashKey> mIframesUsedPostMessageQueue;
|
||||
|
||||
// non-null if the JS execution for this docgroup is regulated with regards
|
||||
// to worker threads. This should only be used when we are forcing serialized
|
||||
// SAB access.
|
||||
RefPtr<JSExecutionManager> mExecutionManager;
|
||||
|
||||
// Each DocGroup has a persisted agent cluster ID.
|
||||
const nsID mAgentClusterId;
|
||||
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include "mozilla/dom/ClientIPCTypes.h"
|
||||
#include "mozilla/dom/DOMMozPromiseRequestHolder.h"
|
||||
#include "mozilla/dom/ipc/StructuredCloneData.h"
|
||||
#include "mozilla/dom/JSExecutionManager.h"
|
||||
#include "mozilla/dom/MessageEvent.h"
|
||||
#include "mozilla/dom/MessageEventBinding.h"
|
||||
#include "mozilla/dom/Navigator.h"
|
||||
@ -349,6 +350,9 @@ void ClientSource::WorkerSyncPing(WorkerPrivate* aWorkerPrivate) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We need to make sure the mainthread is unblocked.
|
||||
AutoYieldJSThreadExecution yield;
|
||||
|
||||
MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate == mManager->GetWorkerPrivate());
|
||||
aWorkerPrivate->AssertIsOnWorkerThread();
|
||||
MOZ_DIAGNOSTIC_ASSERT(GetActor());
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include "mozilla/BasePrincipal.h"
|
||||
#include "mozilla/CycleCollectedJSContext.h"
|
||||
#include "mozilla/ThreadLocal.h"
|
||||
#include "mozilla/dom/JSExecutionManager.h"
|
||||
#include "mozilla/dom/WorkerPrivate.h"
|
||||
|
||||
#include "jsapi.h"
|
||||
@ -580,7 +581,8 @@ AutoEntryScript::AutoEntryScript(nsIGlobalObject* aGlobalObject,
|
||||
"", aReason, JS::ProfilingCategoryPair::JS,
|
||||
uint32_t(js::ProfilingStackFrame::Flags::RELEVANT_FOR_JS))
|
||||
#endif
|
||||
{
|
||||
,
|
||||
mJSThreadExecution(aGlobalObject, aIsMainThread) {
|
||||
MOZ_ASSERT(aGlobalObject);
|
||||
|
||||
if (aIsMainThread) {
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "nsIPrincipal.h"
|
||||
#include "xpcpublic.h"
|
||||
|
||||
#include "mozilla/dom/JSExecutionManager.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
|
||||
#include "jsapi.h"
|
||||
@ -383,6 +384,7 @@ class MOZ_STACK_CLASS AutoEntryScript : public AutoJSAPI {
|
||||
#ifdef MOZ_GECKO_PROFILER
|
||||
AutoProfilerLabel mAutoProfilerLabel;
|
||||
#endif
|
||||
AutoRequestJSThreadExecution mJSThreadExecution;
|
||||
};
|
||||
|
||||
/*
|
||||
@ -422,6 +424,8 @@ class AutoNoJSAPI : protected ScriptSettingsStackEntry,
|
||||
// fix JSAutoNullableRealm to not hold on to a JSContext either, or
|
||||
// something.
|
||||
JSContext* mCx;
|
||||
|
||||
AutoYieldJSThreadExecution mExecutionYield;
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
|
238
dom/workers/JSExecutionManager.cpp
Normal file
238
dom/workers/JSExecutionManager.cpp
Normal file
@ -0,0 +1,238 @@
|
||||
/* -*- 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 "JSExecutionManager.h"
|
||||
#include "WorkerPrivate.h"
|
||||
|
||||
#include "mozilla/dom/DocGroup.h"
|
||||
#include "mozilla/dom/ScriptSettings.h"
|
||||
#include "mozilla/StaticMutex.h"
|
||||
#include "mozilla/StaticPtr.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
JSExecutionManager* JSExecutionManager::mCurrentMTManager;
|
||||
|
||||
const uint32_t kTimeSliceExpirationMS = 50;
|
||||
|
||||
using RequestState = JSExecutionManager::RequestState;
|
||||
|
||||
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 dom
|
||||
} // namespace mozilla
|
192
dom/workers/JSExecutionManager.h
Normal file
192
dom/workers/JSExecutionManager.h
Normal file
@ -0,0 +1,192 @@
|
||||
/* -*- 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 "mozilla/dom/WorkerCommon.h"
|
||||
#include "mozilla/dom/WorkerRef.h"
|
||||
#include "mozilla/dom/WorkerStatus.h"
|
||||
#include "mozilla/StaticPtr.h"
|
||||
|
||||
#include "nsICancelableRunnable.h"
|
||||
|
||||
#include "mozilla/Atomics.h"
|
||||
#include "mozilla/CondVar.h"
|
||||
#include "mozilla/Mutex.h"
|
||||
#include "nsISupportsImpl.h"
|
||||
#include "nsThreadUtils.h" /* nsRunnable */
|
||||
|
||||
#include <deque>
|
||||
|
||||
struct JSContext;
|
||||
class nsIEventTarget;
|
||||
class nsIGlobalObject;
|
||||
|
||||
// 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.
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class ErrorResult;
|
||||
|
||||
namespace dom {
|
||||
|
||||
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 };
|
||||
|
||||
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;
|
||||
|
||||
// Number of threads currently executing concurrently for this manager.
|
||||
// Guarded by mExecutionQueueMutex.
|
||||
int32_t mRunning = 0;
|
||||
|
||||
// Number of threads allowed to run concurrently for environments managed
|
||||
// by this manager.
|
||||
// Guarded by mExecutionQueueMutex.
|
||||
int32_t mMaxRunning = 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 = 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__
|
@ -8,6 +8,7 @@
|
||||
|
||||
#include "nsSimpleEnumerator.h"
|
||||
|
||||
#include "mozilla/dom/JSExecutionManager.h"
|
||||
#include "mozilla/ClearOnShutdown.h"
|
||||
#include "mozilla/StaticPtr.h"
|
||||
|
||||
|
@ -40,6 +40,7 @@
|
||||
#include "mozilla/dom/RemoteWorkerService.h"
|
||||
#include "mozilla/dom/TimeoutHandler.h"
|
||||
#include "mozilla/dom/WorkerBinding.h"
|
||||
#include "mozilla/dom/JSExecutionManager.h"
|
||||
#include "mozilla/StorageAccess.h"
|
||||
#include "mozilla/ThreadEventQueue.h"
|
||||
#include "mozilla/ThrottledEventQueue.h"
|
||||
@ -230,6 +231,9 @@ class WorkerFinishedRunnable final : public WorkerControlRunnable {
|
||||
|
||||
virtual bool WorkerRun(JSContext* aCx,
|
||||
WorkerPrivate* aWorkerPrivate) override {
|
||||
// This may block on the main thread.
|
||||
AutoYieldJSThreadExecution yield;
|
||||
|
||||
if (!mFinishedWorker->ProxyReleaseMainThreadObjects()) {
|
||||
NS_WARNING("Failed to dispatch, going to leak!");
|
||||
}
|
||||
@ -2105,7 +2109,8 @@ WorkerPrivate::WorkerThreadAccessible::WorkerThreadAccessible(
|
||||
mRunningExpiredTimeouts(false),
|
||||
mPeriodicGCTimerRunning(false),
|
||||
mIdleGCTimerRunning(false),
|
||||
mOnLine(aParent ? aParent->OnLine() : !NS_IsOffline()) {}
|
||||
mOnLine(aParent ? aParent->OnLine() : !NS_IsOffline()),
|
||||
mJSThreadExecutionGranted(false) {}
|
||||
|
||||
namespace {
|
||||
|
||||
@ -2762,6 +2767,8 @@ void WorkerPrivate::DoRunLoop(JSContext* aCx) {
|
||||
MOZ_ACCESS_THREAD_BOUND(mWorkerThreadAccessible, data);
|
||||
MOZ_ASSERT(mThread);
|
||||
|
||||
MOZ_RELEASE_ASSERT(!GetExecutionManager());
|
||||
|
||||
{
|
||||
MutexAutoLock lock(mMutex);
|
||||
mJSContext = aCx;
|
||||
@ -3112,6 +3119,49 @@ const ClientState WorkerPrivate::GetClientState() const {
|
||||
return ClientState();
|
||||
}
|
||||
|
||||
bool WorkerPrivate::GetExecutionGranted() const {
|
||||
MOZ_ACCESS_THREAD_BOUND(mWorkerThreadAccessible, data);
|
||||
return data->mJSThreadExecutionGranted;
|
||||
}
|
||||
|
||||
void WorkerPrivate::SetExecutionGranted(bool aGranted) {
|
||||
MOZ_ACCESS_THREAD_BOUND(mWorkerThreadAccessible, data);
|
||||
data->mJSThreadExecutionGranted = aGranted;
|
||||
}
|
||||
|
||||
void WorkerPrivate::ScheduleTimeSliceExpiration(uint32_t aDelay) {
|
||||
MOZ_ACCESS_THREAD_BOUND(mWorkerThreadAccessible, data);
|
||||
|
||||
if (!data->mTSTimer) {
|
||||
data->mTSTimer = NS_NewTimer();
|
||||
MOZ_ALWAYS_SUCCEEDS(data->mTSTimer->SetTarget(mWorkerControlEventTarget));
|
||||
}
|
||||
|
||||
// Whenever an event is scheduled on the WorkerControlEventTarget an
|
||||
// interrupt is automatically requested which causes us to yield JS execution
|
||||
// and the next JS execution in the queue to execute.
|
||||
// This allows for simple code reuse of the existing interrupt callback code
|
||||
// used for control events.
|
||||
MOZ_ALWAYS_SUCCEEDS(data->mTSTimer->InitWithNamedFuncCallback(
|
||||
[](nsITimer* Timer, void* aClosure) { return; }, nullptr, aDelay,
|
||||
nsITimer::TYPE_ONE_SHOT, "TimeSliceExpirationTimer"));
|
||||
}
|
||||
|
||||
void WorkerPrivate::CancelTimeSliceExpiration() {
|
||||
MOZ_ACCESS_THREAD_BOUND(mWorkerThreadAccessible, data);
|
||||
MOZ_ALWAYS_SUCCEEDS(data->mTSTimer->Cancel());
|
||||
}
|
||||
|
||||
JSExecutionManager* WorkerPrivate::GetExecutionManager() const {
|
||||
MOZ_ACCESS_THREAD_BOUND(mWorkerThreadAccessible, data);
|
||||
return data->mExecutionManager.get();
|
||||
}
|
||||
|
||||
void WorkerPrivate::SetExecutionManager(JSExecutionManager* aManager) {
|
||||
MOZ_ACCESS_THREAD_BOUND(mWorkerThreadAccessible, data);
|
||||
data->mExecutionManager = aManager;
|
||||
}
|
||||
|
||||
const Maybe<ServiceWorkerDescriptor> WorkerPrivate::GetController() {
|
||||
MOZ_ACCESS_THREAD_BOUND(mWorkerThreadAccessible, data);
|
||||
{
|
||||
@ -3246,6 +3296,8 @@ void WorkerPrivate::ShutdownGCTimers() {
|
||||
bool WorkerPrivate::InterruptCallback(JSContext* aCx) {
|
||||
MOZ_ACCESS_THREAD_BOUND(mWorkerThreadAccessible, data);
|
||||
|
||||
AutoYieldJSThreadExecution yield;
|
||||
|
||||
// If we are here it's because a WorkerControlRunnable has been dispatched.
|
||||
// The runnable could be processed here or it could have already been
|
||||
// processed by a sync event loop.
|
||||
@ -3434,6 +3486,8 @@ WorkerPrivate::ProcessAllControlRunnablesLocked() {
|
||||
AssertIsOnWorkerThread();
|
||||
mMutex.AssertCurrentThreadOwns();
|
||||
|
||||
AutoYieldJSThreadExecution yield;
|
||||
|
||||
auto result = ProcessAllControlRunnablesResult::Nothing;
|
||||
|
||||
for (;;) {
|
||||
@ -3499,6 +3553,8 @@ bool WorkerPrivate::FreezeInternal() {
|
||||
MOZ_ACCESS_THREAD_BOUND(mWorkerThreadAccessible, data);
|
||||
NS_ASSERTION(!data->mFrozen, "Already frozen!");
|
||||
|
||||
AutoYieldJSThreadExecution yield;
|
||||
|
||||
if (data->mClientSource) {
|
||||
data->mClientSource->Freeze();
|
||||
}
|
||||
@ -3767,6 +3823,8 @@ bool WorkerPrivate::RunCurrentSyncLoop() {
|
||||
|
||||
SyncLoopInfo* loopInfo = mSyncLoopStack[currentLoopIndex];
|
||||
|
||||
AutoYieldJSThreadExecution yield;
|
||||
|
||||
MOZ_ASSERT(loopInfo);
|
||||
MOZ_ASSERT(!loopInfo->mHasRun);
|
||||
MOZ_ASSERT(!loopInfo->mCompleted);
|
||||
@ -3844,6 +3902,8 @@ bool WorkerPrivate::DestroySyncLoop(uint32_t aLoopIndex) {
|
||||
MOZ_ASSERT(!mSyncLoopStack.IsEmpty());
|
||||
MOZ_ASSERT(mSyncLoopStack.Length() - 1 == aLoopIndex);
|
||||
|
||||
AutoYieldJSThreadExecution yield;
|
||||
|
||||
// We're about to delete the loop, stash its event target and result.
|
||||
SyncLoopInfo* loopInfo = mSyncLoopStack[aLoopIndex];
|
||||
nsIEventTarget* nestedEventTarget =
|
||||
@ -4096,6 +4156,7 @@ void WorkerPrivate::EnterDebuggerEventLoop() {
|
||||
MOZ_ASSERT(cx);
|
||||
|
||||
AutoPushEventLoopGlobal eventLoopGlobal(this, cx);
|
||||
AutoYieldJSThreadExecution yield;
|
||||
|
||||
CycleCollectedJSContext* ccjscx = CycleCollectedJSContext::Get();
|
||||
|
||||
@ -4197,6 +4258,10 @@ void WorkerPrivate::ReportErrorToDebugger(const nsAString& aFilename,
|
||||
bool WorkerPrivate::NotifyInternal(WorkerStatus aStatus) {
|
||||
MOZ_ACCESS_THREAD_BOUND(mWorkerThreadAccessible, data);
|
||||
|
||||
// Yield execution while notifying out-of-module WorkerRefs and cancelling
|
||||
// runnables.
|
||||
AutoYieldJSThreadExecution yield;
|
||||
|
||||
NS_ASSERTION(aStatus > Running && aStatus < Dead, "Bad status!");
|
||||
|
||||
RefPtr<EventTarget> eventTarget;
|
||||
@ -4867,18 +4932,41 @@ void WorkerPrivate::ResetWorkerPrivateInWorkerThread() {
|
||||
|
||||
void WorkerPrivate::BeginCTypesCall() {
|
||||
AssertIsOnWorkerThread();
|
||||
MOZ_ACCESS_THREAD_BOUND(mWorkerThreadAccessible, data);
|
||||
|
||||
// Don't try to GC while we're blocked in a ctypes call.
|
||||
SetGCTimerMode(NoTimer);
|
||||
|
||||
data->mYieldJSThreadExecution.EmplaceBack();
|
||||
}
|
||||
|
||||
void WorkerPrivate::EndCTypesCall() {
|
||||
AssertIsOnWorkerThread();
|
||||
MOZ_ACCESS_THREAD_BOUND(mWorkerThreadAccessible, data);
|
||||
|
||||
data->mYieldJSThreadExecution.RemoveLastElement();
|
||||
|
||||
// Make sure the periodic timer is running before we start running JS again.
|
||||
SetGCTimerMode(PeriodicTimer);
|
||||
}
|
||||
|
||||
void WorkerPrivate::BeginCTypesCallback() {
|
||||
AssertIsOnWorkerThread();
|
||||
|
||||
// Make sure the periodic timer is running before we start running JS again.
|
||||
SetGCTimerMode(PeriodicTimer);
|
||||
|
||||
// Re-requesting execution is not needed since the JSRuntime code calling
|
||||
// this will do an AutoEntryScript.
|
||||
}
|
||||
|
||||
void WorkerPrivate::EndCTypesCallback() {
|
||||
AssertIsOnWorkerThread();
|
||||
|
||||
// Don't try to GC while we're blocked in a ctypes call.
|
||||
SetGCTimerMode(NoTimer);
|
||||
}
|
||||
|
||||
bool WorkerPrivate::ConnectMessagePort(JSContext* aCx,
|
||||
UniqueMessagePortId& aIdentifier) {
|
||||
AssertIsOnWorkerThread();
|
||||
|
@ -46,6 +46,7 @@ enum WorkerType { WorkerTypeDedicated, WorkerTypeShared, WorkerTypeService };
|
||||
class ClientInfo;
|
||||
class ClientSource;
|
||||
class Function;
|
||||
class JSExecutionManager;
|
||||
class MessagePort;
|
||||
class UniqueMessagePortId;
|
||||
class PerformanceStorage;
|
||||
@ -171,6 +172,9 @@ class WorkerPrivate : public RelativeTimeline {
|
||||
void WaitForIsDebuggerRegistered(bool aDebuggerRegistered) {
|
||||
AssertIsOnParentThread();
|
||||
|
||||
// Yield so that the main thread won't be blocked.
|
||||
AutoYieldJSThreadExecution yield;
|
||||
|
||||
MOZ_ASSERT(!NS_IsMainThread());
|
||||
|
||||
MutexAutoLock lock(mMutex);
|
||||
@ -356,17 +360,9 @@ class WorkerPrivate : public RelativeTimeline {
|
||||
// This may block!
|
||||
void EndCTypesCall();
|
||||
|
||||
void BeginCTypesCallback() {
|
||||
// If a callback is beginning then we need to do the exact same thing as
|
||||
// when a ctypes call ends.
|
||||
EndCTypesCall();
|
||||
}
|
||||
void BeginCTypesCallback();
|
||||
|
||||
void EndCTypesCallback() {
|
||||
// If a callback is ending then we need to do the exact same thing as
|
||||
// when a ctypes call begins.
|
||||
BeginCTypesCall();
|
||||
}
|
||||
void EndCTypesCallback();
|
||||
|
||||
bool ConnectMessagePort(JSContext* aCx, UniqueMessagePortId& aIdentifier);
|
||||
|
||||
@ -465,6 +461,15 @@ class WorkerPrivate : public RelativeTimeline {
|
||||
|
||||
const ClientState GetClientState() const;
|
||||
|
||||
bool GetExecutionGranted() const;
|
||||
void SetExecutionGranted(bool aGranted);
|
||||
|
||||
void ScheduleTimeSliceExpiration(uint32_t aDelay);
|
||||
void CancelTimeSliceExpiration();
|
||||
|
||||
JSExecutionManager* GetExecutionManager() const;
|
||||
void SetExecutionManager(JSExecutionManager* aManager);
|
||||
|
||||
const Maybe<ServiceWorkerDescriptor> GetController();
|
||||
|
||||
void Control(const ServiceWorkerDescriptor& aServiceWorker);
|
||||
@ -1173,6 +1178,15 @@ class WorkerPrivate : public RelativeTimeline {
|
||||
// thread.
|
||||
nsCOMPtr<nsIGlobalObject> mCurrentEventLoopGlobal;
|
||||
|
||||
// Timer that triggers an interrupt on expiration of the current time slice
|
||||
nsCOMPtr<nsITimer> mTSTimer;
|
||||
|
||||
// Execution manager used to regulate execution for this worker.
|
||||
RefPtr<JSExecutionManager> mExecutionManager;
|
||||
|
||||
// Used to relinguish clearance for CTypes Callbacks.
|
||||
nsTArray<AutoYieldJSThreadExecution> mYieldJSThreadExecution;
|
||||
|
||||
uint32_t mNumWorkerRefsPreventingShutdownStart;
|
||||
uint32_t mDebuggerEventLoopLevel;
|
||||
|
||||
@ -1185,6 +1199,7 @@ class WorkerPrivate : public RelativeTimeline {
|
||||
bool mPeriodicGCTimerRunning;
|
||||
bool mIdleGCTimerRunning;
|
||||
bool mOnLine;
|
||||
bool mJSThreadExecutionGranted;
|
||||
};
|
||||
ThreadBound<WorkerThreadAccessible> mWorkerThreadAccessible;
|
||||
|
||||
|
@ -14,6 +14,7 @@
|
||||
|
||||
#include "mozilla/DebugOnly.h"
|
||||
#include "mozilla/ErrorResult.h"
|
||||
#include "mozilla/dom/JSExecutionManager.h"
|
||||
#include "mozilla/dom/ScriptSettings.h"
|
||||
#include "mozilla/Telemetry.h"
|
||||
|
||||
|
@ -31,6 +31,7 @@
|
||||
#include "mozilla/dom/SimpleGlobalObject.h"
|
||||
#include "mozilla/dom/TimeoutHandler.h"
|
||||
#include "mozilla/dom/WorkerDebuggerGlobalScopeBinding.h"
|
||||
#include "mozilla/dom/JSExecutionManager.h"
|
||||
#include "mozilla/dom/WorkerGlobalScopeBinding.h"
|
||||
#include "mozilla/dom/WorkerLocation.h"
|
||||
#include "mozilla/dom/WorkerNavigator.h"
|
||||
|
@ -12,6 +12,7 @@ DIRS += ['remoteworkers', 'sharedworkers']
|
||||
# Public stuff.
|
||||
EXPORTS.mozilla.dom += [
|
||||
'ChromeWorker.h',
|
||||
'JSExecutionManager.h',
|
||||
'Worker.h',
|
||||
'WorkerCommon.h',
|
||||
'WorkerDebugger.h',
|
||||
@ -45,6 +46,7 @@ XPIDL_SOURCES += [
|
||||
UNIFIED_SOURCES += [
|
||||
'ChromeWorker.cpp',
|
||||
'ChromeWorkerScope.cpp',
|
||||
'JSExecutionManager.cpp',
|
||||
'MessageEventRunnable.cpp',
|
||||
'Principal.cpp',
|
||||
'RegisterBindings.cpp',
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include "WinUtils.h"
|
||||
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
#include "mozilla/dom/JSExecutionManager.h"
|
||||
#include "mozilla/ipc/ProtocolUtils.h"
|
||||
#include "mozilla/PaintTracker.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
@ -1067,6 +1068,10 @@ bool MessageChannel::WaitForSyncNotify(bool aHandleWindowsMessages) {
|
||||
bool MessageChannel::WaitForInterruptNotify() {
|
||||
mMonitor->AssertCurrentThreadOwns();
|
||||
|
||||
// Receiving the interrupt notification may require JS to execute on a
|
||||
// worker.
|
||||
dom::AutoYieldJSThreadExecution yield;
|
||||
|
||||
if (!gUIThreadId) {
|
||||
mozilla::ipc::windows::InitUIThread();
|
||||
}
|
||||
|
@ -75,6 +75,7 @@
|
||||
#include "mozilla/TimelineMarker.h"
|
||||
#include "mozilla/Unused.h"
|
||||
#include "mozilla/dom/DOMJSClass.h"
|
||||
#include "mozilla/dom/JSExecutionManager.h"
|
||||
#include "mozilla/dom/ProfileTimelineMarkerBinding.h"
|
||||
#include "mozilla/dom/Promise.h"
|
||||
#include "mozilla/dom/PromiseBinding.h"
|
||||
@ -592,6 +593,8 @@ CycleCollectedJSRuntime::CycleCollectedJSRuntime(JSContext* aCx)
|
||||
|
||||
JS_SetObjectsTenuredCallback(aCx, JSObjectsTenuredCb, this);
|
||||
JS::SetOutOfMemoryCallback(aCx, OutOfMemoryCallback, this);
|
||||
JS::SetWaitCallback(mJSRuntime, BeforeWaitCallback, AfterWaitCallback,
|
||||
sizeof(dom::AutoYieldJSThreadExecution));
|
||||
JS::SetWarningReporter(aCx, MozCrashWarningReporter);
|
||||
|
||||
js::AutoEnterOOMUnsafeRegion::setAnnotateOOMAllocationSizeCallback(
|
||||
@ -1008,6 +1011,22 @@ void CycleCollectedJSRuntime::OutOfMemoryCallback(JSContext* aContext,
|
||||
self->OnOutOfMemory();
|
||||
}
|
||||
|
||||
/* static */
|
||||
void* CycleCollectedJSRuntime::BeforeWaitCallback(uint8_t* aMemory) {
|
||||
MOZ_ASSERT(aMemory);
|
||||
|
||||
// aMemory is stack allocated memory to contain our RAII object. This allows
|
||||
// for us to avoid allocations on the heap during this callback.
|
||||
return new (aMemory) dom::AutoYieldJSThreadExecution;
|
||||
}
|
||||
|
||||
/* static */
|
||||
void CycleCollectedJSRuntime::AfterWaitCallback(void* aCookie) {
|
||||
MOZ_ASSERT(aCookie);
|
||||
static_cast<dom::AutoYieldJSThreadExecution*>(aCookie)
|
||||
->~AutoYieldJSThreadExecution();
|
||||
}
|
||||
|
||||
struct JsGcTracer : public TraceCallbacks {
|
||||
virtual void Trace(JS::Heap<JS::Value>* aPtr, const char* aName,
|
||||
void* aClosure) const override {
|
||||
|
@ -186,6 +186,9 @@ class CycleCollectedJSRuntime {
|
||||
|
||||
static bool ContextCallback(JSContext* aCx, unsigned aOperation, void* aData);
|
||||
|
||||
static void* BeforeWaitCallback(uint8_t* aMemory);
|
||||
static void AfterWaitCallback(void* aCookie);
|
||||
|
||||
virtual void TraceNativeBlackRoots(JSTracer* aTracer){};
|
||||
void TraceNativeGrayRoots(JSTracer* aTracer);
|
||||
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include <utility>
|
||||
|
||||
#include "mozilla/AbstractThread.h"
|
||||
#include "mozilla/dom/JSExecutionManager.h"
|
||||
#include "mozilla/Monitor.h"
|
||||
#include "nsThreadUtils.h"
|
||||
|
||||
@ -61,6 +62,10 @@ class SyncRunnable : public Runnable {
|
||||
rv = aThread->Dispatch(this, NS_DISPATCH_NORMAL);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
mozilla::MonitorAutoLock lock(mMonitor);
|
||||
// This could be synchronously dispatching to a thread currently waiting
|
||||
// for JS execution clearance. Yield JS execution.
|
||||
dom::AutoYieldJSThreadExecution yield;
|
||||
|
||||
while (!mDone) {
|
||||
lock.Wait();
|
||||
}
|
||||
|
@ -1096,6 +1096,7 @@ nsThread::ProcessNextEvent(bool aMayWait, bool* aResult) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
Maybe<dom::AutoNoJSAPI> noJSAPI;
|
||||
if (mIsMainThread) {
|
||||
DoMainThreadSpecificProcessing(reallyWait);
|
||||
}
|
||||
@ -1105,7 +1106,6 @@ nsThread::ProcessNextEvent(bool aMayWait, bool* aResult) {
|
||||
// We only want to create an AutoNoJSAPI on threads that actually do DOM stuff
|
||||
// (including workers). Those are exactly the threads that have an
|
||||
// mScriptObserver.
|
||||
Maybe<dom::AutoNoJSAPI> noJSAPI;
|
||||
bool callScriptObserver = !!mScriptObserver;
|
||||
if (callScriptObserver) {
|
||||
noJSAPI.emplace();
|
||||
|
Loading…
Reference in New Issue
Block a user