tor-browser

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

OffThreadPromiseRuntimeState.h (17706B)


      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 #ifndef vm_OffThreadPromiseRuntimeState_h
      8 #define vm_OffThreadPromiseRuntimeState_h
      9 
     10 #include <stddef.h>  // size_t
     11 
     12 #include "jstypes.h"  // JS_PUBLIC_API
     13 
     14 #include "ds/Fifo.h"           // js::Fifo
     15 #include "ds/PriorityQueue.h"  // js::PriorityQueue
     16 #include "js/AllocPolicy.h"    // js::SystemAllocPolicy
     17 #include "js/HashTable.h"      // js::DefaultHasher, js::HashSet
     18 #include "js/Promise.h"  // JS::Dispatchable, JS::Dispatchable::MaybeShuttingDown,
     19                         // JS::DispatchToEventLoopCallback,
     20                         // JS::DelayedDispatchToEventLoopCallback
     21 #include "js/RootingAPI.h"                // JS::Handle, JS::PersistentRooted
     22 #include "threading/ConditionVariable.h"  // js::ConditionVariable
     23 #include "vm/PromiseObject.h"             // js::PromiseObject
     24 
     25 struct JS_PUBLIC_API JSContext;
     26 struct JS_PUBLIC_API JSRuntime;
     27 
     28 namespace js {
     29 
     30 class AutoLockHelperThreadState;
     31 class OffThreadPromiseRuntimeState;
     32 
     33 // [SMDOC] OffThreadPromiseTask: an off-main-thread task that resolves a promise
     34 //
     35 // An OffThreadPromiseTask is an abstract base class holding a JavaScript
     36 // promise that will be resolved (fulfilled or rejected) with the results of a
     37 // task possibly performed by some other thread. OffThreadPromiseTasks can be
     38 // undispatched (meaning that they can be dropped if the JSContext owning the
     39 // promise shuts down before the tasks resolves) or dispatched (meaning that
     40 // shutdown should wait for the task to join).
     41 //
     42 // An OffThreadPromiseTask's lifecycle is as follows:
     43 //
     44 // - Some JavaScript native wishes to return a promise of the result of some
     45 //   computation that might be performed by other threads (say, helper threads
     46 //   or the embedding's I/O threads), so it creates a PromiseObject to represent
     47 //   the result, and an OffThreadPromiseTask referring to it. After handing the
     48 //   OffThreadPromiseTask to the code doing the actual work, the native is free
     49 //   to return the PromiseObject to its caller.
     50 //
     51 // - When the computation is done, successfully or otherwise, it populates the
     52 //   OffThreadPromiseTask—which is actually an instance of some concrete
     53 //   subclass specific to the task—with the information needed to resolve the
     54 //   promise, and calls OffThreadPromiseTask::dispatchResolveAndDestroy. This
     55 //   enqueues a runnable on the JavaScript thread to which the promise belongs.
     56 //
     57 // - When it gets around to the runnable, the JavaScript thread calls the
     58 //   OffThreadPromiseTask's `resolve` method, which the concrete subclass has
     59 //   overriden to resolve the promise appropriately. This probably enqueues a
     60 //   promise reaction job.
     61 //
     62 // - The JavaScript thread then deletes the OffThreadPromiseTask.
     63 //
     64 // During shutdown, the process is slightly different. Enqueuing runnables to
     65 // the JavaScript thread begins to fail. Undispatched tasks are immediately
     66 // destroyed. JSRuntime shutdown waits for all outstanding dispatched tasks
     67 // to call their run methods, and then deletes them on the main thread,
     68 // without calling `resolve`.
     69 //
     70 // For example, the JavaScript function WebAssembly.compile uses
     71 // OffThreadPromiseTask to manage the result of a helper thread task, accepting
     72 // binary WebAssembly code and returning a promise of a compiled
     73 // WebAssembly.Module. It would like to do this compilation work on a helper
     74 // thread. When called by JavaScript, WebAssembly.compile creates a promise,
     75 // builds a CompileBufferTask (the OffThreadPromiseTask concrete subclass) to
     76 // keep track of it, and then hands that to a helper thread. When the helper
     77 // thread is done, successfully or otherwise, it calls the CompileBufferTask's
     78 // dispatchResolveAndDestroy method, which enqueues a runnable to the JavaScript
     79 // thread to resolve the promise and delete the CompileBufferTask.
     80 // (CompileBufferTask actually implements PromiseHelperTask, which implements
     81 // OffThreadPromiseTask; PromiseHelperTask is what our helper thread scheduler
     82 // requires.)
     83 //
     84 // OffThreadPromiseTasks are not limited to use with helper threads. For
     85 // example, a function returning a promise of the result of a network operation
     86 // could provide the code collecting the incoming data with an
     87 // OffThreadPromiseTask for the promise, and let the embedding's network I/O
     88 // threads call dispatchResolveAndDestroy.
     89 //
     90 // OffThreadPromiseTask may also be used purely on the main thread, as a way to
     91 // "queue a task" in HTML terms. Note that a "task" is not the same as a
     92 // "microtask" and there are separate queues for tasks and microtasks that are
     93 // drained at separate times in the browser. The task queue is implemented by
     94 // the browser's main event loop. The microtask queue is implemented
     95 // by JS::JobQueue, used for promises and gets drained before returning to
     96 // the event loop. Thus OffThreadPromiseTask can only be used when the spec
     97 // says "queue a task", as the WebAssembly APIs do. In some cases, like
     98 // Atomics.waitAsync, the choice between queuing a task or a microtask depends
     99 // on whether the promise is being resolved from the owning thread or another
    100 // thread. In such cases, ExtractAndForget can be used from the owning thread to
    101 // cancel the task and return the underlying promise, which can then be resolved
    102 // the normal way.
    103 //
    104 // An OffThreadPromiseTask has a JSContext, and must be constructed and have its
    105 // 'init' method called on that JSContext's thread. Once
    106 // initialized, its dispatchResolveAndDestroy method may be called from any
    107 // thread. Other than calling `ExtractAndForget`, or `DestroyUndispatchedTask`
    108 // during shutdown, this is the only safe way to destruct an
    109 // OffThreadPromiseTask; doing so ensures the OffThreadPromiseTask's destructor
    110 // will run on the JSContext's thread, either from the event loop or during
    111 // shutdown.
    112 //
    113 // OffThreadPromiseTask::dispatchResolveAndDestroy uses the
    114 // JS::DispatchToEventLoopCallback provided by the embedding to enqueue
    115 // runnables on the JavaScript thread. See the comments for
    116 // DispatchToEventLoopCallback for details.
    117 
    118 class OffThreadPromiseTask : public JS::Dispatchable {
    119  friend class OffThreadPromiseRuntimeState;
    120 
    121  JSRuntime* runtime_;
    122  JS::PersistentRooted<PromiseObject*> promise_;
    123 
    124  // Indicates that this is an undispatched cancellable task, which is a member
    125  // of the Cancellable list.  If cancellable is set to false, we can no longer
    126  // terminate the task early, this means it is no longer tracked by the
    127  // Cancellable list, and a dispatch has been attempted.
    128  bool cancellable_;
    129 
    130  void operator=(const OffThreadPromiseTask&) = delete;
    131  OffThreadPromiseTask(const OffThreadPromiseTask&) = delete;
    132 
    133  void unregister(OffThreadPromiseRuntimeState& state);
    134  // Used when we want to reuse a lock for unregistration and deletion.
    135  void unregister(OffThreadPromiseRuntimeState& state,
    136                  const AutoLockHelperThreadState& lock);
    137 
    138 protected:
    139  OffThreadPromiseTask(JSContext* cx, JS::Handle<PromiseObject*> promise);
    140 
    141  // To be called by OffThreadPromiseTask and implemented by the derived class.
    142  virtual bool resolve(JSContext* cx, JS::Handle<PromiseObject*> promise) {
    143    MOZ_CRASH("Tasks should override resolve");
    144  };
    145 
    146  // JS::Dispatchable override implementation.
    147  // Runs the task, and ends with 'js_delete(this)'.
    148  void run(JSContext* cx, MaybeShuttingDown maybeShuttingDown) final;
    149 
    150  // JS::Dispatchable override implementation, moves ownership to
    151  // OffThreadPromiseRuntimeState's failed_ list, for cleanup on shutdown.
    152  void transferToRuntime() final;
    153 
    154  // To be called by `destroy` during shutdown and implemented by the derived
    155  // class (for undispatched tasks only). Gives the task a chance to clean up
    156  // before being deleted.
    157  virtual void prepareForCancel() {
    158    MOZ_CRASH("Undispatched tasks should override prepareForCancel");
    159  }
    160 
    161 public:
    162  ~OffThreadPromiseTask() override;
    163  static void DestroyUndispatchedTask(OffThreadPromiseTask* task,
    164                                      OffThreadPromiseRuntimeState& state,
    165                                      const AutoLockHelperThreadState& lock);
    166 
    167  JSRuntime* runtime() { return runtime_; }
    168 
    169  // Calling `init` on an OffThreadPromiseTask informs the runtime that it must
    170  // wait on shutdown for this task to rejoin the active JSContext by calling
    171  // dispatchResolveAndDestroy().
    172  bool init(JSContext* cx);
    173  bool init(JSContext* cx, const AutoLockHelperThreadState& lock);
    174 
    175  // Cancellable initialization track the task in case it needs to be aborted
    176  // before it is dispatched. Cancellable tasks are owned by the cancellable_
    177  // hashMap until they are dispatched.
    178  static bool InitCancellable(JSContext* cx,
    179                              js::UniquePtr<OffThreadPromiseTask>&& task);
    180  static bool InitCancellable(JSContext* cx,
    181                              const AutoLockHelperThreadState& lock,
    182                              js::UniquePtr<OffThreadPromiseTask>&& task);
    183 
    184  // Remove the cancellable task from the runtime cancellable list and
    185  // call DispatchResolveAndDestroy with a newly created UniquePtr,
    186  // so that ownership moves to the embedding.
    187  //
    188  // If a task is never removed from the cancellable list, it is deleted on
    189  // shutdown without running.
    190  void removeFromCancellableListAndDispatch();
    191  void removeFromCancellableListAndDispatch(
    192      const AutoLockHelperThreadState& lock);
    193 
    194  // These first two methods will wrap a pointer in a uniquePtr for the purpose
    195  // of passing it's ownership eventually to the embedding. These are used by
    196  // WASM and PromiseHelperTask.
    197  void dispatchResolveAndDestroy();
    198  void dispatchResolveAndDestroy(const AutoLockHelperThreadState& lock);
    199 
    200  // An initialized OffThreadPromiseTask can be dispatched to an active
    201  // JSContext of its Promise's JSRuntime from any thread. Normally, this will
    202  // lead to resolve() being called on JSContext thread, given the Promise.
    203  // However, if shutdown interrupts, resolve() may not be called, though the
    204  // OffThreadPromiseTask will be destroyed on a JSContext thread.
    205  static void DispatchResolveAndDestroy(
    206      js::UniquePtr<OffThreadPromiseTask>&& task);
    207  static void DispatchResolveAndDestroy(
    208      js::UniquePtr<OffThreadPromiseTask>&& task,
    209      const AutoLockHelperThreadState& lock);
    210 
    211  static PromiseObject* ExtractAndForget(OffThreadPromiseTask* task,
    212                                         const AutoLockHelperThreadState& lock);
    213 };
    214 
    215 using OffThreadPromiseTaskSet =
    216    HashSet<OffThreadPromiseTask*, DefaultHasher<OffThreadPromiseTask*>,
    217            SystemAllocPolicy>;
    218 
    219 using DispatchableFifo =
    220    Fifo<js::UniquePtr<JS::Dispatchable>, 0, SystemAllocPolicy>;
    221 
    222 class DelayedDispatchable {
    223  js::UniquePtr<JS::Dispatchable> dispatchable_;
    224  mozilla::TimeStamp endTime_;
    225 
    226 public:
    227  DelayedDispatchable(DelayedDispatchable&& other)
    228      : dispatchable_(other.dispatchable()), endTime_(other.endTime()) {}
    229 
    230  DelayedDispatchable(js::UniquePtr<JS::Dispatchable>&& dispatchable,
    231                      mozilla::TimeStamp endTime)
    232      : dispatchable_(std::move(dispatchable)), endTime_(endTime) {}
    233 
    234  void operator=(DelayedDispatchable&& other) {
    235    dispatchable_ = other.dispatchable();
    236    endTime_ = other.endTime();
    237  }
    238  js::UniquePtr<JS::Dispatchable> dispatchable() {
    239    return std::move(dispatchable_);
    240  }
    241  mozilla::TimeStamp endTime() const { return endTime_; }
    242 
    243  static bool higherPriority(const DelayedDispatchable& a,
    244                             const DelayedDispatchable& b) {
    245    return a.endTime_ < b.endTime_;
    246  }
    247 };
    248 
    249 using DelayedDispatchablePriorityQueue =
    250    PriorityQueue<DelayedDispatchable, DelayedDispatchable, 0,
    251                  SystemAllocPolicy>;
    252 
    253 class OffThreadPromiseRuntimeState {
    254  friend class OffThreadPromiseTask;
    255 
    256  // These fields are initialized once before any off-thread usage and thus do
    257  // not require a lock.
    258  JS::DispatchToEventLoopCallback dispatchToEventLoopCallback_;
    259  JS::DelayedDispatchToEventLoopCallback delayedDispatchToEventLoopCallback_;
    260  JS::AsyncTaskStartedCallback asyncTaskStartedCallback_;
    261  JS::AsyncTaskFinishedCallback asyncTaskFinishedCallback_;
    262  void* dispatchToEventLoopClosure_;
    263 
    264 #ifdef DEBUG
    265  // Set to true when the JS shell is force-quitting.
    266  // In this case the tasks won't be drained and the destructor cannot
    267  // assert anything.
    268  HelperThreadLockData<bool> forceQuitting_;
    269 #endif
    270 
    271  // A set of all OffThreadPromiseTasks that have successfully called 'init'.
    272  // This set doesn't own tasks. OffThreadPromiseTask's destructor decrements
    273  // this counter.
    274  HelperThreadLockData<size_t> numRegistered_;
    275 
    276  // Currently, we have a subset of tasks which are not registered with
    277  // OffThreadPromiseRuntimeState (as they do not contain a promise), but
    278  // still depend on the internalDispatchQueue. These are JS::Dispatchables
    279  // that do not inherit from OffThreadPromiseTask, namely
    280  // WaitAsyncTimeoutTask. As a result, our assertions are broken. In order
    281  // to handle these non-registered tasks, we keep a separate counter for
    282  // dispatchables that are passed from the delayed dispatch queue to the
    283  // internal dispatch queue.
    284  //
    285  // This is a temporary fix that allows us to track these tasks.
    286  // TODO: remove this once we clean up this behavior.
    287  HelperThreadLockData<size_t> numDelayed_;
    288 
    289  // The cancellable hashmap tracks the registered, but thusfar
    290  // undispatched tasks. Not all undispatched tasks are cancellable, namely
    291  // webassembly helpers are held in an undispatched state, but are not
    292  // cancellable. Until the task is dispatched, this hashmap acts as the owner
    293  // of the task. In order to place something in the cancellable list, use
    294  // InitCancellable. Any task owned by the cancellable list cannot be
    295  // dispatched using DispatchResolveAndDestroy. Instead, use the method on
    296  // OffThreadPromiseTask removeFromCancellableAndDispatch.
    297  HelperThreadLockData<OffThreadPromiseTaskSet> cancellable_;
    298 
    299  // This list owns tasks that have failed to dispatch or failed to execute.
    300  // The list is cleared on shutdown.
    301  HelperThreadLockData<DispatchableFifo> failed_;
    302 
    303  // The allFailed_ condition is waited on and notified during engine
    304  // shutdown, communicating when all off-thread tasks in failed_ are safe to be
    305  // destroyed from the (shutting down) main thread. This condition is met when
    306  // numRegistered_ == failed().count(), where the collection of failed tasks
    307  // mean "the DispatchToEventLoopCallback failed after this task was dispatched
    308  // for execution".
    309  HelperThreadLockData<ConditionVariable> allFailed_;
    310 
    311  // The queue of JS::Dispatchables used by the DispatchToEventLoopCallback that
    312  // calling js::UseInternalJobQueues installs.
    313  HelperThreadLockData<DispatchableFifo> internalDispatchQueue_;
    314  HelperThreadLockData<ConditionVariable> internalDispatchQueueAppended_;
    315  HelperThreadLockData<bool> internalDispatchQueueClosed_;
    316  HelperThreadLockData<DelayedDispatchablePriorityQueue>
    317      internalDelayedDispatchPriorityQueue_;
    318 
    319  ConditionVariable& allFailed() { return allFailed_.ref(); }
    320 
    321  DispatchableFifo& failed() { return failed_.ref(); }
    322  OffThreadPromiseTaskSet& cancellable() { return cancellable_.ref(); }
    323 
    324  DispatchableFifo& internalDispatchQueue() {
    325    return internalDispatchQueue_.ref();
    326  }
    327  ConditionVariable& internalDispatchQueueAppended() {
    328    return internalDispatchQueueAppended_.ref();
    329  }
    330  DelayedDispatchablePriorityQueue& internalDelayedDispatchPriorityQueue() {
    331    return internalDelayedDispatchPriorityQueue_.ref();
    332  }
    333 
    334  void dispatchDelayedTasks();
    335 
    336  static bool internalDispatchToEventLoop(void*,
    337                                          js::UniquePtr<JS::Dispatchable>&&);
    338  static bool internalDelayedDispatchToEventLoop(
    339      void*, js::UniquePtr<JS::Dispatchable>&&, uint32_t);
    340  bool usingInternalDispatchQueue() const;
    341 
    342  void operator=(const OffThreadPromiseRuntimeState&) = delete;
    343  OffThreadPromiseRuntimeState(const OffThreadPromiseRuntimeState&) = delete;
    344 
    345  // Used by OffThreadPromiseTask
    346  void registerTask(JSContext* cx, OffThreadPromiseTask* task);
    347  void unregisterTask(OffThreadPromiseTask* task);
    348 
    349 public:
    350  OffThreadPromiseRuntimeState();
    351  ~OffThreadPromiseRuntimeState();
    352  void init(JS::DispatchToEventLoopCallback dispatchCallback,
    353            JS::DelayedDispatchToEventLoopCallback delayedDispatchCallback,
    354            JS::AsyncTaskStartedCallback asyncTaskStartedCallback,
    355            JS::AsyncTaskFinishedCallback asyncTaskFinishedCallback,
    356            void* closure);
    357  void initInternalDispatchQueue();
    358  bool initialized() const;
    359 
    360  // If initInternalDispatchQueue() was called, internalDrain() can be
    361  // called to periodically drain the dispatch queue before shutdown.
    362  void internalDrain(JSContext* cx);
    363  bool internalHasPending();
    364  bool internalHasPending(AutoLockHelperThreadState& lock);
    365 
    366  void stealFailedTask(JS::Dispatchable* dispatchable);
    367 
    368  bool dispatchToEventLoop(js::UniquePtr<JS::Dispatchable>&& dispatchable);
    369  bool delayedDispatchToEventLoop(
    370      js::UniquePtr<JS::Dispatchable>&& dispatchable, uint32_t delay);
    371 
    372  void cancelTasks(JSContext* cx);
    373  void cancelTasks(AutoLockHelperThreadState& lock, JSContext* cx);
    374 
    375  // shutdown() must be called by the JSRuntime while the JSRuntime is valid.
    376  void shutdown(JSContext* cx);
    377 
    378 #ifdef DEBUG
    379  void setForceQuitting() { forceQuitting_ = true; }
    380 #endif
    381 };
    382 
    383 }  // namespace js
    384 
    385 #endif  // vm_OffThreadPromiseRuntimeState_h