Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  OffThreadPromiseRuntimeState.cpp

  Sprache: C
 

/* 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/OffThreadPromiseRuntimeState.h"

#include "mozilla/Assertions.h"  // MOZ_ASSERT{,_IF}

#include <utility>  // mozilla::Swap

#include "jspubtd.h"  // js::CurrentThreadCanAccessRuntime

#include "js/AllocPolicy.h"  // js::ReportOutOfMemory
#include "js/HeapAPI.h"      // JS::shadow::Zone
#include "js/Promise.h"  // JS::Dispatchable, JS::DispatchToEventLoopCallback,
                         // JS::DelayedDispatchToEventLoopCallback
#include "js/Utility.h"  // js_delete, js::AutoEnterOOMUnsafeRegion
#include "threading/ProtectedData.h"  // js::UnprotectedData
#include "vm/HelperThreads.h"         // js::AutoLockHelperThreadState
#include "vm/JSContext.h"             // JSContext
#include "vm/PromiseObject.h"         // js::PromiseObject
#include "vm/Realm.h"                 // js::AutoRealm
#include "vm/Runtime.h"               // JSRuntime

#include "vm/Realm-inl.h"  // js::AutoRealm::AutoRealm

using JS::Handle;

using js::OffThreadPromiseRuntimeState;
using js::OffThreadPromiseTask;

OffThreadPromiseTask::OffThreadPromiseTask(JSContext* cx,
                                           JS::Handle<PromiseObject*> promise)
    : runtime_(cx->runtime()), promise_(cx, promise), cancellable_(false) {
  MOZ_ASSERT(runtime_ == promise_->zone()->runtimeFromMainThread());
  MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime_));
  MOZ_ASSERT(cx->runtime()->offThreadPromiseState.ref().initialized());
}

OffThreadPromiseTask::~OffThreadPromiseTask() {
  MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime_));

  OffThreadPromiseRuntimeState& state = runtime_->offThreadPromiseState.ref();
  MOZ_ASSERT(state.initialized());

  if (registered_) {
    unregister(state);
  }
}

bool OffThreadPromiseTask::init(JSContext* cx) {
  AutoLockHelperThreadState lock;
  return init(cx, lock);
}

bool OffThreadPromiseTask::init(JSContext* cx,
                                const AutoLockHelperThreadState& lock) {
  MOZ_ASSERT(cx->runtime() == runtime_);
  MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime_));

  OffThreadPromiseRuntimeState& state = runtime_->offThreadPromiseState.ref();
  MOZ_ASSERT(state.initialized());

  state.registerTask(cx, this);
  return true;
}

bool OffThreadPromiseTask::InitCancellable(
    JSContext* cx, js::UniquePtr<OffThreadPromiseTask>&& task) {
  AutoLockHelperThreadState lock;
  return InitCancellable(cx, lock, std::move(task));
}

bool OffThreadPromiseTask::InitCancellable(
    JSContext* cx, const AutoLockHelperThreadState& lock,
    js::UniquePtr<OffThreadPromiseTask>&& task) {
  MOZ_ASSERT(cx->runtime() == task->runtime_);
  MOZ_ASSERT(CurrentThreadCanAccessRuntime(task->runtime_));
  OffThreadPromiseRuntimeState& state =
      task->runtime_->offThreadPromiseState.ref();
  MOZ_ASSERT(state.initialized());

  if (!task->init(cx, lock)) {
    ReportOutOfMemory(cx);
    return false;
  }

  if (!state.cancellable().putNew(task.get())) {
    // The task will be freed because it's only owned by this function. Eagerly
    // unregister it now using the provided helper thread lock so that it
    // doesn't need to be reacquired.
    task->unregister(state, lock);
    ReportOutOfMemory(cx);
    return false;
  }

  // We're infallible from this point on.
  OffThreadPromiseTask* rawTask = task.release();

  // Only mark the task as cancellable once we've added it to the cancellable
  // set. The destructor will remove from the set if this flag is set.
  rawTask->cancellable_ = true;

  return true;
}

void OffThreadPromiseTask::unregister(OffThreadPromiseRuntimeState& state) {
  AutoLockHelperThreadState lock;
  unregister(state, lock);
}
void OffThreadPromiseTask::unregister(OffThreadPromiseRuntimeState& state,
                                      const AutoLockHelperThreadState& lock) {
  MOZ_ASSERT(registered_);
  state.unregisterTask(this);
}

void OffThreadPromiseTask::run(JSContext* cx,
                               MaybeShuttingDown maybeShuttingDown) {
  MOZ_ASSERT(cx->runtime() == runtime_);
  MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime_));
  MOZ_ASSERT(registered_);

  // Remove this task from numRegistered_ before calling `resolve`, so that if
  // `resolve` itself drains the queue reentrantly, the queue will not think
  // this task is yet to be queued and block waiting for it.
  //
  // The unregister method synchronizes on the helper thread lock and ensures
  // that we don't delete the task while the helper thread is still running.
  OffThreadPromiseRuntimeState& state = runtime_->offThreadPromiseState.ref();
  MOZ_ASSERT(state.initialized());
  unregister(state);

  if (maybeShuttingDown == JS::Dispatchable::NotShuttingDown) {
    // We can't leave a pending exception when returning to the caller so do
    // the same thing as Gecko, which is to ignore the error. This should
    // only happen due to OOM or interruption.
    AutoRealm ar(cx, promise_);
    if (!resolve(cx, promise_)) {
      cx->clearPendingException();
    }
  }

  js_delete(this);
}

void OffThreadPromiseTask::transferToRuntime() {
  MOZ_ASSERT(registered_);

  // The unregister method synchronizes on the helper thread lock and ensures
  // that we don't delete the task while the helper thread is still running.
  OffThreadPromiseRuntimeState& state = runtime_->offThreadPromiseState.ref();
  MOZ_ASSERT(state.initialized());

  // Task is now owned by the state and will be deleted on ::shutdown.
  state.stealFailedTask(this);
}

/* static */
void OffThreadPromiseTask::DestroyUndispatchedTask(
    OffThreadPromiseTask* task, OffThreadPromiseRuntimeState& state,
    const AutoLockHelperThreadState& lock) {
  MOZ_ASSERT(CurrentThreadCanAccessRuntime(task->runtime_));
  MOZ_ASSERT(task->registered_);
  MOZ_ASSERT(task->cancellable_);
  // Cleanup Steps from 4. in SMDOC for Atomics.waitAsync
  task->prepareForCancel();
  // unregister with the passed lock. Necessary so that
  // there is no conflict in js_delete with shutdown code.
  task->unregister(state, lock);
  js_delete(task);
}

void OffThreadPromiseTask::dispatchResolveAndDestroy() {
  AutoLockHelperThreadState lock;
  js::UniquePtr<OffThreadPromiseTask> task(this);
  DispatchResolveAndDestroy(std::move(task), lock);
}

void OffThreadPromiseTask::dispatchResolveAndDestroy(
    const AutoLockHelperThreadState& lock) {
  js::UniquePtr<OffThreadPromiseTask> task(this);
  DispatchResolveAndDestroy(std::move(task), lock);
}

void OffThreadPromiseTask::removeFromCancellableListAndDispatch() {
  AutoLockHelperThreadState lock;
  removeFromCancellableListAndDispatch(lock);
}

void OffThreadPromiseTask::removeFromCancellableListAndDispatch(
    const AutoLockHelperThreadState& lock) {
  OffThreadPromiseRuntimeState& state = runtime_->offThreadPromiseState.ref();
  MOZ_ASSERT(state.initialized());
  MOZ_ASSERT(state.cancellable().has(this));

  MOZ_ASSERT(registered_);
  MOZ_ASSERT(cancellable_);
  cancellable_ = false;
  // remove this task from the runnable's cancellable list. This ends the
  // runtime's ownership of the the task.
  state.cancellable().remove(this);

  // Create a UniquePtr that will be passed to the embedding.
  js::UniquePtr<OffThreadPromiseTask> task;
  // move ownership of this task to the newly created pointer
  task.reset(this);
  DispatchResolveAndDestroy(std::move(task), lock);
}

/* static */
void OffThreadPromiseTask::DispatchResolveAndDestroy(
    js::UniquePtr<OffThreadPromiseTask>&& task) {
  AutoLockHelperThreadState lock;
  DispatchResolveAndDestroy(std::move(task), lock);
}

/* static */
void OffThreadPromiseTask::DispatchResolveAndDestroy(
    js::UniquePtr<OffThreadPromiseTask>&& task,
    const AutoLockHelperThreadState& lock) {
  OffThreadPromiseRuntimeState& state =
      task->runtime()->offThreadPromiseState.ref();
  MOZ_ASSERT(state.initialized());

  MOZ_ASSERT(task->registered_);
  MOZ_ASSERT(!task->cancellable_);
  // If the dispatch succeeds, then we are guaranteed that run() will be
  // called on an active JSContext of runtime_.
  {
    // Hazard analysis can't tell that the callback does not GC.
    JS::AutoSuppressGCAnalysis nogc;
    if (state.dispatchToEventLoop(std::move(task))) {
      return;
    }
  }

  // The DispatchToEventLoopCallback has failed to dispatch this task,
  // indicating that shutdown has begun. Compare the number of failed tasks that
  // have called dispatchResolveAndDestroy, and when they account for all of
  // numRegistered_, notify OffThreadPromiseRuntimeState::shutdown that it is
  // safe to destruct them.
  if (state.failed().length() == state.numRegistered_) {
    state.allFailed().notify_one();
  }
}

OffThreadPromiseRuntimeState::OffThreadPromiseRuntimeState()
    : dispatchToEventLoopCallback_(nullptr),
      delayedDispatchToEventLoopCallback_(nullptr),
      asyncTaskStartedCallback_(nullptr),
      asyncTaskFinishedCallback_(nullptr),
      dispatchToEventLoopClosure_(nullptr),
#ifdef DEBUG
      forceQuitting_(false),
#endif
      numRegistered_(0),
      internalDispatchQueueClosed_(false) {
}

OffThreadPromiseRuntimeState::~OffThreadPromiseRuntimeState() {
  MOZ_ASSERT_IF(!forceQuitting_, numRegistered_ == 0);
  MOZ_ASSERT_IF(!forceQuitting_, numDelayed_ == 0);
  MOZ_ASSERT_IF(!forceQuitting_, internalDispatchQueue_.refNoCheck().empty());
  MOZ_ASSERT(!initialized());
}

void OffThreadPromiseRuntimeState::init(
    JS::DispatchToEventLoopCallback dispatchCallback,
    JS::DelayedDispatchToEventLoopCallback delayedDispatchCallback,
    JS::AsyncTaskStartedCallback asyncTaskStartedCallback,
    JS::AsyncTaskFinishedCallback asyncTaskFinishedCallback, void* closure) {
  MOZ_ASSERT(!initialized());

  dispatchToEventLoopCallback_ = dispatchCallback;
  delayedDispatchToEventLoopCallback_ = delayedDispatchCallback;
  asyncTaskStartedCallback_ = asyncTaskStartedCallback;
  asyncTaskFinishedCallback_ = asyncTaskFinishedCallback;
  dispatchToEventLoopClosure_ = closure;

  MOZ_ASSERT(initialized());
}

bool OffThreadPromiseRuntimeState::dispatchToEventLoop(
    js::UniquePtr<JS::Dispatchable>&& dispatchable) {
  return dispatchToEventLoopCallback_(dispatchToEventLoopClosure_,
                                      std::move(dispatchable));
}

bool OffThreadPromiseRuntimeState::delayedDispatchToEventLoop(
    js::UniquePtr<JS::Dispatchable>&& dispatchable, uint32_t delay) {
  return delayedDispatchToEventLoopCallback_(dispatchToEventLoopClosure_,
                                             std::move(dispatchable), delay);
}

void OffThreadPromiseRuntimeState::registerTask(JSContext* cx,
                                                OffThreadPromiseTask* task) {
  // Track the total number of pending async tasks
  numRegistered_++;

  // Mark the task as registered
  task->registered_ = true;

  if (!asyncTaskStartedCallback_) {
    return;
  }

  // The embedder must not perform a GC, suppress GC analysis here.
  JS::AutoSuppressGCAnalysis nogc(cx);
  asyncTaskStartedCallback_(dispatchToEventLoopClosure_, task);
}

void OffThreadPromiseRuntimeState::unregisterTask(OffThreadPromiseTask* task) {
  // Track the total number of pending async tasks
  MOZ_ASSERT(numRegistered_ != 0);
  numRegistered_--;

  // Mark the task as unregistered
  task->registered_ = false;

  // If the task was cancellable, remove from our cancellable set.
  if (task->cancellable_) {
    task->cancellable_ = false;
    cancellable().remove(task);
  }

  if (!asyncTaskFinishedCallback_) {
    return;
  }

  // The embedder must not perform a GC, suppress GC analysis here.
  JS::AutoSuppressGCAnalysis nogc;
  asyncTaskFinishedCallback_(dispatchToEventLoopClosure_, task);
}

/* static */
bool OffThreadPromiseRuntimeState::internalDispatchToEventLoop(
    void* closure, js::UniquePtr<JS::Dispatchable>&& d) {
  OffThreadPromiseRuntimeState& state =
      *reinterpret_cast<OffThreadPromiseRuntimeState*>(closure);
  MOZ_ASSERT(state.usingInternalDispatchQueue());
  gHelperThreadLock.assertOwnedByCurrentThread();

  if (state.internalDispatchQueueClosed_) {
    JS::Dispatchable::ReleaseFailedTask(std::move(d));
    return false;
  }

  state.dispatchDelayedTasks();

  // The JS API contract is that 'false' means shutdown, so be infallible
  // here (like Gecko).
  AutoEnterOOMUnsafeRegion noOOM;
  if (!state.internalDispatchQueue().pushBack(std::move(d))) {
    noOOM.crash("internalDispatchToEventLoop");
  }

  // Wake up internalDrain() if it is waiting for a job to finish.
  state.internalDispatchQueueAppended().notify_one();
  return true;
}

// This function (and all related delayedDispatch data structures), are in place
// in order to make the JS Shell work as expected with delayed tasks such as
// Atomics.waitAsync.
bool OffThreadPromiseRuntimeState::internalDelayedDispatchToEventLoop(
    void* closure, js::UniquePtr<JS::Dispatchable>&& d, uint32_t delay) {
  OffThreadPromiseRuntimeState& state =
      *reinterpret_cast<OffThreadPromiseRuntimeState*>(closure);
  MOZ_ASSERT(state.usingInternalDispatchQueue());
  gHelperThreadLock.assertOwnedByCurrentThread();

  if (state.internalDispatchQueueClosed_) {
    return false;
  }

  state.dispatchDelayedTasks();

  // endTime is calculated synchronously from the moment we call
  // internalDelayedDispatchToEventLoop. The only current use-case is
  // Atomics.waitAsync.
  mozilla::TimeStamp endTime = mozilla::TimeStamp::Now() +
                               mozilla::TimeDuration::FromMilliseconds(delay);
  if (!state.internalDelayedDispatchPriorityQueue().reserveOne()) {
    JS::Dispatchable::ReleaseFailedTask(std::move(d));
    return false;
  }

  state.internalDelayedDispatchPriorityQueue().infallibleInsert(
      DelayedDispatchable(std::move(d), endTime));

  return true;
}

void OffThreadPromiseRuntimeState::dispatchDelayedTasks() {
  MOZ_ASSERT(usingInternalDispatchQueue());
  gHelperThreadLock.assertOwnedByCurrentThread();

  if (internalDispatchQueueClosed_) {
    return;
  }

  auto& queue = internalDelayedDispatchPriorityQueue();

  if (queue.empty()) {
    return;
  }

  mozilla::TimeStamp now = mozilla::TimeStamp::Now();

  while (!queue.empty() && queue.highest().endTime() <= now) {
    DelayedDispatchable d(std::move(queue.highest()));
    queue.popHighest();

    AutoEnterOOMUnsafeRegion noOOM;
    numDelayed_++;
    if (!internalDispatchQueue().pushBack(d.dispatchable())) {
      noOOM.crash("dispatchDelayedTasks");
    }
    internalDispatchQueueAppended().notify_one();
  }
}

bool OffThreadPromiseRuntimeState::usingInternalDispatchQueue() const {
  return dispatchToEventLoopCallback_ == internalDispatchToEventLoop;
}

void OffThreadPromiseRuntimeState::initInternalDispatchQueue() {
  init(internalDispatchToEventLoop, internalDelayedDispatchToEventLoop, nullptr,
       nullptr, this);
  MOZ_ASSERT(usingInternalDispatchQueue());
}

bool OffThreadPromiseRuntimeState::initialized() const {
  return !!dispatchToEventLoopCallback_;
}

void OffThreadPromiseRuntimeState::internalDrain(JSContext* cx) {
  MOZ_ASSERT(usingInternalDispatchQueue());

  for (;;) {
    js::UniquePtr<JS::Dispatchable> d;
    {
      AutoLockHelperThreadState lock;
      dispatchDelayedTasks();

      MOZ_ASSERT(!internalDispatchQueueClosed_);
      MOZ_ASSERT_IF(!internalDispatchQueue().empty(),
                    numRegistered_ + numDelayed_ > 0);
      if (internalDispatchQueue().empty() && !internalHasPending(lock)) {
        return;
      }

      // There are extant live dispatched OffThreadPromiseTasks.
      // If none are in the queue, block until one of them finishes
      // and enqueues a dispatchable.
      while (internalDispatchQueue().empty()) {
        internalDispatchQueueAppended().wait(lock);
      }

      d = std::move(internalDispatchQueue().front());
      internalDispatchQueue().popFront();
      if (!d->registered()) {
        numDelayed_--;
      }
    }

    // Don't call Run() with lock held to avoid deadlock.
    OffThreadPromiseTask::Run(cx, std::move(d),
                              JS::Dispatchable::NotShuttingDown);
  }
}

bool OffThreadPromiseRuntimeState::internalHasPending() {
  AutoLockHelperThreadState lock;
  return internalHasPending(lock);
}

bool OffThreadPromiseRuntimeState::internalHasPending(
    AutoLockHelperThreadState& lock) {
  MOZ_ASSERT(usingInternalDispatchQueue());

  MOZ_ASSERT(!internalDispatchQueueClosed_);
  MOZ_ASSERT_IF(!internalDispatchQueue().empty(),
                numRegistered_ + numDelayed_ > 0);
  return numDelayed_ > 0 || numRegistered_ > cancellable().count();
}

void OffThreadPromiseRuntimeState::stealFailedTask(JS::Dispatchable* task) {
  js::AutoEnterOOMUnsafeRegion noOOM;
  if (!failed().pushBack(task)) {
    noOOM.crash("stealFailedTask");
  }
}

void OffThreadPromiseRuntimeState::cancelTasks(
    js::AutoLockHelperThreadState& lock, JSContext* cx) {
  MOZ_ASSERT(initialized());
  if (!initialized()) {
    return;
  }

  // Cancel all undispatched tasks that are cancellable.
  for (auto iter = cancellable().modIter(); !iter.done(); iter.next()) {
    OffThreadPromiseTask* task = iter.get();
    MOZ_ASSERT(task->cancellable_);
    iter.remove();

    OffThreadPromiseTask::DestroyUndispatchedTask(task, *this, lock);
  }
}

void OffThreadPromiseRuntimeState::cancelTasks(JSContext* cx) {
  if (!initialized()) {
    return;
  }

  AutoLockHelperThreadState lock;
  cancelTasks(lock, cx);
}

void OffThreadPromiseRuntimeState::shutdown(JSContext* cx) {
  if (!initialized()) {
    return;
  }

  AutoLockHelperThreadState lock;

  // Cancel all undispatched tasks.
  cancelTasks(lock, cx);
  MOZ_ASSERT(cancellable().empty());

  // When the shell is using the internal event loop, we must simulate our
  // requirement of the embedding that, before shutdown, all successfully-
  // dispatched-to-event-loop tasks have been run.
  if (usingInternalDispatchQueue()) {
    DispatchableFifo dispatchQueue;
    {
      std::swap(dispatchQueue, internalDispatchQueue());
      MOZ_ASSERT(internalDispatchQueue().empty());
      internalDispatchQueueClosed_ = true;
    }

    // Don't call run() with lock held to avoid deadlock.
    AutoUnlockHelperThreadState unlock(lock);
    while (!dispatchQueue.empty()) {
      js::UniquePtr<JS::Dispatchable> d = std::move(dispatchQueue.front());
      dispatchQueue.popFront();
      OffThreadPromiseTask::Run(cx, std::move(d),
                                JS::Dispatchable::ShuttingDown);
    }
  }

  // An OffThreadPromiseTask may only be safely deleted on its JSContext's
  // thread (since it contains a PersistentRooted holding its promise), and
  // only after it has called DispatchResolveAndDestroy (since that is our
  // only indication that its owner is done writing into it).
  //
  // OffThreadPromiseTasks accepted by the DispatchToEventLoopCallback are
  // deleted by their 'run' methods. Only DispatchResolveAndDestroy invokes
  // the callback, and the point of the callback is to call 'run' on the
  // JSContext's thread, so the conditions above are met.
  //
  // But although the embedding's DispatchToEventLoopCallback promises to run
  // every task it accepts before shutdown, when shutdown does begin it starts
  // rejecting tasks; we cannot count on 'run' to clean those up for us.
  // Instead, tasks which fail to run have their ownership passed to the failed_
  // list; once that count covers everything in numRegisterd_, this function
  // itself runs only on the JSContext's thread, so we can delete them all here.
  while (numRegistered_ != failed().length()) {
    MOZ_ASSERT(failed().length() < numRegistered_);
    allFailed().wait(lock);
  }

  {
    DispatchableFifo failedQueue;
    {
      std::swap(failedQueue, failed());
      MOZ_ASSERT(failed().empty());
    }

    AutoUnlockHelperThreadState unlock(lock);
    while (!failedQueue.empty()) {
      js::UniquePtr<JS::Dispatchable> d = std::move(failedQueue.front());
      failedQueue.popFront();
      js_delete(d.release());
    }
  }

  // Everything should be empty at this point.
  MOZ_ASSERT(numRegistered_ == 0);

  // After shutdown, there should be no OffThreadPromiseTask activity in this
  // JSRuntime. Revert to the !initialized() state to catch bugs.
  dispatchToEventLoopCallback_ = nullptr;
  MOZ_ASSERT(!initialized());
}

/* static */
js::PromiseObject* OffThreadPromiseTask::ExtractAndForget(
    OffThreadPromiseTask* task, const AutoLockHelperThreadState& lock) {
  OffThreadPromiseRuntimeState& state =
      task->runtime()->offThreadPromiseState.ref();
  MOZ_ASSERT(state.initialized());
  MOZ_ASSERT(task->registered_);
  js::PromiseObject* promise = task->promise_;
  // Call the unregister method manually with our provided lock, otherwise the
  // destructor will try to acquire it and fail.
  task->unregister(state, lock);
  // Now we can call the destructor.
  js_delete(task);
  return promise;
}

Messung V0.5 in Prozent
C=90 H=94 G=91

¤ Dauer der Verarbeitung: 0.8 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=141584
#Domains=752002