InternalThreadPool.cpp (9905B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "vm/InternalThreadPool.h" 8 9 #include "mozilla/TimeStamp.h" 10 11 #include "js/ProfilingCategory.h" 12 #include "js/ProfilingStack.h" 13 #include "threading/Thread.h" 14 #include "util/NativeStack.h" 15 #include "vm/HelperThreadState.h" 16 #include "vm/JSContext.h" 17 18 // We want our default stack size limit to be approximately 2MB, to be safe, but 19 // expect most threads to use much less. On Linux, however, requesting a stack 20 // of 2MB or larger risks the kernel allocating an entire 2MB huge page for it 21 // on first access, which we do not want. To avoid this possibility, we subtract 22 // 2 standard VM page sizes from our default. 23 static const uint32_t kDefaultHelperStackSize = 2048 * 1024 - 2 * 4096; 24 25 // TSan enforces a minimum stack size that's just slightly larger than our 26 // default helper stack size. It does this to store blobs of TSan-specific 27 // data on each thread's stack. Unfortunately, that means that even though 28 // we'll actually receive a larger stack than we requested, the effective 29 // usable space of that stack is significantly less than what we expect. 30 // To offset TSan stealing our stack space from underneath us, double the 31 // default. 32 // 33 // Note that we don't need this for ASan/MOZ_ASAN because ASan doesn't 34 // require all the thread-specific state that TSan does. 35 #if defined(MOZ_TSAN) 36 static const uint32_t HELPER_STACK_SIZE = 2 * kDefaultHelperStackSize; 37 #else 38 static const uint32_t HELPER_STACK_SIZE = kDefaultHelperStackSize; 39 #endif 40 41 // These macros are identical in function to the same-named ones in 42 // GeckoProfiler.h, but they are defined separately because SpiderMonkey can't 43 // use GeckoProfiler.h. 44 #define PROFILER_RAII_PASTE(id, line) id##line 45 #define PROFILER_RAII_EXPAND(id, line) PROFILER_RAII_PASTE(id, line) 46 #define PROFILER_RAII PROFILER_RAII_EXPAND(raiiObject, __LINE__) 47 #define AUTO_PROFILER_LABEL(label, categoryPair) \ 48 HelperThread::AutoProfilerLabel PROFILER_RAII( \ 49 this, label, JS::ProfilingCategoryPair::categoryPair) 50 51 using namespace js; 52 53 namespace js { 54 55 class HelperThread { 56 Thread thread; 57 58 ConditionVariable wakeup; 59 60 HelperThreadLockData<HelperThreadTask*> nextTask; 61 62 /* 63 * The profiling thread for this helper thread, which can be used to push 64 * and pop label frames. 65 * This field being non-null indicates that this thread has been registered 66 * and needs to be unregistered at shutdown. 67 */ 68 ProfilingStack* profilingStack = nullptr; 69 70 public: 71 const uint32_t id; 72 73 explicit HelperThread(uint32_t id); 74 [[nodiscard]] bool init(InternalThreadPool* pool); 75 76 ThreadId threadId() { return thread.get_id(); } 77 78 void join(); 79 80 static void ThreadMain(InternalThreadPool* pool, HelperThread* helper); 81 void threadLoop(InternalThreadPool* pool); 82 83 void ensureRegisteredWithProfiler(); 84 void unregisterWithProfilerIfNeeded(); 85 86 void dispatchTask(HelperThreadTask* task); 87 void notify(); 88 89 private: 90 struct AutoProfilerLabel { 91 AutoProfilerLabel(HelperThread* helperThread, const char* label, 92 JS::ProfilingCategoryPair categoryPair); 93 ~AutoProfilerLabel(); 94 95 private: 96 ProfilingStack* profilingStack; 97 }; 98 }; 99 100 } // namespace js 101 102 InternalThreadPool* InternalThreadPool::Instance = nullptr; 103 104 /* static */ InternalThreadPool& InternalThreadPool::Get() { 105 MOZ_ASSERT(IsInitialized()); 106 return *Instance; 107 } 108 109 /* static */ 110 bool InternalThreadPool::Initialize(size_t threadCount, 111 AutoLockHelperThreadState& lock) { 112 if (IsInitialized()) { 113 return true; 114 } 115 116 auto instance = MakeUnique<InternalThreadPool>(); 117 if (!instance) { 118 return false; 119 } 120 121 if (!instance->ensureThreadCount(threadCount, lock)) { 122 instance->shutDown(lock); 123 return false; 124 } 125 126 Instance = instance.release(); 127 HelperThreadState().setDispatchTaskCallback(DispatchTask, threadCount, 128 HELPER_STACK_SIZE, lock); 129 return true; 130 } 131 132 bool InternalThreadPool::ensureThreadCount(size_t threadCount, 133 AutoLockHelperThreadState& lock) { 134 // Ensure space in freeThreadSet. 135 threadCount = std::min(threadCount, sizeof(uint32_t) * CHAR_BIT); 136 137 MOZ_ASSERT(threads(lock).length() <= threadCount); 138 139 if (!threads(lock).reserve(threadCount)) { 140 return false; 141 } 142 143 while (threads(lock).length() < threadCount) { 144 uint32_t id = threads(lock).length(); 145 146 auto thread = js::MakeUnique<HelperThread>(id); 147 if (!thread || !thread->init(this)) { 148 return false; 149 } 150 151 threads(lock).infallibleEmplaceBack(std::move(thread)); 152 153 setThreadFree(id); 154 } 155 156 for (size_t i = 0; i < threads(lock).length(); i++) { 157 MOZ_ASSERT(threads(lock)[i]->id == i); 158 } 159 160 return true; 161 } 162 163 size_t InternalThreadPool::threadCount(const AutoLockHelperThreadState& lock) { 164 return threads(lock).length(); 165 } 166 167 /* static */ 168 void InternalThreadPool::ShutDown(AutoLockHelperThreadState& lock) { 169 MOZ_ASSERT(HelperThreadState().isTerminating(lock)); 170 171 Get().shutDown(lock); 172 js_delete(Instance); 173 Instance = nullptr; 174 } 175 176 void InternalThreadPool::shutDown(AutoLockHelperThreadState& lock) { 177 MOZ_ASSERT(!terminating); 178 terminating = true; 179 180 for (auto& thread : threads(lock)) { 181 thread->notify(); 182 } 183 184 for (auto& thread : threads(lock)) { 185 AutoUnlockHelperThreadState unlock(lock); 186 thread->join(); 187 } 188 } 189 190 inline HelperThreadVector& InternalThreadPool::threads( 191 const AutoLockHelperThreadState& lock) { 192 return threads_.ref(); 193 } 194 inline const HelperThreadVector& InternalThreadPool::threads( 195 const AutoLockHelperThreadState& lock) const { 196 return threads_.ref(); 197 } 198 199 size_t InternalThreadPool::sizeOfIncludingThis( 200 mozilla::MallocSizeOf mallocSizeOf, 201 const AutoLockHelperThreadState& lock) const { 202 return sizeof(InternalThreadPool) + 203 threads(lock).sizeOfExcludingThis(mallocSizeOf); 204 } 205 206 /* static */ 207 void InternalThreadPool::DispatchTask(HelperThreadTask* task) { 208 Get().dispatchOrQueueTask(task); 209 } 210 211 void InternalThreadPool::dispatchOrQueueTask(HelperThreadTask* task) { 212 // This could now use a separate mutex like TaskController, but continues to 213 // use the helper thread state lock for convenience. 214 AutoLockHelperThreadState lock; 215 MOZ_ASSERT(!terminating); 216 MOZ_ASSERT(freeThreadSet != 0); 217 218 uint32_t id = mozilla::CountTrailingZeroes32(freeThreadSet); 219 clearThreadFree(id); 220 221 HelperThread* thread = threads_.ref()[id].get(); 222 thread->dispatchTask(task); 223 } 224 225 void InternalThreadPool::setThreadFree(uint32_t threadId) { 226 uint32_t idMask = 1 << threadId; 227 MOZ_ASSERT((freeThreadSet & idMask) == 0); 228 freeThreadSet |= idMask; 229 } 230 231 void InternalThreadPool::clearThreadFree(uint32_t threadId) { 232 uint32_t idMask = 1 << threadId; 233 MOZ_ASSERT((freeThreadSet & idMask) != 0); 234 freeThreadSet &= ~idMask; 235 } 236 237 HelperThread::HelperThread(uint32_t id) 238 : thread(Thread::Options().setStackSize(HELPER_STACK_SIZE)), id(id) {} 239 240 bool HelperThread::init(InternalThreadPool* pool) { 241 return thread.init(HelperThread::ThreadMain, pool, this); 242 } 243 244 void HelperThread::join() { thread.join(); } 245 246 /* static */ 247 void HelperThread::ThreadMain(InternalThreadPool* pool, HelperThread* helper) { 248 ThisThread::SetName("JS Helper"); 249 250 helper->ensureRegisteredWithProfiler(); 251 helper->threadLoop(pool); 252 helper->unregisterWithProfilerIfNeeded(); 253 } 254 255 void HelperThread::ensureRegisteredWithProfiler() { 256 if (profilingStack) { 257 return; 258 } 259 260 // Note: To avoid dead locks, we should not hold on the helper thread lock 261 // while calling this function. This is safe because the registerThread field 262 // is a WriteOnceData<> type stored on the global helper tread state. 263 JS::RegisterThreadCallback callback = HelperThreadState().registerThread; 264 if (callback) { 265 profilingStack = 266 callback("JS Helper", reinterpret_cast<void*>(GetNativeStackBase())); 267 } 268 } 269 270 void HelperThread::unregisterWithProfilerIfNeeded() { 271 if (!profilingStack) { 272 return; 273 } 274 275 // Note: To avoid dead locks, we should not hold on the helper thread lock 276 // while calling this function. This is safe because the unregisterThread 277 // field is a WriteOnceData<> type stored on the global helper tread state. 278 JS::UnregisterThreadCallback callback = HelperThreadState().unregisterThread; 279 if (callback) { 280 callback(); 281 profilingStack = nullptr; 282 } 283 } 284 285 HelperThread::AutoProfilerLabel::AutoProfilerLabel( 286 HelperThread* helperThread, const char* label, 287 JS::ProfilingCategoryPair categoryPair) 288 : profilingStack(helperThread->profilingStack) { 289 if (profilingStack) { 290 profilingStack->pushLabelFrame(label, nullptr, this, categoryPair); 291 } 292 } 293 294 HelperThread::AutoProfilerLabel::~AutoProfilerLabel() { 295 if (profilingStack) { 296 profilingStack->pop(); 297 } 298 } 299 300 void HelperThread::dispatchTask(HelperThreadTask* task) { 301 MOZ_ASSERT(!nextTask); 302 nextTask = task; 303 notify(); 304 } 305 306 void HelperThread::notify() { wakeup.notify_one(); } 307 308 void HelperThread::threadLoop(InternalThreadPool* pool) { 309 MOZ_ASSERT(CanUseExtraThreads()); 310 311 AutoLockHelperThreadState lock; 312 313 while (!pool->terminating) { 314 if (!nextTask) { 315 AUTO_PROFILER_LABEL("HelperThread::threadLoop::wait", IDLE); 316 wakeup.wait(lock); 317 continue; 318 } 319 320 // JS::RunHelperThreadTask calls runOneTask and then dispatch. Here we split 321 // this up so we can mark the current thread as free in between and allow 322 // dispatch to pick this thread for the next task. 323 324 HelperThreadState().runOneTask(nextTask, lock); 325 326 nextTask = nullptr; 327 pool->setThreadFree(id); 328 329 HelperThreadState().dispatch(lock); 330 AutoUnlockHelperThreadState unlock(lock); 331 } 332 }