From 5bab33a357be25c77cb7ea0afc87c4e4d54528ed Mon Sep 17 00:00:00 2001 From: Basilisk-Dev Date: Thu, 16 Apr 2026 12:03:33 -0400 Subject: [PATCH] Issue #3050 - Backport 1379814: Prevent HelperThreads master-task deadlock --- js/src/vm/HelperThreads.cpp | 107 +++++++++++++++++++++++------------- js/src/vm/HelperThreads.h | 2 +- 2 files changed, 70 insertions(+), 39 deletions(-) diff --git a/js/src/vm/HelperThreads.cpp b/js/src/vm/HelperThreads.cpp index b264b3bb9c..ca5b3020a5 100644 --- a/js/src/vm/HelperThreads.cpp +++ b/js/src/vm/HelperThreads.cpp @@ -860,19 +860,30 @@ GlobalHelperThreadState::waitForAllThreads() template bool -GlobalHelperThreadState::checkTaskThreadLimit(size_t maxThreads) const +GlobalHelperThreadState::checkTaskThreadLimit(size_t maxThreads, bool isMaster) const { if (maxThreads >= threadCount) return true; size_t count = 0; + size_t idle = 0; for (auto& thread : *threads) { - if (thread.currentTask.isSome() && thread.currentTask->is()) - count++; + if (thread.currentTask.isSome()) { + if (thread.currentTask->is()) + count++; + } else { + idle++; + } if (count >= maxThreads) return false; } + // At least the current thread is idle. + MOZ_ASSERT(idle > 0); + + if (isMaster && idle <= 1) + return false; + return true; } @@ -1091,7 +1102,8 @@ GlobalHelperThreadState::pendingIonCompileHasSufficientPriority( bool GlobalHelperThreadState::canStartParseTask(const AutoLockHelperThreadState& lock) { - return !parseWorklist(lock).empty() && checkTaskThreadLimit(maxParseThreads()); + return !parseWorklist(lock).empty() && + checkTaskThreadLimit(maxParseThreads(), /* isMaster = */ true); } bool @@ -1892,49 +1904,68 @@ HelperThread::threadLoop() while (true) { MOZ_ASSERT(idle()); - // Block until a task is available. Save the value of whether we are - // going to do an Ion compile, in case the value returned by the method - // changes. - bool ionCompile = false; + js::oom::ThreadType task; while (true) { if (terminate) return; - if ((ionCompile = HelperThreadState().pendingIonCompileHasSufficientPriority(lock)) || - HelperThreadState().canStartWasmCompile(lock) || - HelperThreadState().canStartPromiseTask(lock) || - HelperThreadState().canStartParseTask(lock) || - HelperThreadState().canStartCompressionTask(lock) || - HelperThreadState().canStartGCHelperTask(lock) || - HelperThreadState().canStartGCParallelTask(lock)) - { + + // Select the task type to run. Task priority is determined + // exclusively here. + // + // The selectors may depend on the HelperThreadState not changing + // between task selection and task execution, in particular, on new + // 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; - } + HelperThreadState().wait(lock, GlobalHelperThreadState::PRODUCER); } - if (ionCompile) { - js::oom::SetThreadType(js::oom::THREAD_TYPE_ION); - handleIonWorkload(lock); - } 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); + js::oom::SetThreadType(task); + switch (task) { + case js::oom::THREAD_TYPE_GCPARALLEL: 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"); } + js::oom::SetThreadType(js::oom::THREAD_TYPE_NONE); } } diff --git a/js/src/vm/HelperThreads.h b/js/src/vm/HelperThreads.h index ebd4c9b9cb..5a1cc02e9a 100644 --- a/js/src/vm/HelperThreads.h +++ b/js/src/vm/HelperThreads.h @@ -256,7 +256,7 @@ class GlobalHelperThreadState void waitForAllThreads(); template - bool checkTaskThreadLimit(size_t maxThreads) const; + bool checkTaskThreadLimit(size_t maxThreads, bool isMaster = false) const; private: