tor-browser

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

OffThreadPromiseRuntimeState.cpp (21118B)


      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/OffThreadPromiseRuntimeState.h"
      8 
      9 #include "mozilla/Assertions.h"  // MOZ_ASSERT{,_IF}
     10 
     11 #include <utility>  // mozilla::Swap
     12 
     13 #include "jspubtd.h"  // js::CurrentThreadCanAccessRuntime
     14 
     15 #include "js/AllocPolicy.h"  // js::ReportOutOfMemory
     16 #include "js/HeapAPI.h"      // JS::shadow::Zone
     17 #include "js/Promise.h"  // JS::Dispatchable, JS::DispatchToEventLoopCallback,
     18                         // JS::DelayedDispatchToEventLoopCallback
     19 #include "js/Utility.h"  // js_delete, js::AutoEnterOOMUnsafeRegion
     20 #include "threading/ProtectedData.h"  // js::UnprotectedData
     21 #include "vm/HelperThreads.h"         // js::AutoLockHelperThreadState
     22 #include "vm/JSContext.h"             // JSContext
     23 #include "vm/PromiseObject.h"         // js::PromiseObject
     24 #include "vm/Realm.h"                 // js::AutoRealm
     25 #include "vm/Runtime.h"               // JSRuntime
     26 
     27 #include "vm/Realm-inl.h"  // js::AutoRealm::AutoRealm
     28 
     29 using JS::Handle;
     30 
     31 using js::OffThreadPromiseRuntimeState;
     32 using js::OffThreadPromiseTask;
     33 
     34 OffThreadPromiseTask::OffThreadPromiseTask(JSContext* cx,
     35                                           JS::Handle<PromiseObject*> promise)
     36    : runtime_(cx->runtime()), promise_(cx, promise), cancellable_(false) {
     37  MOZ_ASSERT(runtime_ == promise_->zone()->runtimeFromMainThread());
     38  MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime_));
     39  MOZ_ASSERT(cx->runtime()->offThreadPromiseState.ref().initialized());
     40 }
     41 
     42 OffThreadPromiseTask::~OffThreadPromiseTask() {
     43  MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime_));
     44 
     45  OffThreadPromiseRuntimeState& state = runtime_->offThreadPromiseState.ref();
     46  MOZ_ASSERT(state.initialized());
     47 
     48  if (registered_) {
     49    unregister(state);
     50  }
     51 }
     52 
     53 bool OffThreadPromiseTask::init(JSContext* cx) {
     54  AutoLockHelperThreadState lock;
     55  return init(cx, lock);
     56 }
     57 
     58 bool OffThreadPromiseTask::init(JSContext* cx,
     59                                const AutoLockHelperThreadState& lock) {
     60  MOZ_ASSERT(cx->runtime() == runtime_);
     61  MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime_));
     62 
     63  OffThreadPromiseRuntimeState& state = runtime_->offThreadPromiseState.ref();
     64  MOZ_ASSERT(state.initialized());
     65 
     66  state.registerTask(cx, this);
     67  return true;
     68 }
     69 
     70 bool OffThreadPromiseTask::InitCancellable(
     71    JSContext* cx, js::UniquePtr<OffThreadPromiseTask>&& task) {
     72  AutoLockHelperThreadState lock;
     73  return InitCancellable(cx, lock, std::move(task));
     74 }
     75 
     76 bool OffThreadPromiseTask::InitCancellable(
     77    JSContext* cx, const AutoLockHelperThreadState& lock,
     78    js::UniquePtr<OffThreadPromiseTask>&& task) {
     79  MOZ_ASSERT(cx->runtime() == task->runtime_);
     80  MOZ_ASSERT(CurrentThreadCanAccessRuntime(task->runtime_));
     81  OffThreadPromiseRuntimeState& state =
     82      task->runtime_->offThreadPromiseState.ref();
     83  MOZ_ASSERT(state.initialized());
     84 
     85  if (!task->init(cx, lock)) {
     86    ReportOutOfMemory(cx);
     87    return false;
     88  }
     89 
     90  if (!state.cancellable().putNew(task.get())) {
     91    // The task will be freed because it's only owned by this function. Eagerly
     92    // unregister it now using the provided helper thread lock so that it
     93    // doesn't need to be reacquired.
     94    task->unregister(state, lock);
     95    ReportOutOfMemory(cx);
     96    return false;
     97  }
     98 
     99  // We're infallible from this point on.
    100  OffThreadPromiseTask* rawTask = task.release();
    101 
    102  // Only mark the task as cancellable once we've added it to the cancellable
    103  // set. The destructor will remove from the set if this flag is set.
    104  rawTask->cancellable_ = true;
    105 
    106  return true;
    107 }
    108 
    109 void OffThreadPromiseTask::unregister(OffThreadPromiseRuntimeState& state) {
    110  AutoLockHelperThreadState lock;
    111  unregister(state, lock);
    112 }
    113 void OffThreadPromiseTask::unregister(OffThreadPromiseRuntimeState& state,
    114                                      const AutoLockHelperThreadState& lock) {
    115  MOZ_ASSERT(registered_);
    116  state.unregisterTask(this);
    117 }
    118 
    119 void OffThreadPromiseTask::run(JSContext* cx,
    120                               MaybeShuttingDown maybeShuttingDown) {
    121  MOZ_ASSERT(cx->runtime() == runtime_);
    122  MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime_));
    123  MOZ_ASSERT(registered_);
    124 
    125  // Remove this task from numRegistered_ before calling `resolve`, so that if
    126  // `resolve` itself drains the queue reentrantly, the queue will not think
    127  // this task is yet to be queued and block waiting for it.
    128  //
    129  // The unregister method synchronizes on the helper thread lock and ensures
    130  // that we don't delete the task while the helper thread is still running.
    131  OffThreadPromiseRuntimeState& state = runtime_->offThreadPromiseState.ref();
    132  MOZ_ASSERT(state.initialized());
    133  unregister(state);
    134 
    135  if (maybeShuttingDown == JS::Dispatchable::NotShuttingDown) {
    136    // We can't leave a pending exception when returning to the caller so do
    137    // the same thing as Gecko, which is to ignore the error. This should
    138    // only happen due to OOM or interruption.
    139    AutoRealm ar(cx, promise_);
    140    if (!resolve(cx, promise_)) {
    141      cx->clearPendingException();
    142    }
    143  }
    144 
    145  js_delete(this);
    146 }
    147 
    148 void OffThreadPromiseTask::transferToRuntime() {
    149  MOZ_ASSERT(registered_);
    150 
    151  // The unregister method synchronizes on the helper thread lock and ensures
    152  // that we don't delete the task while the helper thread is still running.
    153  OffThreadPromiseRuntimeState& state = runtime_->offThreadPromiseState.ref();
    154  MOZ_ASSERT(state.initialized());
    155 
    156  // Task is now owned by the state and will be deleted on ::shutdown.
    157  state.stealFailedTask(this);
    158 }
    159 
    160 /* static */
    161 void OffThreadPromiseTask::DestroyUndispatchedTask(
    162    OffThreadPromiseTask* task, OffThreadPromiseRuntimeState& state,
    163    const AutoLockHelperThreadState& lock) {
    164  MOZ_ASSERT(CurrentThreadCanAccessRuntime(task->runtime_));
    165  MOZ_ASSERT(task->registered_);
    166  MOZ_ASSERT(task->cancellable_);
    167  // Cleanup Steps from 4. in SMDOC for Atomics.waitAsync
    168  task->prepareForCancel();
    169  // unregister with the passed lock. Necessary so that
    170  // there is no conflict in js_delete with shutdown code.
    171  task->unregister(state, lock);
    172  js_delete(task);
    173 }
    174 
    175 void OffThreadPromiseTask::dispatchResolveAndDestroy() {
    176  AutoLockHelperThreadState lock;
    177  js::UniquePtr<OffThreadPromiseTask> task(this);
    178  DispatchResolveAndDestroy(std::move(task), lock);
    179 }
    180 
    181 void OffThreadPromiseTask::dispatchResolveAndDestroy(
    182    const AutoLockHelperThreadState& lock) {
    183  js::UniquePtr<OffThreadPromiseTask> task(this);
    184  DispatchResolveAndDestroy(std::move(task), lock);
    185 }
    186 
    187 void OffThreadPromiseTask::removeFromCancellableListAndDispatch() {
    188  AutoLockHelperThreadState lock;
    189  removeFromCancellableListAndDispatch(lock);
    190 }
    191 
    192 void OffThreadPromiseTask::removeFromCancellableListAndDispatch(
    193    const AutoLockHelperThreadState& lock) {
    194  OffThreadPromiseRuntimeState& state = runtime_->offThreadPromiseState.ref();
    195  MOZ_ASSERT(state.initialized());
    196  MOZ_ASSERT(state.cancellable().has(this));
    197 
    198  MOZ_ASSERT(registered_);
    199  MOZ_ASSERT(cancellable_);
    200  cancellable_ = false;
    201  // remove this task from the runnable's cancellable list. This ends the
    202  // runtime's ownership of the the task.
    203  state.cancellable().remove(this);
    204 
    205  // Create a UniquePtr that will be passed to the embedding.
    206  js::UniquePtr<OffThreadPromiseTask> task;
    207  // move ownership of this task to the newly created pointer
    208  task.reset(this);
    209  DispatchResolveAndDestroy(std::move(task), lock);
    210 }
    211 
    212 /* static */
    213 void OffThreadPromiseTask::DispatchResolveAndDestroy(
    214    js::UniquePtr<OffThreadPromiseTask>&& task) {
    215  AutoLockHelperThreadState lock;
    216  DispatchResolveAndDestroy(std::move(task), lock);
    217 }
    218 
    219 /* static */
    220 void OffThreadPromiseTask::DispatchResolveAndDestroy(
    221    js::UniquePtr<OffThreadPromiseTask>&& task,
    222    const AutoLockHelperThreadState& lock) {
    223  OffThreadPromiseRuntimeState& state =
    224      task->runtime()->offThreadPromiseState.ref();
    225  MOZ_ASSERT(state.initialized());
    226 
    227  MOZ_ASSERT(task->registered_);
    228  MOZ_ASSERT(!task->cancellable_);
    229  // If the dispatch succeeds, then we are guaranteed that run() will be
    230  // called on an active JSContext of runtime_.
    231  {
    232    // Hazard analysis can't tell that the callback does not GC.
    233    JS::AutoSuppressGCAnalysis nogc;
    234    if (state.dispatchToEventLoop(std::move(task))) {
    235      return;
    236    }
    237  }
    238 
    239  // The DispatchToEventLoopCallback has failed to dispatch this task,
    240  // indicating that shutdown has begun. Compare the number of failed tasks that
    241  // have called dispatchResolveAndDestroy, and when they account for all of
    242  // numRegistered_, notify OffThreadPromiseRuntimeState::shutdown that it is
    243  // safe to destruct them.
    244  if (state.failed().length() == state.numRegistered_) {
    245    state.allFailed().notify_one();
    246  }
    247 }
    248 
    249 OffThreadPromiseRuntimeState::OffThreadPromiseRuntimeState()
    250    : dispatchToEventLoopCallback_(nullptr),
    251      delayedDispatchToEventLoopCallback_(nullptr),
    252      asyncTaskStartedCallback_(nullptr),
    253      asyncTaskFinishedCallback_(nullptr),
    254      dispatchToEventLoopClosure_(nullptr),
    255 #ifdef DEBUG
    256      forceQuitting_(false),
    257 #endif
    258      numRegistered_(0),
    259      internalDispatchQueueClosed_(false) {
    260 }
    261 
    262 OffThreadPromiseRuntimeState::~OffThreadPromiseRuntimeState() {
    263  MOZ_ASSERT_IF(!forceQuitting_, numRegistered_ == 0);
    264  MOZ_ASSERT_IF(!forceQuitting_, numDelayed_ == 0);
    265  MOZ_ASSERT_IF(!forceQuitting_, internalDispatchQueue_.refNoCheck().empty());
    266  MOZ_ASSERT(!initialized());
    267 }
    268 
    269 void OffThreadPromiseRuntimeState::init(
    270    JS::DispatchToEventLoopCallback dispatchCallback,
    271    JS::DelayedDispatchToEventLoopCallback delayedDispatchCallback,
    272    JS::AsyncTaskStartedCallback asyncTaskStartedCallback,
    273    JS::AsyncTaskFinishedCallback asyncTaskFinishedCallback, void* closure) {
    274  MOZ_ASSERT(!initialized());
    275 
    276  dispatchToEventLoopCallback_ = dispatchCallback;
    277  delayedDispatchToEventLoopCallback_ = delayedDispatchCallback;
    278  asyncTaskStartedCallback_ = asyncTaskStartedCallback;
    279  asyncTaskFinishedCallback_ = asyncTaskFinishedCallback;
    280  dispatchToEventLoopClosure_ = closure;
    281 
    282  MOZ_ASSERT(initialized());
    283 }
    284 
    285 bool OffThreadPromiseRuntimeState::dispatchToEventLoop(
    286    js::UniquePtr<JS::Dispatchable>&& dispatchable) {
    287  return dispatchToEventLoopCallback_(dispatchToEventLoopClosure_,
    288                                      std::move(dispatchable));
    289 }
    290 
    291 bool OffThreadPromiseRuntimeState::delayedDispatchToEventLoop(
    292    js::UniquePtr<JS::Dispatchable>&& dispatchable, uint32_t delay) {
    293  return delayedDispatchToEventLoopCallback_(dispatchToEventLoopClosure_,
    294                                             std::move(dispatchable), delay);
    295 }
    296 
    297 void OffThreadPromiseRuntimeState::registerTask(JSContext* cx,
    298                                                OffThreadPromiseTask* task) {
    299  // Track the total number of pending async tasks
    300  numRegistered_++;
    301 
    302  // Mark the task as registered
    303  task->registered_ = true;
    304 
    305  if (!asyncTaskStartedCallback_) {
    306    return;
    307  }
    308 
    309  // The embedder must not perform a GC, suppress GC analysis here.
    310  JS::AutoSuppressGCAnalysis nogc(cx);
    311  asyncTaskStartedCallback_(dispatchToEventLoopClosure_, task);
    312 }
    313 
    314 void OffThreadPromiseRuntimeState::unregisterTask(OffThreadPromiseTask* task) {
    315  // Track the total number of pending async tasks
    316  MOZ_ASSERT(numRegistered_ != 0);
    317  numRegistered_--;
    318 
    319  // Mark the task as unregistered
    320  task->registered_ = false;
    321 
    322  // If the task was cancellable, remove from our cancellable set.
    323  if (task->cancellable_) {
    324    task->cancellable_ = false;
    325    cancellable().remove(task);
    326  }
    327 
    328  if (!asyncTaskFinishedCallback_) {
    329    return;
    330  }
    331 
    332  // The embedder must not perform a GC, suppress GC analysis here.
    333  JS::AutoSuppressGCAnalysis nogc;
    334  asyncTaskFinishedCallback_(dispatchToEventLoopClosure_, task);
    335 }
    336 
    337 /* static */
    338 bool OffThreadPromiseRuntimeState::internalDispatchToEventLoop(
    339    void* closure, js::UniquePtr<JS::Dispatchable>&& d) {
    340  OffThreadPromiseRuntimeState& state =
    341      *reinterpret_cast<OffThreadPromiseRuntimeState*>(closure);
    342  MOZ_ASSERT(state.usingInternalDispatchQueue());
    343  gHelperThreadLock.assertOwnedByCurrentThread();
    344 
    345  if (state.internalDispatchQueueClosed_) {
    346    JS::Dispatchable::ReleaseFailedTask(std::move(d));
    347    return false;
    348  }
    349 
    350  state.dispatchDelayedTasks();
    351 
    352  // The JS API contract is that 'false' means shutdown, so be infallible
    353  // here (like Gecko).
    354  AutoEnterOOMUnsafeRegion noOOM;
    355  if (!state.internalDispatchQueue().pushBack(std::move(d))) {
    356    noOOM.crash("internalDispatchToEventLoop");
    357  }
    358 
    359  // Wake up internalDrain() if it is waiting for a job to finish.
    360  state.internalDispatchQueueAppended().notify_one();
    361  return true;
    362 }
    363 
    364 // This function (and all related delayedDispatch data structures), are in place
    365 // in order to make the JS Shell work as expected with delayed tasks such as
    366 // Atomics.waitAsync.
    367 bool OffThreadPromiseRuntimeState::internalDelayedDispatchToEventLoop(
    368    void* closure, js::UniquePtr<JS::Dispatchable>&& d, uint32_t delay) {
    369  OffThreadPromiseRuntimeState& state =
    370      *reinterpret_cast<OffThreadPromiseRuntimeState*>(closure);
    371  MOZ_ASSERT(state.usingInternalDispatchQueue());
    372  gHelperThreadLock.assertOwnedByCurrentThread();
    373 
    374  if (state.internalDispatchQueueClosed_) {
    375    return false;
    376  }
    377 
    378  state.dispatchDelayedTasks();
    379 
    380  // endTime is calculated synchronously from the moment we call
    381  // internalDelayedDispatchToEventLoop. The only current use-case is
    382  // Atomics.waitAsync.
    383  mozilla::TimeStamp endTime = mozilla::TimeStamp::Now() +
    384                               mozilla::TimeDuration::FromMilliseconds(delay);
    385  if (!state.internalDelayedDispatchPriorityQueue().reserveOne()) {
    386    JS::Dispatchable::ReleaseFailedTask(std::move(d));
    387    return false;
    388  }
    389 
    390  state.internalDelayedDispatchPriorityQueue().infallibleInsert(
    391      DelayedDispatchable(std::move(d), endTime));
    392 
    393  return true;
    394 }
    395 
    396 void OffThreadPromiseRuntimeState::dispatchDelayedTasks() {
    397  MOZ_ASSERT(usingInternalDispatchQueue());
    398  gHelperThreadLock.assertOwnedByCurrentThread();
    399 
    400  if (internalDispatchQueueClosed_) {
    401    return;
    402  }
    403 
    404  auto& queue = internalDelayedDispatchPriorityQueue();
    405 
    406  if (queue.empty()) {
    407    return;
    408  }
    409 
    410  mozilla::TimeStamp now = mozilla::TimeStamp::Now();
    411 
    412  while (!queue.empty() && queue.highest().endTime() <= now) {
    413    DelayedDispatchable d(std::move(queue.highest()));
    414    queue.popHighest();
    415 
    416    AutoEnterOOMUnsafeRegion noOOM;
    417    numDelayed_++;
    418    if (!internalDispatchQueue().pushBack(d.dispatchable())) {
    419      noOOM.crash("dispatchDelayedTasks");
    420    }
    421    internalDispatchQueueAppended().notify_one();
    422  }
    423 }
    424 
    425 bool OffThreadPromiseRuntimeState::usingInternalDispatchQueue() const {
    426  return dispatchToEventLoopCallback_ == internalDispatchToEventLoop;
    427 }
    428 
    429 void OffThreadPromiseRuntimeState::initInternalDispatchQueue() {
    430  init(internalDispatchToEventLoop, internalDelayedDispatchToEventLoop, nullptr,
    431       nullptr, this);
    432  MOZ_ASSERT(usingInternalDispatchQueue());
    433 }
    434 
    435 bool OffThreadPromiseRuntimeState::initialized() const {
    436  return !!dispatchToEventLoopCallback_;
    437 }
    438 
    439 void OffThreadPromiseRuntimeState::internalDrain(JSContext* cx) {
    440  MOZ_ASSERT(usingInternalDispatchQueue());
    441 
    442  for (;;) {
    443    js::UniquePtr<JS::Dispatchable> d;
    444    {
    445      AutoLockHelperThreadState lock;
    446      dispatchDelayedTasks();
    447 
    448      MOZ_ASSERT(!internalDispatchQueueClosed_);
    449      MOZ_ASSERT_IF(!internalDispatchQueue().empty(),
    450                    numRegistered_ + numDelayed_ > 0);
    451      if (internalDispatchQueue().empty() && !internalHasPending(lock)) {
    452        return;
    453      }
    454 
    455      // There are extant live dispatched OffThreadPromiseTasks.
    456      // If none are in the queue, block until one of them finishes
    457      // and enqueues a dispatchable.
    458      while (internalDispatchQueue().empty()) {
    459        internalDispatchQueueAppended().wait(lock);
    460      }
    461 
    462      d = std::move(internalDispatchQueue().front());
    463      internalDispatchQueue().popFront();
    464      if (!d->registered()) {
    465        numDelayed_--;
    466      }
    467    }
    468 
    469    // Don't call Run() with lock held to avoid deadlock.
    470    OffThreadPromiseTask::Run(cx, std::move(d),
    471                              JS::Dispatchable::NotShuttingDown);
    472  }
    473 }
    474 
    475 bool OffThreadPromiseRuntimeState::internalHasPending() {
    476  AutoLockHelperThreadState lock;
    477  return internalHasPending(lock);
    478 }
    479 
    480 bool OffThreadPromiseRuntimeState::internalHasPending(
    481    AutoLockHelperThreadState& lock) {
    482  MOZ_ASSERT(usingInternalDispatchQueue());
    483 
    484  MOZ_ASSERT(!internalDispatchQueueClosed_);
    485  MOZ_ASSERT_IF(!internalDispatchQueue().empty(),
    486                numRegistered_ + numDelayed_ > 0);
    487  return numDelayed_ > 0 || numRegistered_ > cancellable().count();
    488 }
    489 
    490 void OffThreadPromiseRuntimeState::stealFailedTask(JS::Dispatchable* task) {
    491  js::AutoEnterOOMUnsafeRegion noOOM;
    492  if (!failed().pushBack(task)) {
    493    noOOM.crash("stealFailedTask");
    494  }
    495 }
    496 
    497 void OffThreadPromiseRuntimeState::cancelTasks(
    498    js::AutoLockHelperThreadState& lock, JSContext* cx) {
    499  MOZ_ASSERT(initialized());
    500  if (!initialized()) {
    501    return;
    502  }
    503 
    504  // Cancel all undispatched tasks that are cancellable.
    505  for (auto iter = cancellable().modIter(); !iter.done(); iter.next()) {
    506    OffThreadPromiseTask* task = iter.get();
    507    MOZ_ASSERT(task->cancellable_);
    508    iter.remove();
    509 
    510    OffThreadPromiseTask::DestroyUndispatchedTask(task, *this, lock);
    511  }
    512 }
    513 
    514 void OffThreadPromiseRuntimeState::cancelTasks(JSContext* cx) {
    515  if (!initialized()) {
    516    return;
    517  }
    518 
    519  AutoLockHelperThreadState lock;
    520  cancelTasks(lock, cx);
    521 }
    522 
    523 void OffThreadPromiseRuntimeState::shutdown(JSContext* cx) {
    524  if (!initialized()) {
    525    return;
    526  }
    527 
    528  AutoLockHelperThreadState lock;
    529 
    530  // Cancel all undispatched tasks.
    531  cancelTasks(lock, cx);
    532  MOZ_ASSERT(cancellable().empty());
    533 
    534  // When the shell is using the internal event loop, we must simulate our
    535  // requirement of the embedding that, before shutdown, all successfully-
    536  // dispatched-to-event-loop tasks have been run.
    537  if (usingInternalDispatchQueue()) {
    538    DispatchableFifo dispatchQueue;
    539    {
    540      std::swap(dispatchQueue, internalDispatchQueue());
    541      MOZ_ASSERT(internalDispatchQueue().empty());
    542      internalDispatchQueueClosed_ = true;
    543    }
    544 
    545    // Don't call run() with lock held to avoid deadlock.
    546    AutoUnlockHelperThreadState unlock(lock);
    547    while (!dispatchQueue.empty()) {
    548      js::UniquePtr<JS::Dispatchable> d = std::move(dispatchQueue.front());
    549      dispatchQueue.popFront();
    550      OffThreadPromiseTask::Run(cx, std::move(d),
    551                                JS::Dispatchable::ShuttingDown);
    552    }
    553  }
    554 
    555  // An OffThreadPromiseTask may only be safely deleted on its JSContext's
    556  // thread (since it contains a PersistentRooted holding its promise), and
    557  // only after it has called DispatchResolveAndDestroy (since that is our
    558  // only indication that its owner is done writing into it).
    559  //
    560  // OffThreadPromiseTasks accepted by the DispatchToEventLoopCallback are
    561  // deleted by their 'run' methods. Only DispatchResolveAndDestroy invokes
    562  // the callback, and the point of the callback is to call 'run' on the
    563  // JSContext's thread, so the conditions above are met.
    564  //
    565  // But although the embedding's DispatchToEventLoopCallback promises to run
    566  // every task it accepts before shutdown, when shutdown does begin it starts
    567  // rejecting tasks; we cannot count on 'run' to clean those up for us.
    568  // Instead, tasks which fail to run have their ownership passed to the failed_
    569  // list; once that count covers everything in numRegisterd_, this function
    570  // itself runs only on the JSContext's thread, so we can delete them all here.
    571  while (numRegistered_ != failed().length()) {
    572    MOZ_ASSERT(failed().length() < numRegistered_);
    573    allFailed().wait(lock);
    574  }
    575 
    576  {
    577    DispatchableFifo failedQueue;
    578    {
    579      std::swap(failedQueue, failed());
    580      MOZ_ASSERT(failed().empty());
    581    }
    582 
    583    AutoUnlockHelperThreadState unlock(lock);
    584    while (!failedQueue.empty()) {
    585      js::UniquePtr<JS::Dispatchable> d = std::move(failedQueue.front());
    586      failedQueue.popFront();
    587      js_delete(d.release());
    588    }
    589  }
    590 
    591  // Everything should be empty at this point.
    592  MOZ_ASSERT(numRegistered_ == 0);
    593 
    594  // After shutdown, there should be no OffThreadPromiseTask activity in this
    595  // JSRuntime. Revert to the !initialized() state to catch bugs.
    596  dispatchToEventLoopCallback_ = nullptr;
    597  MOZ_ASSERT(!initialized());
    598 }
    599 
    600 /* static */
    601 js::PromiseObject* OffThreadPromiseTask::ExtractAndForget(
    602    OffThreadPromiseTask* task, const AutoLockHelperThreadState& lock) {
    603  OffThreadPromiseRuntimeState& state =
    604      task->runtime()->offThreadPromiseState.ref();
    605  MOZ_ASSERT(state.initialized());
    606  MOZ_ASSERT(task->registered_);
    607  js::PromiseObject* promise = task->promise_;
    608  // Call the unregister method manually with our provided lock, otherwise the
    609  // destructor will try to acquire it and fail.
    610  task->unregister(state, lock);
    611  // Now we can call the destructor.
    612  js_delete(task);
    613  return promise;
    614 }