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:
Bas Schouten 2020-03-26 00:36:24 +00:00
parent e7da41700a
commit 1bc21ff19c
18 changed files with 606 additions and 13 deletions

View File

@ -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));

View File

@ -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;

View File

@ -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());

View File

@ -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) {

View File

@ -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

View 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

View 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__

View File

@ -8,6 +8,7 @@
#include "nsSimpleEnumerator.h"
#include "mozilla/dom/JSExecutionManager.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/StaticPtr.h"

View File

@ -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();

View File

@ -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;

View File

@ -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"

View File

@ -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"

View File

@ -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',

View File

@ -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();
}

View File

@ -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 {

View File

@ -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);

View File

@ -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();
}

View File

@ -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();