Issue #3050 - Backport 1379814: Prevent HelperThreads master-task deadlock

This commit is contained in:
Basilisk-Dev 2026-04-16 12:03:33 -04:00 • committed by wuggy
commit 5bab33a357
2 changed files with 66 additions and 35 deletions

View file

@ -860,19 +860,30 @@ GlobalHelperThreadState::waitForAllThreads()
template <typename T> template <typename T>
bool bool
GlobalHelperThreadState::checkTaskThreadLimit(size_t maxThreads) const GlobalHelperThreadState::checkTaskThreadLimit(size_t maxThreads, bool isMaster) const
{ {
if (maxThreads >= threadCount) if (maxThreads >= threadCount)
return true; return true;
size_t count = 0; size_t count = 0;
size_t idle = 0;
for (auto& thread : *threads) { for (auto& thread : *threads) {
if (thread.currentTask.isSome() && thread.currentTask->is<T>()) if (thread.currentTask.isSome()) {
count++; if (thread.currentTask->is<T>())
count++;
} else {
idle++;
}
if (count >= maxThreads) if (count >= maxThreads)
return false; return false;
} }
// At least the current thread is idle.
MOZ_ASSERT(idle > 0);
if (isMaster && idle <= 1)
return false;
return true; return true;
} }
@ -1091,7 +1102,8 @@ GlobalHelperThreadState::pendingIonCompileHasSufficientPriority(
bool bool
GlobalHelperThreadState::canStartParseTask(const AutoLockHelperThreadState& lock) GlobalHelperThreadState::canStartParseTask(const AutoLockHelperThreadState& lock)
{ {
return !parseWorklist(lock).empty() && checkTaskThreadLimit<ParseTask*>(maxParseThreads()); return !parseWorklist(lock).empty() &&
checkTaskThreadLimit<ParseTask*>(maxParseThreads(), /* isMaster = */ true);
} }
bool bool
@ -1892,49 +1904,68 @@ HelperThread::threadLoop()
while (true) { while (true) {
MOZ_ASSERT(idle()); MOZ_ASSERT(idle());
// Block until a task is available. Save the value of whether we are js::oom::ThreadType task;
// going to do an Ion compile, in case the value returned by the method
// changes.
bool ionCompile = false;
while (true) { while (true) {
if (terminate) if (terminate)
return; return;
if ((ionCompile = HelperThreadState().pendingIonCompileHasSufficientPriority(lock)) ||
HelperThreadState().canStartWasmCompile(lock) || // Select the task type to run. Task priority is determined
HelperThreadState().canStartPromiseTask(lock) || // exclusively here.
HelperThreadState().canStartParseTask(lock) || //
HelperThreadState().canStartCompressionTask(lock) || // The selectors may depend on the HelperThreadState not changing
HelperThreadState().canStartGCHelperTask(lock) || // between task selection and task execution, in particular, on new
HelperThreadState().canStartGCParallelTask(lock)) // tasks not being added (because of the lifo structure of the work
{ // lists). Unlocking the HelperThreadState between task selection
// and execution is not well-defined.
if (HelperThreadState().canStartGCParallelTask(lock))
task = js::oom::THREAD_TYPE_GCPARALLEL;
else if (HelperThreadState().canStartGCHelperTask(lock))
task = js::oom::THREAD_TYPE_GCHELPER;
else if (HelperThreadState().pendingIonCompileHasSufficientPriority(lock))
task = js::oom::THREAD_TYPE_ION;
else if (HelperThreadState().canStartWasmCompile(lock))
task = js::oom::THREAD_TYPE_ASMJS;
else if (HelperThreadState().canStartPromiseTask(lock))
task = js::oom::THREAD_TYPE_PROMISE_TASK;
else if (HelperThreadState().canStartParseTask(lock))
task = js::oom::THREAD_TYPE_PARSE;
else if (HelperThreadState().canStartCompressionTask(lock))
task = js::oom::THREAD_TYPE_COMPRESS;
else
task = js::oom::THREAD_TYPE_NONE;
if (task != js::oom::THREAD_TYPE_NONE)
break; break;
}
HelperThreadState().wait(lock, GlobalHelperThreadState::PRODUCER); HelperThreadState().wait(lock, GlobalHelperThreadState::PRODUCER);
} }
if (ionCompile) { js::oom::SetThreadType(task);
js::oom::SetThreadType(js::oom::THREAD_TYPE_ION); switch (task) {
handleIonWorkload(lock); case js::oom::THREAD_TYPE_GCPARALLEL:
} else if (HelperThreadState().canStartWasmCompile(lock)) {
js::oom::SetThreadType(js::oom::THREAD_TYPE_ASMJS);
handleWasmWorkload(lock);
} else if (HelperThreadState().canStartPromiseTask(lock)) {
js::oom::SetThreadType(js::oom::THREAD_TYPE_PROMISE_TASK);
handlePromiseTaskWorkload(lock);
} else if (HelperThreadState().canStartParseTask(lock)) {
js::oom::SetThreadType(js::oom::THREAD_TYPE_PARSE);
handleParseWorkload(lock, stackLimit);
} else if (HelperThreadState().canStartCompressionTask(lock)) {
js::oom::SetThreadType(js::oom::THREAD_TYPE_COMPRESS);
handleCompressionWorkload(lock);
} else if (HelperThreadState().canStartGCHelperTask(lock)) {
js::oom::SetThreadType(js::oom::THREAD_TYPE_GCHELPER);
handleGCHelperWorkload(lock);
} else if (HelperThreadState().canStartGCParallelTask(lock)) {
js::oom::SetThreadType(js::oom::THREAD_TYPE_GCPARALLEL);
handleGCParallelWorkload(lock); handleGCParallelWorkload(lock);
} else { break;
case js::oom::THREAD_TYPE_GCHELPER:
handleGCHelperWorkload(lock);
break;
case js::oom::THREAD_TYPE_ION:
handleIonWorkload(lock);
break;
case js::oom::THREAD_TYPE_ASMJS:
handleWasmWorkload(lock);
break;
case js::oom::THREAD_TYPE_PROMISE_TASK:
handlePromiseTaskWorkload(lock);
break;
case js::oom::THREAD_TYPE_PARSE:
handleParseWorkload(lock, stackLimit);
break;
case js::oom::THREAD_TYPE_COMPRESS:
handleCompressionWorkload(lock);
break;
default:
MOZ_CRASH("No task to perform"); MOZ_CRASH("No task to perform");
} }
js::oom::SetThreadType(js::oom::THREAD_TYPE_NONE);
} }
} }

View file

@ -256,7 +256,7 @@ class GlobalHelperThreadState
void waitForAllThreads(); void waitForAllThreads();
template <typename T> template <typename T>
bool checkTaskThreadLimit(size_t maxThreads) const; bool checkTaskThreadLimit(size_t maxThreads, bool isMaster = false) const;
private: private: