Bug 916753, Bug 916531, Bug 916504 - Fix various deadlocks, r=billm,jandem.

This commit is contained in:
Brian Hackett 2013-09-17 11:29:28 -06:00
Родитель 3cdaadd0f1
Коммит aef42563a1
6 изменённых файлов: 117 добавлений и 71 удалений

Просмотреть файл

@ -5015,13 +5015,14 @@ static AsmJSParallelTask *
GetFinishedCompilation(ModuleCompiler &m, ParallelGroupState &group)
{
AutoLockWorkerThreadState lock(*m.cx()->workerThreadState());
AutoPauseCurrentWorkerThread maybePause(m.cx());
while (!group.state.asmJSWorkerFailed()) {
if (!group.state.asmJSFinishedList.empty()) {
group.outstandingJobs--;
return group.state.asmJSFinishedList.popCopy();
}
group.state.wait(WorkerThreadState::MAIN);
group.state.wait(WorkerThreadState::CONSUMER);
}
return NULL;
@ -5139,10 +5140,12 @@ CancelOutstandingJobs(ModuleCompiler &m, ParallelGroupState &group)
// Eliminate tasks that failed without adding to the finished list.
group.outstandingJobs -= group.state.harvestFailedAsmJSJobs();
AutoPauseCurrentWorkerThread maybePause(m.cx());
// Any remaining tasks are therefore undergoing active compilation.
JS_ASSERT(group.outstandingJobs >= 0);
while (group.outstandingJobs > 0) {
group.state.wait(WorkerThreadState::MAIN);
group.state.wait(WorkerThreadState::CONSUMER);
group.outstandingJobs -= group.state.harvestFailedAsmJSJobs();
group.outstandingJobs -= group.state.asmJSFinishedList.length();

Просмотреть файл

@ -1934,6 +1934,10 @@ js::TriggerGC(JSRuntime *rt, JS::gcreason::Reason reason)
return;
}
/* Don't trigger GCs when allocating under the operation callback lock. */
if (rt->currentThreadOwnsOperationCallbackLock())
return;
JS_ASSERT(CurrentThreadCanAccessRuntime(rt));
if (rt->isHeapBusy())
@ -1957,6 +1961,10 @@ js::TriggerZoneGC(Zone *zone, JS::gcreason::Reason reason)
JSRuntime *rt = zone->runtimeFromMainThread();
/* Don't trigger GCs when allocating under the operation callback lock. */
if (rt->currentThreadOwnsOperationCallbackLock())
return;
if (rt->isHeapBusy())
return;

Просмотреть файл

@ -71,7 +71,7 @@ js::StartOffThreadAsmJSCompile(ExclusiveContext *cx, AsmJSParallelTask *asmData)
if (!state.asmJSWorklist.append(asmData))
return false;
state.notify(WorkerThreadState::WORKER);
state.notifyAll(WorkerThreadState::PRODUCER);
return true;
}
@ -89,7 +89,7 @@ js::StartOffThreadIonCompile(JSContext *cx, jit::IonBuilder *builder)
if (!state.ionWorklist.append(builder))
return false;
state.notify(WorkerThreadState::WORKER);
state.notifyAll(WorkerThreadState::PRODUCER);
return true;
}
@ -149,7 +149,7 @@ js::CancelOffThreadIonCompile(JSCompartment *compartment, JSScript *script)
CompiledScriptMatches(compartment, script, helper.ionBuilder->script()))
{
helper.ionBuilder->cancel();
state.wait(WorkerThreadState::MAIN);
state.wait(WorkerThreadState::CONSUMER);
}
}
@ -284,7 +284,7 @@ js::StartOffThreadParseScript(JSContext *cx, const CompileOptions &options,
task.forget();
state.notify(WorkerThreadState::WORKER);
state.notifyAll(WorkerThreadState::PRODUCER);
return true;
}
@ -306,7 +306,7 @@ js::WaitForOffThreadParsingToFinish(JSRuntime *rt)
if (!parseInProgress)
break;
}
state.wait(WorkerThreadState::MAIN);
state.wait(WorkerThreadState::CONSUMER);
}
}
@ -322,12 +322,12 @@ WorkerThreadState::init(JSRuntime *rt)
if (!workerLock)
return false;
mainWakeup = PR_NewCondVar(workerLock);
if (!mainWakeup)
consumerWakeup = PR_NewCondVar(workerLock);
if (!consumerWakeup)
return false;
helperWakeup = PR_NewCondVar(workerLock);
if (!helperWakeup)
producerWakeup = PR_NewCondVar(workerLock);
if (!producerWakeup)
return false;
numThreads = rt->helperThreadCount();
@ -388,11 +388,11 @@ WorkerThreadState::~WorkerThreadState()
if (workerLock)
PR_DestroyLock(workerLock);
if (mainWakeup)
PR_DestroyCondVar(mainWakeup);
if (consumerWakeup)
PR_DestroyCondVar(consumerWakeup);
if (helperWakeup)
PR_DestroyCondVar(helperWakeup);
if (producerWakeup)
PR_DestroyCondVar(producerWakeup);
}
void
@ -431,7 +431,7 @@ WorkerThreadState::wait(CondVar which, uint32_t millis)
lockOwner = NULL;
#endif
DebugOnly<PRStatus> status =
PR_WaitCondVar((which == MAIN) ? mainWakeup : helperWakeup,
PR_WaitCondVar((which == CONSUMER) ? consumerWakeup : producerWakeup,
millis ? PR_MillisecondsToInterval(millis) : PR_INTERVAL_NO_TIMEOUT);
JS_ASSERT(status == PR_SUCCESS);
#ifdef DEBUG
@ -439,18 +439,11 @@ WorkerThreadState::wait(CondVar which, uint32_t millis)
#endif
}
void
WorkerThreadState::notify(CondVar which)
{
JS_ASSERT(isLocked());
PR_NotifyCondVar((which == MAIN) ? mainWakeup : helperWakeup);
}
void
WorkerThreadState::notifyAll(CondVar which)
{
JS_ASSERT(isLocked());
PR_NotifyAllCondVar((which == MAIN) ? mainWakeup : helperWakeup);
PR_NotifyAllCondVar((which == CONSUMER) ? consumerWakeup : producerWakeup);
}
bool
@ -613,7 +606,7 @@ WorkerThread::destroy()
terminate = true;
/* Notify all workers, to ensure that this thread wakes up. */
state.notifyAll(WorkerThreadState::WORKER);
state.notifyAll(WorkerThreadState::PRODUCER);
}
PR_JoinThread(thread);
@ -667,7 +660,7 @@ WorkerThread::handleAsmJSWorkload(WorkerThreadState &state)
if (!success) {
asmData = NULL;
state.noteAsmJSFailure(asmData->func);
state.notify(WorkerThreadState::MAIN);
state.notifyAll(WorkerThreadState::CONSUMER);
return;
}
@ -676,7 +669,7 @@ WorkerThread::handleAsmJSWorkload(WorkerThreadState &state)
asmData = NULL;
// Notify the main thread in case it's blocked waiting for a LifoAlloc.
state.notify(WorkerThreadState::MAIN);
state.notifyAll(WorkerThreadState::CONSUMER);
}
void
@ -709,7 +702,7 @@ WorkerThread::handleIonWorkload(WorkerThreadState &state)
ionBuilder = NULL;
// Notify the main thread in case it is waiting for the compilation to finish.
state.notify(WorkerThreadState::MAIN);
state.notifyAll(WorkerThreadState::CONSUMER);
// Ping the main thread so that the compiled code can be incorporated
// at the next operation callback. Don't interrupt Ion code for this, as
@ -764,7 +757,7 @@ WorkerThread::handleParseWorkload(WorkerThreadState &state)
parseTask = NULL;
// Notify the main thread in case it is waiting for the parse/emit to finish.
state.notify(WorkerThreadState::MAIN);
state.notifyAll(WorkerThreadState::CONSUMER);
}
void
@ -787,7 +780,7 @@ WorkerThread::handleCompressionWorkload(WorkerThreadState &state)
compressionTask = NULL;
// Notify the main thread in case it is waiting for the compression to finish.
state.notify(WorkerThreadState::MAIN);
state.notifyAll(WorkerThreadState::CONSUMER);
}
bool
@ -802,7 +795,7 @@ js::StartOffThreadCompression(ExclusiveContext *cx, SourceCompressionTask *task)
if (!state.compressionWorklist.append(task))
return false;
state.notify(WorkerThreadState::WORKER);
state.notifyAll(WorkerThreadState::PRODUCER);
return true;
}
@ -829,23 +822,10 @@ SourceCompressionTask::complete()
WorkerThreadState &state = *cx->workerThreadState();
AutoLockWorkerThreadState lock(state);
// If this compression task is itself being completed on a worker
// thread, treat the thread as paused while waiting for the completion.
// Otherwise we will not wake up and mark this as paused due to the
// loop in AutoPauseWorkersForGC.
if (cx->workerThread()) {
state.numPaused++;
if (state.numPaused == state.numThreads)
state.notify(WorkerThreadState::MAIN);
}
while (state.compressionInProgress(this))
state.wait(WorkerThreadState::MAIN);
if (cx->workerThread()) {
state.numPaused--;
if (state.shouldPause)
cx->workerThread()->pause();
{
AutoPauseCurrentWorkerThread maybePause(cx);
while (state.compressionInProgress(this))
state.wait(WorkerThreadState::CONSUMER);
}
ss->ready_ = true;
@ -930,7 +910,7 @@ WorkerThread::threadLoop()
{
break;
}
state.wait(WorkerThreadState::WORKER);
state.wait(WorkerThreadState::PRODUCER);
}
// Dispatch tasks, prioritizing AsmJS work.
@ -976,8 +956,8 @@ AutoPauseWorkersForGC::AutoPauseWorkersForGC(JSRuntime *rt MOZ_GUARD_OBJECT_NOTI
state.shouldPause = 1;
while (state.numPaused != state.numThreads) {
state.notifyAll(WorkerThreadState::WORKER);
state.wait(WorkerThreadState::MAIN);
state.notifyAll(WorkerThreadState::PRODUCER);
state.wait(WorkerThreadState::CONSUMER);
}
}
@ -994,7 +974,42 @@ AutoPauseWorkersForGC::~AutoPauseWorkersForGC()
state.shouldPause = 0;
// Notify all workers, to ensure that each wakes up.
state.notifyAll(WorkerThreadState::WORKER);
state.notifyAll(WorkerThreadState::PRODUCER);
}
AutoPauseCurrentWorkerThread::AutoPauseCurrentWorkerThread(ExclusiveContext *cx
MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
: cx(cx)
{
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
// If the current thread is a worker thread, treat it as paused while
// the caller is waiting for another worker thread to complete. Otherwise
// we will not wake up and mark this as paused due to the loop in
// AutoPauseWorkersForGC.
if (cx->workerThread()) {
WorkerThreadState &state = *cx->workerThreadState();
JS_ASSERT(state.isLocked());
state.numPaused++;
if (state.numPaused == state.numThreads)
state.notifyAll(WorkerThreadState::CONSUMER);
}
}
AutoPauseCurrentWorkerThread::~AutoPauseCurrentWorkerThread()
{
if (cx->workerThread()) {
WorkerThreadState &state = *cx->workerThreadState();
JS_ASSERT(state.isLocked());
state.numPaused--;
// Before resuming execution of the worker thread, make sure the main
// thread does not expect worker threads to be paused.
if (state.shouldPause)
cx->workerThread()->pause();
}
}
void
@ -1009,10 +1024,10 @@ WorkerThread::pause()
// Don't bother to notify the main thread until all workers have paused.
if (state.numPaused == state.numThreads)
state.notify(WorkerThreadState::MAIN);
state.notifyAll(WorkerThreadState::CONSUMER);
while (state.shouldPause)
state.wait(WorkerThreadState::WORKER);
state.wait(WorkerThreadState::PRODUCER);
state.numPaused--;
}
@ -1080,6 +1095,16 @@ AutoPauseWorkersForGC::~AutoPauseWorkersForGC()
{
}
AutoPauseCurrentWorkerThread::AutoPauseCurrentWorkerThread(ExclusiveContext *cx
MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
{
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
}
AutoPauseCurrentWorkerThread::~AutoPauseCurrentWorkerThread()
{
}
frontend::CompileError &
ExclusiveContext::addPendingCompileError()
{

Просмотреть файл

@ -51,8 +51,11 @@ class WorkerThreadState
uint32_t numPaused;
enum CondVar {
MAIN,
WORKER
/* For notifying threads waiting for work that they may be able to make progress. */
CONSUMER,
/* For notifying threads doing work that they may be able to make progress. */
PRODUCER
};
/* Shared worklist for Ion worker threads. */
@ -88,7 +91,6 @@ class WorkerThreadState
# endif
void wait(CondVar which, uint32_t timeoutMillis = 0);
void notify(CondVar which);
void notifyAll(CondVar which);
bool canStartAsmJSCompile();
@ -136,11 +138,9 @@ class WorkerThreadState
PRThread *lockOwner;
# endif
/* Condvar to notify the main thread that work has been completed. */
PRCondVar *mainWakeup;
/* Condvar to notify helper threads that they may be able to make progress. */
PRCondVar *helperWakeup;
/* Condvars for threads waiting/notifying each other. */
PRCondVar *consumerWakeup;
PRCondVar *producerWakeup;
/*
* Number of AsmJS workers that encountered failure for the active module.
@ -336,6 +336,23 @@ class AutoPauseWorkersForGC
~AutoPauseWorkersForGC();
};
/*
* If the current thread is a worker thread, treat it as paused during this
* class's lifetime. This should be used at any time the current thread is
* waiting for a worker to complete.
*/
class AutoPauseCurrentWorkerThread
{
#ifdef JS_WORKER_THREADS
ExclusiveContext *cx;
#endif
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
public:
AutoPauseCurrentWorkerThread(ExclusiveContext *cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
~AutoPauseCurrentWorkerThread();
};
/* Wait for any in progress off thread parses to halt. */
void
PauseOffThreadParsing();

Просмотреть файл

@ -110,10 +110,8 @@ JSRuntime::JSRuntime(JSUseHelperThreads useHelperThreads)
operationCallback(NULL),
#ifdef JS_THREADSAFE
operationCallbackLock(NULL),
#ifdef DEBUG
operationCallbackOwner(NULL),
#endif
#endif
#ifdef JS_WORKER_THREADS
workerThreadState(NULL),
exclusiveAccessLock(NULL),

Просмотреть файл

@ -720,9 +720,7 @@ struct JSRuntime : public JS::shadow::Runtime,
* Protects all data that is touched in this process.
*/
PRLock *operationCallbackLock;
#ifdef DEBUG
PRThread *operationCallbackOwner;
#endif
public:
#endif // JS_THREADSAFE
@ -732,16 +730,13 @@ struct JSRuntime : public JS::shadow::Runtime,
public:
AutoLockForOperationCallback(JSRuntime *rt MOZ_GUARD_OBJECT_NOTIFIER_PARAM) : rt(rt) {
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
JS_ASSERT(!rt->currentThreadOwnsOperationCallbackLock());
PR_Lock(rt->operationCallbackLock);
#ifdef DEBUG
rt->operationCallbackOwner = PR_GetCurrentThread();
#endif
}
~AutoLockForOperationCallback() {
JS_ASSERT(rt->operationCallbackOwner == PR_GetCurrentThread());
#ifdef DEBUG
rt->operationCallbackOwner = NULL;
#endif
PR_Unlock(rt->operationCallbackLock);
}
#else // JS_THREADSAFE
@ -755,7 +750,7 @@ struct JSRuntime : public JS::shadow::Runtime,
};
bool currentThreadOwnsOperationCallbackLock() {
#if defined(JS_THREADSAFE) && defined(DEBUG)
#if defined(JS_THREADSAFE)
return operationCallbackOwner == PR_GetCurrentThread();
#else
return true;