Bug 1092102 - Implement worker debugger runnables;r=khuey

This commit is contained in:
Eddy Bruël 2015-03-04 15:11:32 +01:00
parent 9a07bdde1f
commit 39c0f79c72
4 changed files with 154 additions and 30 deletions

View File

@ -2612,6 +2612,37 @@ WorkerPrivateParent<Derived>::DispatchControlRunnable(
return NS_OK;
}
template <class Derived>
nsresult
WorkerPrivateParent<Derived>::DispatchDebuggerRunnable(
WorkerRunnable *aDebuggerRunnable)
{
// May be called on any thread!
MOZ_ASSERT(aDebuggerRunnable);
nsRefPtr<WorkerRunnable> runnable = aDebuggerRunnable;
WorkerPrivate* self = ParentAsWorkerPrivate();
{
MutexAutoLock lock(mMutex);
if (self->mStatus == Dead) {
NS_WARNING("A debugger runnable was posted to a worker that is already "
"shutting down!");
return NS_ERROR_UNEXPECTED;
}
// Transfer ownership to the debugger queue.
self->mDebuggerQueue.Push(runnable.forget().take());
mCondVar.Notify();
}
return NS_OK;
}
template <class Derived>
already_AddRefed<WorkerRunnable>
WorkerPrivateParent<Derived>::MaybeWrapAsWorkerRunnable(nsIRunnable* aRunnable)
@ -4619,19 +4650,15 @@ WorkerPrivate::DoRunLoop(JSContext* aCx)
Maybe<JSAutoCompartment> workerCompartment;
for (;;) {
// Workers lazily create a global object in CompileScriptRunnable. We need
// to enter the global's compartment as soon as it has been created.
if (!workerCompartment && GlobalScope()) {
workerCompartment.emplace(aCx, GlobalScope()->GetGlobalJSObject());
}
Status currentStatus;
bool debuggerRunnablesPending = false;
bool normalRunnablesPending = false;
{
MutexAutoLock lock(mMutex);
while (mControlQueue.IsEmpty() &&
!(debuggerRunnablesPending = !mDebuggerQueue.IsEmpty()) &&
!(normalRunnablesPending = NS_HasPendingEvents(mThread))) {
WaitForWorkerEvents();
}
@ -4693,33 +4720,54 @@ WorkerPrivate::DoRunLoop(JSContext* aCx)
}
}
// Nothing to do here if we don't have any runnables in the main queue.
if (!normalRunnablesPending) {
SetGCTimerMode(IdleTimer);
continue;
if (debuggerRunnablesPending || normalRunnablesPending) {
// Start the periodic GC timer if it is not already running.
SetGCTimerMode(PeriodicTimer);
}
MOZ_ASSERT(NS_HasPendingEvents(mThread));
if (debuggerRunnablesPending) {
WorkerRunnable* runnable;
// Start the periodic GC timer if it is not already running.
SetGCTimerMode(PeriodicTimer);
{
MutexAutoLock lock(mMutex);
// Process a single runnable from the main queue.
MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(mThread, false));
mDebuggerQueue.Pop(runnable);
debuggerRunnablesPending = !mDebuggerQueue.IsEmpty();
}
// Only perform the Promise microtask checkpoint on the outermost event
// loop. Don't run it, for example, during sync XHR or importScripts.
(void)Promise::PerformMicroTaskCheckpoint();
MOZ_ASSERT(runnable);
static_cast<nsIRunnable*>(runnable)->Run();
runnable->Release();
if (NS_HasPendingEvents(mThread)) {
// Now *might* be a good time to GC. Let the JS engine make the decision.
if (workerCompartment) {
if (debuggerRunnablesPending) {
WorkerDebuggerGlobalScope* globalScope = DebuggerGlobalScope();
MOZ_ASSERT(globalScope);
// Now *might* be a good time to GC. Let the JS engine make the decision.
JSAutoCompartment ac(aCx, globalScope->GetGlobalJSObject());
JS_MaybeGC(aCx);
}
} else if (normalRunnablesPending) {
MOZ_ASSERT(NS_HasPendingEvents(mThread));
// Process a single runnable from the main queue.
MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(mThread, false));
// Only perform the Promise microtask checkpoint on the outermost event
// loop. Don't run it, for example, during sync XHR or importScripts.
(void)Promise::PerformMicroTaskCheckpoint();
normalRunnablesPending = NS_HasPendingEvents(mThread);
if (normalRunnablesPending && GlobalScope()) {
// Now *might* be a good time to GC. Let the JS engine make the decision.
JSAutoCompartment ac(aCx, GlobalScope()->GetGlobalJSObject());
JS_MaybeGC(aCx);
}
}
else {
// The normal event queue has been exhausted, cancel the periodic GC timer
// and schedule the idle GC timer.
if (!debuggerRunnablesPending && !normalRunnablesPending) {
// Both the debugger event queue and the normal event queue has been
// exhausted, cancel the periodic GC timer and schedule the idle GC timer.
SetGCTimerMode(IdleTimer);
}
}

View File

@ -259,6 +259,9 @@ public:
nsresult
DispatchControlRunnable(WorkerControlRunnable* aWorkerControlRunnable);
nsresult
DispatchDebuggerRunnable(WorkerRunnable* aDebuggerRunnable);
already_AddRefed<WorkerRunnable>
MaybeWrapAsWorkerRunnable(nsIRunnable* aRunnable);
@ -763,6 +766,7 @@ class WorkerPrivate : public WorkerPrivateParent<WorkerPrivate>
nsRefPtr<WorkerDebugger> mDebugger;
Queue<WorkerControlRunnable*, 4> mControlQueue;
Queue<WorkerRunnable*, 4> mDebuggerQueue;
// Touched on multiple threads, protected with mMutex.
JSContext* mJSContext;

View File

@ -50,6 +50,22 @@ WorkerRunnable::WorkerRunnable(WorkerPrivate* aWorkerPrivate,
}
#endif
bool
WorkerRunnable::IsDebuggerRunnable() const
{
return false;
}
nsIGlobalObject*
WorkerRunnable::DefaultGlobalObject() const
{
if (IsDebuggerRunnable()) {
return mWorkerPrivate->DebuggerGlobalScope();
} else {
return mWorkerPrivate->GlobalScope();
}
}
bool
WorkerRunnable::PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
{
@ -121,7 +137,11 @@ WorkerRunnable::DispatchInternal()
{
if (mBehavior == WorkerThreadModifyBusyCount ||
mBehavior == WorkerThreadUnchangedBusyCount) {
return NS_SUCCEEDED(mWorkerPrivate->Dispatch(this));
if (IsDebuggerRunnable()) {
return NS_SUCCEEDED(mWorkerPrivate->DispatchDebuggerRunnable(this));
} else {
return NS_SUCCEEDED(mWorkerPrivate->Dispatch(this));
}
}
MOZ_ASSERT(mBehavior == ParentThreadUnchangedBusyCount);
@ -285,9 +305,13 @@ WorkerRunnable::Run()
MOZ_ASSERT(isMainThread == NS_IsMainThread());
nsRefPtr<WorkerPrivate> kungFuDeathGrip;
if (targetIsWorkerThread) {
globalObject = mWorkerPrivate->GlobalScope();
}
else {
JSObject* global = JS::CurrentGlobalOrNull(GetCurrentThreadJSContext());
if (global) {
globalObject = GetGlobalObjectForGlobal(global);
} else {
globalObject = DefaultGlobalObject();
}
} else {
kungFuDeathGrip = mWorkerPrivate;
if (isMainThread) {
globalObject = static_cast<nsGlobalWindow*>(mWorkerPrivate->GetWindow());
@ -327,8 +351,8 @@ WorkerRunnable::Run()
// In the case of CompileScriptRunnnable, WorkerRun above can cause us to
// lazily create a global, so we construct aes here before calling PostRun.
if (targetIsWorkerThread && !aes && mWorkerPrivate->GlobalScope()) {
aes.emplace(mWorkerPrivate->GlobalScope(), false, GetCurrentThreadJSContext());
if (targetIsWorkerThread && !aes && DefaultGlobalObject()) {
aes.emplace(DefaultGlobalObject(), false, GetCurrentThreadJSContext());
cx = aes->cx();
}
@ -349,6 +373,14 @@ WorkerRunnable::Cancel()
return (canceledCount == 1) ? NS_OK : NS_ERROR_UNEXPECTED;
}
void
WorkerDebuggerRunnable::PostDispatch(JSContext* aCx,
WorkerPrivate* aWorkerPrivate,
bool aDispatchResult)
{
MaybeReportMainThreadException(aCx, aDispatchResult);
}
WorkerSyncRunnable::WorkerSyncRunnable(WorkerPrivate* aWorkerPrivate,
nsIEventTarget* aSyncLoopTarget)
: WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount),

View File

@ -99,6 +99,14 @@ protected:
virtual ~WorkerRunnable()
{ }
// Returns true if this runnable should be dispatched to the debugger queue,
// and false otherwise.
virtual bool
IsDebuggerRunnable() const;
nsIGlobalObject*
DefaultGlobalObject() const;
// By default asserts that Dispatch() is being called on the right thread
// (ParentThread if |mTarget| is WorkerThread, or WorkerThread otherwise).
// Also increments the busy count of |mWorkerPrivate| if targeting the
@ -133,6 +141,38 @@ protected:
NS_DECL_NSIRUNNABLE
};
// This runnable is used to send a message to a worker debugger.
class WorkerDebuggerRunnable : public WorkerRunnable
{
protected:
explicit WorkerDebuggerRunnable(WorkerPrivate* aWorkerPrivate)
: WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
{
}
virtual ~WorkerDebuggerRunnable()
{ }
private:
virtual bool
IsDebuggerRunnable() const MOZ_OVERRIDE
{
return true;
}
virtual bool
PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE
{
AssertIsOnMainThread();
return true;
}
virtual void
PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
bool aDispatchResult) MOZ_OVERRIDE;
};
// This runnable is used to send a message directly to a worker's sync loop.
class WorkerSyncRunnable : public WorkerRunnable
{