зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1714141 - Move internal thread pool into a new class r=sfink
Mostly this moves the code over to a new InternalThreadPool class in a new source file. The initialization and shutdown code had to change a little to make this work. Differential Revision: https://phabricator.services.mozilla.com/D117160
This commit is contained in:
Родитель
bf5f56a33d
Коммит
b18beb7e7f
|
@ -395,6 +395,7 @@ UNIFIED_SOURCES += [
|
||||||
"vm/HelperThreads.cpp",
|
"vm/HelperThreads.cpp",
|
||||||
"vm/Id.cpp",
|
"vm/Id.cpp",
|
||||||
"vm/Initialization.cpp",
|
"vm/Initialization.cpp",
|
||||||
|
"vm/InternalThreadPool.cpp",
|
||||||
"vm/Iteration.cpp",
|
"vm/Iteration.cpp",
|
||||||
"vm/JitActivation.cpp",
|
"vm/JitActivation.cpp",
|
||||||
"vm/JSAtom.cpp",
|
"vm/JSAtom.cpp",
|
||||||
|
|
|
@ -99,8 +99,6 @@ class GlobalHelperThreadState {
|
||||||
typedef Vector<PromiseHelperTask*, 0, SystemAllocPolicy>
|
typedef Vector<PromiseHelperTask*, 0, SystemAllocPolicy>
|
||||||
PromiseHelperTaskVector;
|
PromiseHelperTaskVector;
|
||||||
typedef Vector<JSContext*, 0, SystemAllocPolicy> ContextVector;
|
typedef Vector<JSContext*, 0, SystemAllocPolicy> ContextVector;
|
||||||
using HelperThreadVector =
|
|
||||||
Vector<UniquePtr<HelperThread>, 0, SystemAllocPolicy>;
|
|
||||||
|
|
||||||
// Count of running task by each threadType.
|
// Count of running task by each threadType.
|
||||||
mozilla::EnumeratedArray<ThreadType, ThreadType::THREAD_TYPE_MAX, size_t>
|
mozilla::EnumeratedArray<ThreadType, ThreadType::THREAD_TYPE_MAX, size_t>
|
||||||
|
@ -113,9 +111,6 @@ class GlobalHelperThreadState {
|
||||||
private:
|
private:
|
||||||
// The lists below are all protected by |lock|.
|
// The lists below are all protected by |lock|.
|
||||||
|
|
||||||
// List of available helper threads.
|
|
||||||
HelperThreadVector threads_;
|
|
||||||
|
|
||||||
// Ion compilation worklist and finished jobs.
|
// Ion compilation worklist and finished jobs.
|
||||||
IonCompileTaskVector ionWorklist_, ionFinishedList_;
|
IonCompileTaskVector ionWorklist_, ionFinishedList_;
|
||||||
IonFreeTaskVector ionFreeList_;
|
IonFreeTaskVector ionFreeList_;
|
||||||
|
@ -197,7 +192,7 @@ class GlobalHelperThreadState {
|
||||||
|
|
||||||
[[nodiscard]] bool ensureInitialized();
|
[[nodiscard]] bool ensureInitialized();
|
||||||
[[nodiscard]] bool ensureThreadCount(size_t count,
|
[[nodiscard]] bool ensureThreadCount(size_t count,
|
||||||
const AutoLockHelperThreadState& lock);
|
AutoLockHelperThreadState& lock);
|
||||||
void finish(AutoLockHelperThreadState& lock);
|
void finish(AutoLockHelperThreadState& lock);
|
||||||
void finishThreads(AutoLockHelperThreadState& lock);
|
void finishThreads(AutoLockHelperThreadState& lock);
|
||||||
|
|
||||||
|
@ -240,14 +235,6 @@ class GlobalHelperThreadState {
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
HelperThreadVector& threads(const AutoLockHelperThreadState& lock) {
|
|
||||||
return threads_;
|
|
||||||
}
|
|
||||||
const HelperThreadVector& threads(
|
|
||||||
const AutoLockHelperThreadState& lock) const {
|
|
||||||
return threads_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void notifyOne(CondVar which, const AutoLockHelperThreadState&);
|
void notifyOne(CondVar which, const AutoLockHelperThreadState&);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -488,43 +475,6 @@ static inline GlobalHelperThreadState& HelperThreadState() {
|
||||||
return *gHelperThreadState;
|
return *gHelperThreadState;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Individual helper thread, one allocated per core. */
|
|
||||||
class HelperThread {
|
|
||||||
Thread thread;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The profiling thread for this helper thread, which can be used to push
|
|
||||||
* and pop label frames.
|
|
||||||
* This field being non-null indicates that this thread has been registered
|
|
||||||
* and needs to be unregistered at shutdown.
|
|
||||||
*/
|
|
||||||
ProfilingStack* profilingStack = nullptr;
|
|
||||||
|
|
||||||
public:
|
|
||||||
HelperThread();
|
|
||||||
[[nodiscard]] bool init();
|
|
||||||
|
|
||||||
ThreadId threadId() { return thread.get_id(); }
|
|
||||||
|
|
||||||
void join();
|
|
||||||
|
|
||||||
static void ThreadMain(void* arg);
|
|
||||||
void threadLoop();
|
|
||||||
|
|
||||||
void ensureRegisteredWithProfiler();
|
|
||||||
void unregisterWithProfilerIfNeeded();
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct AutoProfilerLabel {
|
|
||||||
AutoProfilerLabel(HelperThread* helperThread, const char* label,
|
|
||||||
JS::ProfilingCategoryPair categoryPair);
|
|
||||||
~AutoProfilerLabel();
|
|
||||||
|
|
||||||
private:
|
|
||||||
ProfilingStack* profilingStack;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
class MOZ_RAII AutoSetHelperThreadContext {
|
class MOZ_RAII AutoSetHelperThreadContext {
|
||||||
JSContext* cx;
|
JSContext* cx;
|
||||||
AutoLockHelperThreadState& lock;
|
AutoLockHelperThreadState& lock;
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
#include "util/NativeStack.h"
|
#include "util/NativeStack.h"
|
||||||
#include "vm/ErrorReporting.h"
|
#include "vm/ErrorReporting.h"
|
||||||
#include "vm/HelperThreadState.h"
|
#include "vm/HelperThreadState.h"
|
||||||
|
#include "vm/InternalThreadPool.h"
|
||||||
#include "vm/MutexIDs.h"
|
#include "vm/MutexIDs.h"
|
||||||
#include "vm/SharedImmutableStringsCache.h"
|
#include "vm/SharedImmutableStringsCache.h"
|
||||||
#include "vm/Time.h"
|
#include "vm/Time.h"
|
||||||
|
@ -62,16 +63,6 @@ GlobalHelperThreadState* gHelperThreadState = nullptr;
|
||||||
|
|
||||||
} // namespace js
|
} // namespace js
|
||||||
|
|
||||||
// These macros are identical in function to the same-named ones in
|
|
||||||
// GeckoProfiler.h, but they are defined separately because SpiderMonkey can't
|
|
||||||
// use GeckoProfiler.h.
|
|
||||||
#define PROFILER_RAII_PASTE(id, line) id##line
|
|
||||||
#define PROFILER_RAII_EXPAND(id, line) PROFILER_RAII_PASTE(id, line)
|
|
||||||
#define PROFILER_RAII PROFILER_RAII_EXPAND(raiiObject, __LINE__)
|
|
||||||
#define AUTO_PROFILER_LABEL(label, categoryPair) \
|
|
||||||
HelperThread::AutoProfilerLabel PROFILER_RAII( \
|
|
||||||
this, label, JS::ProfilingCategoryPair::categoryPair)
|
|
||||||
|
|
||||||
bool js::CreateHelperThreadsState() {
|
bool js::CreateHelperThreadsState() {
|
||||||
MOZ_ASSERT(!gHelperThreadState);
|
MOZ_ASSERT(!gHelperThreadState);
|
||||||
gHelperThreadState = js_new<GlobalHelperThreadState>();
|
gHelperThreadState = js_new<GlobalHelperThreadState>();
|
||||||
|
@ -117,7 +108,7 @@ bool js::SetFakeCPUCount(size_t count) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void GlobalHelperThreadState::setCpuCount(size_t count) {
|
void GlobalHelperThreadState::setCpuCount(size_t count) {
|
||||||
// This must be called before the threads have been initialized.
|
// This must be called before any threads have been initialized.
|
||||||
AutoLockHelperThreadState lock;
|
AutoLockHelperThreadState lock;
|
||||||
MOZ_ASSERT(!isInitialized(lock));
|
MOZ_ASSERT(!isInitialized(lock));
|
||||||
|
|
||||||
|
@ -499,32 +490,6 @@ struct MOZ_RAII AutoSetContextParse {
|
||||||
~AutoSetContextParse() { TlsContext.get()->setParseTask(nullptr); }
|
~AutoSetContextParse() { TlsContext.get()->setParseTask(nullptr); }
|
||||||
};
|
};
|
||||||
|
|
||||||
// We want our default stack size limit to be approximately 2MB, to be safe, but
|
|
||||||
// expect most threads to use much less. On Linux, however, requesting a stack
|
|
||||||
// of 2MB or larger risks the kernel allocating an entire 2MB huge page for it
|
|
||||||
// on first access, which we do not want. To avoid this possibility, we subtract
|
|
||||||
// 2 standard VM page sizes from our default.
|
|
||||||
static const uint32_t kDefaultHelperStackSize = 2048 * 1024 - 2 * 4096;
|
|
||||||
static const uint32_t kDefaultHelperStackQuota = 1800 * 1024;
|
|
||||||
|
|
||||||
// TSan enforces a minimum stack size that's just slightly larger than our
|
|
||||||
// default helper stack size. It does this to store blobs of TSan-specific
|
|
||||||
// data on each thread's stack. Unfortunately, that means that even though
|
|
||||||
// we'll actually receive a larger stack than we requested, the effective
|
|
||||||
// usable space of that stack is significantly less than what we expect.
|
|
||||||
// To offset TSan stealing our stack space from underneath us, double the
|
|
||||||
// default.
|
|
||||||
//
|
|
||||||
// Note that we don't need this for ASan/MOZ_ASAN because ASan doesn't
|
|
||||||
// require all the thread-specific state that TSan does.
|
|
||||||
#if defined(MOZ_TSAN)
|
|
||||||
static const uint32_t HELPER_STACK_SIZE = 2 * kDefaultHelperStackSize;
|
|
||||||
static const uint32_t HELPER_STACK_QUOTA = 2 * kDefaultHelperStackQuota;
|
|
||||||
#else
|
|
||||||
static const uint32_t HELPER_STACK_SIZE = kDefaultHelperStackSize;
|
|
||||||
static const uint32_t HELPER_STACK_QUOTA = kDefaultHelperStackQuota;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
AutoSetHelperThreadContext::AutoSetHelperThreadContext(
|
AutoSetHelperThreadContext::AutoSetHelperThreadContext(
|
||||||
AutoLockHelperThreadState& lock)
|
AutoLockHelperThreadState& lock)
|
||||||
: lock(lock) {
|
: lock(lock) {
|
||||||
|
@ -1335,6 +1300,11 @@ bool GlobalHelperThreadState::ensureInitialized() {
|
||||||
i = 0;
|
i = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (useInternalThreadPool(lock) &&
|
||||||
|
!InternalThreadPool::Initialize(threadCount, lock)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!ensureThreadCount(threadCount, lock)) {
|
if (!ensureThreadCount(threadCount, lock)) {
|
||||||
finishThreads(lock);
|
finishThreads(lock);
|
||||||
return false;
|
return false;
|
||||||
|
@ -1346,7 +1316,7 @@ bool GlobalHelperThreadState::ensureInitialized() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GlobalHelperThreadState::ensureThreadCount(
|
bool GlobalHelperThreadState::ensureThreadCount(
|
||||||
size_t count, const AutoLockHelperThreadState& lock) {
|
size_t count, AutoLockHelperThreadState& lock) {
|
||||||
if (!ensureContextList(count, lock)) {
|
if (!ensureContextList(count, lock)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -1355,26 +1325,15 @@ bool GlobalHelperThreadState::ensureThreadCount(
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!useInternalThreadPool(lock) || threads(lock).length() >= count) {
|
if (useInternalThreadPool(lock)) {
|
||||||
return true;
|
InternalThreadPool& pool = InternalThreadPool::Get();
|
||||||
}
|
if (pool.threadCount(lock) < count) {
|
||||||
|
if (!pool.ensureThreadCount(count, lock)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!threads(lock).reserve(count)) {
|
threadCount = pool.threadCount(lock);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update threadCount on exit so this stays consistent with how many threads
|
|
||||||
// there are.
|
|
||||||
auto updateThreadCount =
|
|
||||||
mozilla::MakeScopeExit([&] { threadCount = threads(lock).length(); });
|
|
||||||
|
|
||||||
while (threads(lock).length() < count) {
|
|
||||||
auto thread = js::MakeUnique<HelperThread>();
|
|
||||||
if (!thread || !thread->init()) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
threads(lock).infallibleEmplaceBack(std::move(thread));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -1418,23 +1377,11 @@ void GlobalHelperThreadState::finish(AutoLockHelperThreadState& lock) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void GlobalHelperThreadState::finishThreads(AutoLockHelperThreadState& lock) {
|
void GlobalHelperThreadState::finishThreads(AutoLockHelperThreadState& lock) {
|
||||||
HelperThreadVector oldThreads;
|
|
||||||
|
|
||||||
waitForAllTasksLocked(lock);
|
waitForAllTasksLocked(lock);
|
||||||
|
|
||||||
terminating_ = true;
|
terminating_ = true;
|
||||||
|
|
||||||
if (!useInternalThreadPool(lock)) {
|
if (InternalThreadPool::IsInitialized()) {
|
||||||
return;
|
InternalThreadPool::ShutDown(lock);
|
||||||
}
|
|
||||||
|
|
||||||
notifyAll(GlobalHelperThreadState::PRODUCER, lock);
|
|
||||||
|
|
||||||
std::swap(threads_, oldThreads);
|
|
||||||
|
|
||||||
AutoUnlockHelperThreadState unlock(lock);
|
|
||||||
for (auto& thread : oldThreads) {
|
|
||||||
thread->join();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1477,7 +1424,7 @@ void GlobalHelperThreadState::assertIsLockedByCurrentThread() const {
|
||||||
void GlobalHelperThreadState::dispatch(
|
void GlobalHelperThreadState::dispatch(
|
||||||
const AutoLockHelperThreadState& locked) {
|
const AutoLockHelperThreadState& locked) {
|
||||||
if (useInternalThreadPool(locked)) {
|
if (useInternalThreadPool(locked)) {
|
||||||
notifyOne(PRODUCER, locked);
|
InternalThreadPool::Get().dispatchTask(locked);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1635,7 +1582,10 @@ void GlobalHelperThreadState::addSizeOfIncludingThis(
|
||||||
|
|
||||||
htStats.stateData += mallocSizeOf(this);
|
htStats.stateData += mallocSizeOf(this);
|
||||||
|
|
||||||
htStats.stateData += threads(lock).sizeOfExcludingThis(mallocSizeOf);
|
if (InternalThreadPool::IsInitialized()) {
|
||||||
|
htStats.stateData +=
|
||||||
|
InternalThreadPool::Get().sizeOfIncludingThis(mallocSizeOf, lock);
|
||||||
|
}
|
||||||
|
|
||||||
// Report memory used by various containers
|
// Report memory used by various containers
|
||||||
htStats.stateData +=
|
htStats.stateData +=
|
||||||
|
@ -2460,56 +2410,6 @@ void GlobalHelperThreadState::mergeParseTaskRealm(JSContext* cx,
|
||||||
gc::MergeRealms(parseTask->parseGlobal->as<GlobalObject>().realm(), dest);
|
gc::MergeRealms(parseTask->parseGlobal->as<GlobalObject>().realm(), dest);
|
||||||
}
|
}
|
||||||
|
|
||||||
HelperThread::HelperThread()
|
|
||||||
: thread(Thread::Options().setStackSize(HELPER_STACK_SIZE)) {}
|
|
||||||
|
|
||||||
bool HelperThread::init() {
|
|
||||||
return thread.init(HelperThread::ThreadMain, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void HelperThread::join() { thread.join(); }
|
|
||||||
|
|
||||||
void HelperThread::ensureRegisteredWithProfiler() {
|
|
||||||
if (profilingStack) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: To avoid dead locks, we should not hold on the helper thread lock
|
|
||||||
// while calling this function. This is safe because the registerThread field
|
|
||||||
// is a WriteOnceData<> type stored on the global helper tread state.
|
|
||||||
JS::RegisterThreadCallback callback = HelperThreadState().registerThread;
|
|
||||||
if (callback) {
|
|
||||||
profilingStack =
|
|
||||||
callback("JS Helper", reinterpret_cast<void*>(GetNativeStackBase()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void HelperThread::unregisterWithProfilerIfNeeded() {
|
|
||||||
if (!profilingStack) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: To avoid dead locks, we should not hold on the helper thread lock
|
|
||||||
// while calling this function. This is safe because the unregisterThread
|
|
||||||
// field is a WriteOnceData<> type stored on the global helper tread state.
|
|
||||||
JS::UnregisterThreadCallback callback = HelperThreadState().unregisterThread;
|
|
||||||
if (callback) {
|
|
||||||
callback();
|
|
||||||
profilingStack = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* static */
|
|
||||||
void HelperThread::ThreadMain(void* arg) {
|
|
||||||
ThisThread::SetName("JS Helper");
|
|
||||||
|
|
||||||
auto helper = static_cast<HelperThread*>(arg);
|
|
||||||
|
|
||||||
helper->ensureRegisteredWithProfiler();
|
|
||||||
helper->threadLoop();
|
|
||||||
helper->unregisterWithProfilerIfNeeded();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool JSContext::addPendingCompileError(js::CompileError** error) {
|
bool JSContext::addPendingCompileError(js::CompileError** error) {
|
||||||
auto errorPtr = make_unique<js::CompileError>();
|
auto errorPtr = make_unique<js::CompileError>();
|
||||||
if (!errorPtr) {
|
if (!errorPtr) {
|
||||||
|
@ -2781,45 +2681,6 @@ bool GlobalHelperThreadState::canStartTasks(
|
||||||
canStartWasmTier2GeneratorTask(lock);
|
canStartWasmTier2GeneratorTask(lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
HelperThread::AutoProfilerLabel::AutoProfilerLabel(
|
|
||||||
HelperThread* helperThread, const char* label,
|
|
||||||
JS::ProfilingCategoryPair categoryPair)
|
|
||||||
: profilingStack(helperThread->profilingStack) {
|
|
||||||
if (profilingStack) {
|
|
||||||
profilingStack->pushLabelFrame(label, nullptr, this, categoryPair);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
HelperThread::AutoProfilerLabel::~AutoProfilerLabel() {
|
|
||||||
if (profilingStack) {
|
|
||||||
profilingStack->pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void HelperThread::threadLoop() {
|
|
||||||
MOZ_ASSERT(CanUseExtraThreads());
|
|
||||||
|
|
||||||
AutoLockHelperThreadState lock;
|
|
||||||
|
|
||||||
while (!HelperThreadState().isTerminating(lock)) {
|
|
||||||
// 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.
|
|
||||||
|
|
||||||
HelperThreadTask* task = HelperThreadState().findHighestPriorityTask(lock);
|
|
||||||
if (!task) {
|
|
||||||
AUTO_PROFILER_LABEL("HelperThread::threadLoop::wait", IDLE);
|
|
||||||
HelperThreadState().wait(lock, GlobalHelperThreadState::PRODUCER);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
HelperThreadState().runTaskLocked(task, lock);
|
|
||||||
HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER, lock);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void JS::RunHelperThreadTask() {
|
void JS::RunHelperThreadTask() {
|
||||||
MOZ_ASSERT(CanUseExtraThreads());
|
MOZ_ASSERT(CanUseExtraThreads());
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,259 @@
|
||||||
|
/* -*- 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 "vm/InternalThreadPool.h"
|
||||||
|
|
||||||
|
#include "mozilla/TimeStamp.h"
|
||||||
|
|
||||||
|
#include "js/ProfilingCategory.h"
|
||||||
|
#include "js/ProfilingStack.h"
|
||||||
|
#include "threading/Thread.h"
|
||||||
|
#include "util/NativeStack.h"
|
||||||
|
#include "vm/HelperThreadState.h"
|
||||||
|
|
||||||
|
// These macros are identical in function to the same-named ones in
|
||||||
|
// GeckoProfiler.h, but they are defined separately because SpiderMonkey can't
|
||||||
|
// use GeckoProfiler.h.
|
||||||
|
#define PROFILER_RAII_PASTE(id, line) id##line
|
||||||
|
#define PROFILER_RAII_EXPAND(id, line) PROFILER_RAII_PASTE(id, line)
|
||||||
|
#define PROFILER_RAII PROFILER_RAII_EXPAND(raiiObject, __LINE__)
|
||||||
|
#define AUTO_PROFILER_LABEL(label, categoryPair) \
|
||||||
|
HelperThread::AutoProfilerLabel PROFILER_RAII( \
|
||||||
|
this, label, JS::ProfilingCategoryPair::categoryPair)
|
||||||
|
|
||||||
|
using namespace js;
|
||||||
|
|
||||||
|
namespace js {
|
||||||
|
|
||||||
|
class HelperThread {
|
||||||
|
Thread thread;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The profiling thread for this helper thread, which can be used to push
|
||||||
|
* and pop label frames.
|
||||||
|
* This field being non-null indicates that this thread has been registered
|
||||||
|
* and needs to be unregistered at shutdown.
|
||||||
|
*/
|
||||||
|
ProfilingStack* profilingStack = nullptr;
|
||||||
|
|
||||||
|
public:
|
||||||
|
HelperThread();
|
||||||
|
[[nodiscard]] bool init(InternalThreadPool* pool);
|
||||||
|
|
||||||
|
ThreadId threadId() { return thread.get_id(); }
|
||||||
|
|
||||||
|
void join();
|
||||||
|
|
||||||
|
static void ThreadMain(InternalThreadPool* pool, HelperThread* helper);
|
||||||
|
void threadLoop(InternalThreadPool* pool);
|
||||||
|
|
||||||
|
void ensureRegisteredWithProfiler();
|
||||||
|
void unregisterWithProfilerIfNeeded();
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct AutoProfilerLabel {
|
||||||
|
AutoProfilerLabel(HelperThread* helperThread, const char* label,
|
||||||
|
JS::ProfilingCategoryPair categoryPair);
|
||||||
|
~AutoProfilerLabel();
|
||||||
|
|
||||||
|
private:
|
||||||
|
ProfilingStack* profilingStack;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace js
|
||||||
|
|
||||||
|
InternalThreadPool* InternalThreadPool::Instance = nullptr;
|
||||||
|
|
||||||
|
/* static */ InternalThreadPool& InternalThreadPool::Get() {
|
||||||
|
MOZ_ASSERT(IsInitialized());
|
||||||
|
return *Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* static */
|
||||||
|
bool InternalThreadPool::Initialize(size_t threadCount,
|
||||||
|
AutoLockHelperThreadState& lock) {
|
||||||
|
if (IsInitialized()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto instance = MakeUnique<InternalThreadPool>();
|
||||||
|
if (!instance) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!instance->ensureThreadCount(threadCount, lock)) {
|
||||||
|
instance->shutDown(lock);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Instance = instance.release();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InternalThreadPool::ensureThreadCount(size_t threadCount,
|
||||||
|
AutoLockHelperThreadState& lock) {
|
||||||
|
MOZ_ASSERT(threads(lock).length() < threadCount);
|
||||||
|
|
||||||
|
if (!threads(lock).reserve(threadCount)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (threads(lock).length() < threadCount) {
|
||||||
|
auto thread = js::MakeUnique<HelperThread>();
|
||||||
|
if (!thread || !thread->init(this)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
threads(lock).infallibleEmplaceBack(std::move(thread));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t InternalThreadPool::threadCount(const AutoLockHelperThreadState& lock) {
|
||||||
|
return threads(lock).length();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* static */
|
||||||
|
void InternalThreadPool::ShutDown(AutoLockHelperThreadState& lock) {
|
||||||
|
MOZ_ASSERT(HelperThreadState().isTerminating(lock));
|
||||||
|
|
||||||
|
Get().shutDown(lock);
|
||||||
|
js_delete(Instance);
|
||||||
|
Instance = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InternalThreadPool::shutDown(AutoLockHelperThreadState& lock) {
|
||||||
|
MOZ_ASSERT(!terminating);
|
||||||
|
terminating = true;
|
||||||
|
|
||||||
|
notifyAll(lock);
|
||||||
|
|
||||||
|
for (auto& thread : threads(lock)) {
|
||||||
|
AutoUnlockHelperThreadState unlock(lock);
|
||||||
|
thread->join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline HelperThreadVector& InternalThreadPool::threads(
|
||||||
|
const AutoLockHelperThreadState& lock) {
|
||||||
|
return threads_.ref();
|
||||||
|
}
|
||||||
|
inline const HelperThreadVector& InternalThreadPool::threads(
|
||||||
|
const AutoLockHelperThreadState& lock) const {
|
||||||
|
return threads_.ref();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t InternalThreadPool::sizeOfIncludingThis(
|
||||||
|
mozilla::MallocSizeOf mallocSizeOf,
|
||||||
|
const AutoLockHelperThreadState& lock) const {
|
||||||
|
return sizeof(InternalThreadPool) +
|
||||||
|
threads(lock).sizeOfExcludingThis(mallocSizeOf);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InternalThreadPool::dispatchTask(const AutoLockHelperThreadState& lock) {
|
||||||
|
notifyOne(lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InternalThreadPool::notifyOne(const AutoLockHelperThreadState& lock) {
|
||||||
|
wakeup.notify_one();
|
||||||
|
}
|
||||||
|
|
||||||
|
void InternalThreadPool::notifyAll(const AutoLockHelperThreadState& lock) {
|
||||||
|
wakeup.notify_all();
|
||||||
|
}
|
||||||
|
|
||||||
|
void InternalThreadPool::wait(AutoLockHelperThreadState& lock) {
|
||||||
|
wakeup.wait_for(lock, mozilla::TimeDuration::Forever());
|
||||||
|
}
|
||||||
|
|
||||||
|
HelperThread::HelperThread()
|
||||||
|
: thread(Thread::Options().setStackSize(HELPER_STACK_SIZE)) {}
|
||||||
|
|
||||||
|
bool HelperThread::init(InternalThreadPool* pool) {
|
||||||
|
return thread.init(HelperThread::ThreadMain, pool, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HelperThread::join() { thread.join(); }
|
||||||
|
|
||||||
|
/* static */
|
||||||
|
void HelperThread::ThreadMain(InternalThreadPool* pool, HelperThread* helper) {
|
||||||
|
ThisThread::SetName("JS Helper");
|
||||||
|
|
||||||
|
helper->ensureRegisteredWithProfiler();
|
||||||
|
helper->threadLoop(pool);
|
||||||
|
helper->unregisterWithProfilerIfNeeded();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HelperThread::ensureRegisteredWithProfiler() {
|
||||||
|
if (profilingStack) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: To avoid dead locks, we should not hold on the helper thread lock
|
||||||
|
// while calling this function. This is safe because the registerThread field
|
||||||
|
// is a WriteOnceData<> type stored on the global helper tread state.
|
||||||
|
JS::RegisterThreadCallback callback = HelperThreadState().registerThread;
|
||||||
|
if (callback) {
|
||||||
|
profilingStack =
|
||||||
|
callback("JS Helper", reinterpret_cast<void*>(GetNativeStackBase()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HelperThread::unregisterWithProfilerIfNeeded() {
|
||||||
|
if (!profilingStack) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: To avoid dead locks, we should not hold on the helper thread lock
|
||||||
|
// while calling this function. This is safe because the unregisterThread
|
||||||
|
// field is a WriteOnceData<> type stored on the global helper tread state.
|
||||||
|
JS::UnregisterThreadCallback callback = HelperThreadState().unregisterThread;
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
profilingStack = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HelperThread::AutoProfilerLabel::AutoProfilerLabel(
|
||||||
|
HelperThread* helperThread, const char* label,
|
||||||
|
JS::ProfilingCategoryPair categoryPair)
|
||||||
|
: profilingStack(helperThread->profilingStack) {
|
||||||
|
if (profilingStack) {
|
||||||
|
profilingStack->pushLabelFrame(label, nullptr, this, categoryPair);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HelperThread::AutoProfilerLabel::~AutoProfilerLabel() {
|
||||||
|
if (profilingStack) {
|
||||||
|
profilingStack->pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HelperThread::threadLoop(InternalThreadPool* pool) {
|
||||||
|
MOZ_ASSERT(CanUseExtraThreads());
|
||||||
|
|
||||||
|
AutoLockHelperThreadState lock;
|
||||||
|
|
||||||
|
while (!pool->terminating) {
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
HelperThreadTask* task = HelperThreadState().findHighestPriorityTask(lock);
|
||||||
|
if (!task) {
|
||||||
|
AUTO_PROFILER_LABEL("HelperThread::threadLoop::wait", IDLE);
|
||||||
|
pool->wait(lock);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
HelperThreadState().runTaskLocked(task, lock);
|
||||||
|
HelperThreadState().notifyAll(GlobalHelperThreadState::CONSUMER, lock);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
/* -*- 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/. */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* An internal thread pool, used for the shell and when
|
||||||
|
* JS::SetHelperThreadTaskCallback not called.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef vm_InternalThreadPool_h
|
||||||
|
#define vm_InternalThreadPool_h
|
||||||
|
|
||||||
|
#include "js/AllocPolicy.h"
|
||||||
|
#include "js/UniquePtr.h"
|
||||||
|
#include "js/Vector.h"
|
||||||
|
#include "threading/ConditionVariable.h"
|
||||||
|
#include "threading/ProtectedData.h"
|
||||||
|
|
||||||
|
// We want our default stack size limit to be approximately 2MB, to be safe, but
|
||||||
|
// expect most threads to use much less. On Linux, however, requesting a stack
|
||||||
|
// of 2MB or larger risks the kernel allocating an entire 2MB huge page for it
|
||||||
|
// on first access, which we do not want. To avoid this possibility, we subtract
|
||||||
|
// 2 standard VM page sizes from our default.
|
||||||
|
static const uint32_t kDefaultHelperStackSize = 2048 * 1024 - 2 * 4096;
|
||||||
|
static const uint32_t kDefaultHelperStackQuota = 1800 * 1024;
|
||||||
|
|
||||||
|
// TSan enforces a minimum stack size that's just slightly larger than our
|
||||||
|
// default helper stack size. It does this to store blobs of TSan-specific
|
||||||
|
// data on each thread's stack. Unfortunately, that means that even though
|
||||||
|
// we'll actually receive a larger stack than we requested, the effective
|
||||||
|
// usable space of that stack is significantly less than what we expect.
|
||||||
|
// To offset TSan stealing our stack space from underneath us, double the
|
||||||
|
// default.
|
||||||
|
//
|
||||||
|
// Note that we don't need this for ASan/MOZ_ASAN because ASan doesn't
|
||||||
|
// require all the thread-specific state that TSan does.
|
||||||
|
#if defined(MOZ_TSAN)
|
||||||
|
static const uint32_t HELPER_STACK_SIZE = 2 * kDefaultHelperStackSize;
|
||||||
|
static const uint32_t HELPER_STACK_QUOTA = 2 * kDefaultHelperStackQuota;
|
||||||
|
#else
|
||||||
|
static const uint32_t HELPER_STACK_SIZE = kDefaultHelperStackSize;
|
||||||
|
static const uint32_t HELPER_STACK_QUOTA = kDefaultHelperStackQuota;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace js {
|
||||||
|
|
||||||
|
class AutoLockHelperThreadState;
|
||||||
|
class HelperThread;
|
||||||
|
|
||||||
|
using HelperThreadVector =
|
||||||
|
Vector<UniquePtr<HelperThread>, 0, SystemAllocPolicy>;
|
||||||
|
|
||||||
|
class InternalThreadPool {
|
||||||
|
public:
|
||||||
|
static bool Initialize(size_t threadCount, AutoLockHelperThreadState& lock);
|
||||||
|
static void ShutDown(AutoLockHelperThreadState& lock);
|
||||||
|
|
||||||
|
static bool IsInitialized() { return Instance; }
|
||||||
|
static InternalThreadPool& Get();
|
||||||
|
|
||||||
|
bool ensureThreadCount(size_t threadCount, AutoLockHelperThreadState& lock);
|
||||||
|
size_t threadCount(const AutoLockHelperThreadState& lock);
|
||||||
|
|
||||||
|
void dispatchTask(const AutoLockHelperThreadState& lock);
|
||||||
|
|
||||||
|
size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf,
|
||||||
|
const AutoLockHelperThreadState& lock) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void shutDown(AutoLockHelperThreadState& lock);
|
||||||
|
|
||||||
|
HelperThreadVector& threads(const AutoLockHelperThreadState& lock);
|
||||||
|
const HelperThreadVector& threads(
|
||||||
|
const AutoLockHelperThreadState& lock) const;
|
||||||
|
|
||||||
|
void notifyOne(const AutoLockHelperThreadState& lock);
|
||||||
|
void notifyAll(const AutoLockHelperThreadState& lock);
|
||||||
|
void wait(AutoLockHelperThreadState& lock);
|
||||||
|
friend class HelperThread;
|
||||||
|
|
||||||
|
static InternalThreadPool* Instance;
|
||||||
|
|
||||||
|
HelperThreadLockData<HelperThreadVector> threads_;
|
||||||
|
|
||||||
|
js::ConditionVariable wakeup;
|
||||||
|
|
||||||
|
HelperThreadLockData<bool> terminating;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace js
|
||||||
|
|
||||||
|
#endif /* vm_InternalThreadPool_h */
|
Загрузка…
Ссылка в новой задаче