tor-browser

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

GCParallelTask.cpp (7640B)


      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 "gc/GCParallelTask.h"
      8 
      9 #include "mozilla/Maybe.h"
     10 #include "mozilla/TimeStamp.h"
     11 
     12 #include "gc/GCContext.h"
     13 #include "gc/GCInternals.h"
     14 #include "gc/ParallelWork.h"
     15 #include "vm/HelperThreadState.h"
     16 #include "vm/Runtime.h"
     17 #include "vm/Time.h"
     18 
     19 using namespace js;
     20 using namespace js::gc;
     21 
     22 using mozilla::Maybe;
     23 using mozilla::TimeDuration;
     24 using mozilla::TimeStamp;
     25 
     26 js::GCParallelTask::~GCParallelTask() {
     27  // The LinkedListElement destructor will remove us from any list we are part
     28  // of without synchronization, so ensure that doesn't happen.
     29  MOZ_DIAGNOSTIC_ASSERT(!isInList());
     30 
     31  // Only most-derived classes' destructors may do the join: base class
     32  // destructors run after those for derived classes' members, so a join in a
     33  // base class can't ensure that the task is done using the members. All we
     34  // can do now is check that someone has previously stopped the task.
     35  assertIdle();
     36 }
     37 
     38 static bool ShouldMeasureTaskStartDelay() {
     39  // We use many tasks during GC so randomly sample a small fraction for the
     40  // purposes of recording telemetry.
     41  return (rand() % 100) == 0;
     42 }
     43 
     44 void js::GCParallelTask::startWithLockHeld(AutoLockHelperThreadState& lock) {
     45  MOZ_ASSERT(CanUseExtraThreads());
     46  MOZ_ASSERT(HelperThreadState().isInitialized(lock));
     47  assertIdle();
     48 
     49  maybeQueueTime_ = TimeStamp();
     50  if (ShouldMeasureTaskStartDelay()) {
     51    maybeQueueTime_ = TimeStamp::Now();
     52  }
     53 
     54  dispatchedToThreadPool = false;
     55 
     56  gc->dispatchOrQueueParallelTask(this, lock);
     57 }
     58 
     59 void js::GCParallelTask::start() {
     60  if (!CanUseExtraThreads()) {
     61    runFromMainThread();
     62    return;
     63  }
     64 
     65  AutoLockHelperThreadState lock;
     66  startWithLockHeld(lock);
     67 }
     68 
     69 void js::GCParallelTask::startOrRunIfIdle(AutoLockHelperThreadState& lock) {
     70  if (wasStarted(lock)) {
     71    return;
     72  }
     73 
     74  // Join the previous invocation of the task. This will return immediately
     75  // if the thread has never been started.
     76  joinWithLockHeld(lock);
     77 
     78  if (!CanUseExtraThreads()) {
     79    runFromMainThread(lock);
     80    return;
     81  }
     82 
     83  startWithLockHeld(lock);
     84 }
     85 
     86 void js::GCParallelTask::cancelAndWait() {
     87  MOZ_ASSERT(!isCancelled());
     88  cancel_ = true;
     89  join();
     90  cancel_ = false;
     91 }
     92 
     93 void js::GCParallelTask::join(Maybe<TimeStamp> deadline) {
     94  AutoLockHelperThreadState lock;
     95  joinWithLockHeld(lock, deadline);
     96 }
     97 
     98 void js::GCParallelTask::joinWithLockHeld(AutoLockHelperThreadState& lock,
     99                                          Maybe<TimeStamp> deadline) {
    100  // Task has not been started; there's nothing to do.
    101  if (isIdle(lock)) {
    102    return;
    103  }
    104 
    105  if (lock.hasQueuedTasks()) {
    106    // Unlock to allow task dispatch without lock held, otherwise we could wait
    107    // forever.
    108    AutoUnlockHelperThreadState unlock(lock);
    109  }
    110 
    111  if (isNotYetRunning(lock) && !dispatchedToThreadPool &&
    112      deadline.isNothing()) {
    113    // If the task was dispatched but has not yet started then cancel the task
    114    // and run it from the main thread. This stops us from blocking here when
    115    // the helper threads are busy with other tasks.
    116    MOZ_ASSERT(isInList());
    117    MOZ_ASSERT_IF(isDispatched(lock), gc->dispatchedParallelTasks != 0);
    118 
    119    remove();
    120    runFromMainThread(lock);
    121  } else {
    122    // Otherwise wait for the task to complete.
    123    joinNonIdleTask(deadline, lock);
    124  }
    125 
    126  if (isIdle(lock)) {
    127    recordDuration();
    128  }
    129 }
    130 
    131 void GCParallelTask::recordDuration() {
    132  if (phaseKind != gcstats::PhaseKind::NONE) {
    133    gc->stats().recordParallelPhase(phaseKind, duration_);
    134  }
    135 }
    136 
    137 void js::GCParallelTask::joinNonIdleTask(Maybe<TimeStamp> deadline,
    138                                         AutoLockHelperThreadState& lock) {
    139  MOZ_ASSERT(!isIdle(lock));
    140 
    141  while (!isFinished(lock)) {
    142    TimeDuration timeout = TimeDuration::Forever();
    143    if (deadline) {
    144      TimeStamp now = TimeStamp::Now();
    145      if (*deadline <= now) {
    146        break;
    147      }
    148      timeout = *deadline - now;
    149    }
    150 
    151    HelperThreadState().wait(lock, timeout);
    152  }
    153 
    154  if (isFinished(lock)) {
    155    setIdle(lock);
    156  }
    157 }
    158 
    159 void js::GCParallelTask::runFromMainThread() {
    160  AutoLockHelperThreadState lock;
    161  runFromMainThread(lock);
    162 }
    163 
    164 void js::GCParallelTask::runFromMainThread(AutoLockHelperThreadState& lock) {
    165  MOZ_ASSERT(isNotYetRunning(lock));
    166  MOZ_ASSERT(js::CurrentThreadCanAccessRuntime(gc->rt));
    167 
    168  if (lock.hasQueuedTasks()) {
    169    // Unlock to allow task dispatch without lock held, otherwise we can wait
    170    // forever.
    171    AutoUnlockHelperThreadState unlock(lock);
    172  }
    173 
    174  runTask(gc->rt->gcContext(), lock);
    175  setIdle(lock);
    176 }
    177 
    178 class MOZ_RAII AutoGCContext {
    179  JS::GCContext context;
    180 
    181 public:
    182  explicit AutoGCContext(JSRuntime* runtime) : context(runtime) {
    183    MOZ_RELEASE_ASSERT(TlsGCContext.init(),
    184                       "Failed to initialize TLS for GC context");
    185 
    186    MOZ_ASSERT(!TlsGCContext.get());
    187    TlsGCContext.set(&context);
    188  }
    189 
    190  ~AutoGCContext() {
    191    MOZ_ASSERT(TlsGCContext.get() == &context);
    192    TlsGCContext.set(nullptr);
    193  }
    194 
    195  JS::GCContext* get() { return &context; }
    196 };
    197 
    198 void js::GCParallelTask::runHelperThreadTask(AutoLockHelperThreadState& lock) {
    199  AutoGCContext gcContext(gc->rt);
    200  runTask(gcContext.get(), lock);
    201  MOZ_ASSERT(isFinished(lock));
    202 }
    203 
    204 void GCParallelTask::runTask(JS::GCContext* gcx,
    205                             AutoLockHelperThreadState& lock) {
    206  // Run the task from either the main thread or a helper thread.
    207 
    208  bool wasDispatched = isDispatched(lock);
    209  setRunning(lock);
    210 
    211  AutoSetThreadGCUse setUse(gcx, use);
    212 
    213  // The hazard analysis can't tell what the call to func_ will do but it's not
    214  // allowed to GC.
    215  JS::AutoSuppressGCAnalysis nogc;
    216 
    217  TimeStamp timeStart = TimeStamp::Now();
    218  run(lock);
    219  duration_ = TimeSince(timeStart);
    220 
    221  if (maybeQueueTime_) {
    222    TimeDuration delay = timeStart - maybeQueueTime_;
    223    gc->rt->metrics().GC_TASK_START_DELAY_US(delay);
    224  }
    225 
    226  setFinished(lock);
    227  gc->onParallelTaskEnd(wasDispatched, lock);
    228 }
    229 
    230 void GCParallelTask::onThreadPoolDispatch() {
    231  MOZ_ASSERT(!dispatchedToThreadPool);
    232  dispatchedToThreadPool = true;
    233 }
    234 
    235 void GCRuntime::dispatchOrQueueParallelTask(
    236    GCParallelTask* task, const AutoLockHelperThreadState& lock) {
    237  task->setQueued(lock);
    238  queuedParallelTasks.ref().insertBack(task, lock);
    239  maybeDispatchParallelTasks(lock);
    240 }
    241 
    242 void GCRuntime::maybeDispatchParallelTasks(
    243    const AutoLockHelperThreadState& lock) {
    244  MOZ_ASSERT(maxParallelThreads != 0);
    245  MOZ_ASSERT(dispatchedParallelTasks <= maxParallelThreads);
    246 
    247  while (dispatchedParallelTasks < maxParallelThreads &&
    248         !queuedParallelTasks.ref().isEmpty(lock)) {
    249    GCParallelTask* task = queuedParallelTasks.ref().popFirst(lock);
    250    task->setDispatched(lock);
    251    HelperThreadState().submitTask(task, lock);
    252    dispatchedParallelTasks++;
    253  }
    254 }
    255 
    256 void GCRuntime::onParallelTaskEnd(bool wasDispatched,
    257                                  const AutoLockHelperThreadState& lock) {
    258  if (wasDispatched) {
    259    MOZ_ASSERT(dispatchedParallelTasks != 0);
    260    dispatchedParallelTasks--;
    261  }
    262  maybeDispatchParallelTasks(lock);
    263 }
    264 
    265 bool js::GCParallelTask::isIdle() const {
    266  AutoLockHelperThreadState lock;
    267  return isIdle(lock);
    268 }
    269 
    270 bool js::GCParallelTask::wasStarted() const {
    271  AutoLockHelperThreadState lock;
    272  return wasStarted(lock);
    273 }
    274 
    275 /* static */
    276 size_t js::gc::GCRuntime::parallelWorkerCount() const {
    277  return std::min(helperThreadCount.ref(), MaxParallelWorkers);
    278 }