tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }