[gjs: 18/25] context: Rewrite promise job queue



commit b29e8362c6e031f80744906afa988fa0154bd428
Author: Philip Chimento <philip chimento gmail com>
Date:   Mon May 20 23:49:10 2019 -0700

    context: Rewrite promise job queue
    
    SpiderMonkey 68 has a new promise job queue API, where instead of
    setting up callbacks when initializing the JS engine, you have to pass
    it an object with overridden methods. It makes the most sense for this
    object to be the GjsContextPrivate object, since the previous callbacks
    were already members of GjsContextPrivate. This means that
    GjsContextPrivate now inherits from JS::JobQueue.

 gjs/context-private.h | 29 ++++++++++++---
 gjs/context.cpp       | 98 +++++++++++++++++++++++++++++++++++++++++----------
 gjs/engine.cpp        | 13 +------
 modules/console.cpp   |  2 +-
 4 files changed, 106 insertions(+), 36 deletions(-)
---
diff --git a/gjs/context-private.h b/gjs/context-private.h
index 35625d07..5cbf3ade 100644
--- a/gjs/context-private.h
+++ b/gjs/context-private.h
@@ -35,6 +35,7 @@
 
 #include "gjs/jsapi-wrapper.h"
 #include "js/GCHashTable.h"
+#include "js/Promise.h"
 
 #include "gjs/atoms.h"
 #include "gjs/context.h"
@@ -42,7 +43,8 @@
 #include "gjs/macros.h"
 #include "gjs/profiler.h"
 
-using JobQueue = JS::GCVector<JS::Heap<JSObject*>, 0, js::SystemAllocPolicy>;
+using JobQueueStorage =
+    JS::GCVector<JS::Heap<JSObject*>, 0, js::SystemAllocPolicy>;
 using ObjectInitList =
     JS::GCVector<JS::Heap<JSObject*>, 0, js::SystemAllocPolicy>;
 using FundamentalTable =
@@ -68,7 +70,7 @@ template <>
 struct GCPolicy<GTypeNotUint64> : public IgnoreGCPolicy<GTypeNotUint64> {};
 }  // namespace JS
 
-class GjsContextPrivate {
+class GjsContextPrivate : public JS::JobQueue {
     GjsContext* m_public_context;
     JSContext* m_cx;
     JS::Heap<JSObject*> m_global;
@@ -82,7 +84,7 @@ class GjsContextPrivate {
 
     GjsAtoms* m_atoms;
 
-    JobQueue m_job_queue;
+    JobQueueStorage m_job_queue;
     unsigned m_idle_drain_handler;
 
     std::unordered_map<uint64_t, GjsAutoChar> m_unhandled_rejection_stacks;
@@ -126,7 +128,12 @@ class GjsContextPrivate {
 
     void schedule_gc_internal(bool force_gc);
     static gboolean trigger_gc_if_needed(void* data);
+
+    class SavedQueue;
+    void start_draining_job_queue(void);
+    void stop_draining_job_queue(void);
     static gboolean drain_job_queue_idle_handler(void* data);
+
     void warn_about_unhandled_promise_rejections(void);
 
     class AutoResetExit {
@@ -202,8 +209,20 @@ class GjsContextPrivate {
     void exit(uint8_t exit_code);
     GJS_USE bool should_exit(uint8_t* exit_code_p) const;
 
-    GJS_JSAPI_RETURN_CONVENTION bool enqueue_job(JS::HandleObject job);
-    GJS_JSAPI_RETURN_CONVENTION bool run_jobs(void);
+    // Implementations of JS::JobQueue virtual functions
+    GJS_JSAPI_RETURN_CONVENTION
+    JSObject* getIncumbentGlobal(JSContext* cx) override;
+    GJS_JSAPI_RETURN_CONVENTION
+    bool enqueuePromiseJob(JSContext* cx, JS::HandleObject promise,
+                           JS::HandleObject job,
+                           JS::HandleObject allocation_site,
+                           JS::HandleObject incumbent_global) override;
+    void runJobs(JSContext* cx) override;
+    GJS_USE bool empty(void) const override { return m_job_queue.empty(); };
+    js::UniquePtr<JS::JobQueue::SavedJobQueue> saveJobQueue(
+        JSContext* cx) override;
+
+    GJS_JSAPI_RETURN_CONVENTION bool run_jobs_fallible(void);
     void register_unhandled_promise_rejection(uint64_t id, GjsAutoChar&& stack);
     void unregister_unhandled_promise_rejection(uint64_t id);
 
diff --git a/gjs/context.cpp b/gjs/context.cpp
index 709a2b81..b5bdd157 100644
--- a/gjs/context.cpp
+++ b/gjs/context.cpp
@@ -651,37 +651,67 @@ bool GjsContextPrivate::should_exit(uint8_t* exit_code_p) const {
     return m_should_exit;
 }
 
+void GjsContextPrivate::start_draining_job_queue(void) {
+    if (!m_idle_drain_handler)
+        m_idle_drain_handler = g_idle_add_full(
+            G_PRIORITY_DEFAULT, drain_job_queue_idle_handler, this, nullptr);
+}
+
+void GjsContextPrivate::stop_draining_job_queue(void) {
+    m_draining_job_queue = false;
+    if (m_idle_drain_handler) {
+        g_source_remove(m_idle_drain_handler);
+        m_idle_drain_handler = 0;
+    }
+}
+
 gboolean GjsContextPrivate::drain_job_queue_idle_handler(void* data) {
     auto* gjs = static_cast<GjsContextPrivate*>(data);
-    if (!gjs->run_jobs())
-        gjs_log_exception(gjs->context());
+    gjs->runJobs(gjs->context());
     /* Uncatchable exceptions are swallowed here - no way to get a handle on
      * the main loop to exit it from this idle handler */
-    g_assert(((void)"GjsContextPrivate::run_jobs() should have emptied queue",
-              gjs->m_idle_drain_handler == 0));
+    g_assert(gjs->empty() && gjs->m_idle_drain_handler == 0 &&
+             "GjsContextPrivate::runJobs() should have emptied queue");
     return G_SOURCE_REMOVE;
 }
 
+JSObject* GjsContextPrivate::getIncumbentGlobal(JSContext* cx) {
+    return gjs_get_import_global(cx);
+}
+
 /* See engine.cpp and JS::SetEnqueuePromiseJobCallback(). */
-bool GjsContextPrivate::enqueue_job(JS::HandleObject job) {
+bool GjsContextPrivate::enqueuePromiseJob(
+    JSContext* cx, JS::HandleObject promise G_GNUC_UNUSED, JS::HandleObject job,
+    JS::HandleObject allocation_site G_GNUC_UNUSED,
+    JS::HandleObject incumbent_global G_GNUC_UNUSED) {
+    g_assert(cx == m_cx);
+    g_assert(from_cx(cx) == this);
+
     if (m_idle_drain_handler)
-        g_assert(m_job_queue.length() > 0);
+        g_assert(!empty());
     else
-        g_assert(m_job_queue.length() == 0);
+        g_assert(empty());
 
     if (!m_job_queue.append(job)) {
         JS_ReportOutOfMemory(m_cx);
         return false;
     }
-    if (!m_idle_drain_handler)
-        m_idle_drain_handler = g_idle_add_full(
-            G_PRIORITY_DEFAULT, drain_job_queue_idle_handler, this, nullptr);
 
+    start_draining_job_queue();
     return true;
 }
 
+// Override of JobQueue::runJobs(). Called by js::RunJobs(), and when execution
+// of the job queue was interrupted by the debugger and is resuming.
+void GjsContextPrivate::runJobs(JSContext* cx) {
+    g_assert(cx == m_cx);
+    g_assert(from_cx(cx) == this);
+    if (!run_jobs_fallible())
+        gjs_log_exception(cx);
+}
+
 /*
- * GjsContext::run_jobs:
+ * GjsContext::run_jobs_fallible:
  *
  * Drains the queue of promise callbacks that the JS engine has reported
  * finished, calling each one and logging any exceptions that it throws.
@@ -692,7 +722,7 @@ bool GjsContextPrivate::enqueue_job(JS::HandleObject job) {
  * Returns: false if one of the jobs threw an uncatchable exception;
  * otherwise true.
  */
-bool GjsContextPrivate::run_jobs(void) {
+bool GjsContextPrivate::run_jobs_fallible(void) {
     bool retval = true;
 
     if (m_draining_job_queue || m_should_exit)
@@ -743,15 +773,47 @@ bool GjsContextPrivate::run_jobs(void) {
         }
     }
 
-    m_draining_job_queue = false;
     m_job_queue.clear();
-    if (m_idle_drain_handler) {
-        g_source_remove(m_idle_drain_handler);
-        m_idle_drain_handler = 0;
-    }
+    stop_draining_job_queue();
     return retval;
 }
 
+class GjsContextPrivate::SavedQueue : public JS::JobQueue::SavedJobQueue {
+ private:
+    GjsContextPrivate* m_gjs;
+    JS::PersistentRooted<JobQueueStorage> m_queue;
+    bool m_was_draining : 1;
+
+ public:
+    explicit SavedQueue(GjsContextPrivate* gjs)
+        : m_gjs(gjs),
+          m_queue(gjs->m_cx, std::move(gjs->m_job_queue)),
+          m_was_draining(gjs->m_draining_job_queue) {
+        gjs->stop_draining_job_queue();
+    }
+
+    ~SavedQueue(void) {
+        m_gjs->m_job_queue = std::move(m_queue.get());
+        if (m_was_draining)
+            m_gjs->start_draining_job_queue();
+    }
+};
+
+js::UniquePtr<JS::JobQueue::SavedJobQueue> GjsContextPrivate::saveJobQueue(
+    JSContext* cx) {
+    g_assert(cx == m_cx);
+    g_assert(from_cx(cx) == this);
+
+    auto saved_queue = js::MakeUnique<SavedQueue>(this);
+    if (!saved_queue) {
+        JS_ReportOutOfMemory(cx);
+        return nullptr;
+    }
+
+    g_assert(m_job_queue.empty());
+    return saved_queue;
+}
+
 void GjsContextPrivate::register_unhandled_promise_rejection(
     uint64_t id, GjsAutoChar&& stack) {
     m_unhandled_rejection_stacks[id] = std::move(stack);
@@ -880,7 +942,7 @@ bool GjsContextPrivate::eval(const char* script, ssize_t script_len,
      * uncaught exceptions have been reported since draining runs callbacks. */
     {
         JS::AutoSaveExceptionState saved_exc(m_cx);
-        ok = run_jobs() && ok;
+        ok = run_jobs_fallible() && ok;
     }
 
     if (auto_profile)
diff --git a/gjs/engine.cpp b/gjs/engine.cpp
index 20ac1178..bf7dab73 100644
--- a/gjs/engine.cpp
+++ b/gjs/engine.cpp
@@ -110,15 +110,6 @@ static void on_garbage_collect(JSContext*, JSGCStatus status, void*) {
     }
 }
 
-GJS_JSAPI_RETURN_CONVENTION
-static bool on_enqueue_promise_job(
-    JSContext*, JS::HandleObject callback,
-    JS::HandleObject allocation_site G_GNUC_UNUSED,
-    JS::HandleObject global G_GNUC_UNUSED, void* data) {
-    auto* gjs = static_cast<GjsContextPrivate*>(data);
-    return gjs->enqueue_job(callback);
-}
-
 static void on_promise_unhandled_rejection(
     JSContext* cx, JS::HandleObject promise,
     JS::PromiseRejectionHandlingState state, void* data) {
@@ -240,9 +231,7 @@ JSContext* gjs_create_js_context(GjsContextPrivate* uninitialized_gjs) {
     JS_AddFinalizeCallback(cx, gjs_finalize_callback, uninitialized_gjs);
     JS_SetGCCallback(cx, on_garbage_collect, uninitialized_gjs);
     JS::SetWarningReporter(cx, gjs_warning_reporter);
-    JS::SetGetIncumbentGlobalCallback(cx, gjs_get_import_global);
-    JS::SetEnqueuePromiseJobCallback(cx, on_enqueue_promise_job,
-                                     uninitialized_gjs);
+    JS::SetJobQueue(cx, dynamic_cast<JS::JobQueue*>(uninitialized_gjs));
     JS::SetPromiseRejectionTrackerCallback(cx, on_promise_unhandled_rejection,
                                            uninitialized_gjs);
 
diff --git a/modules/console.cpp b/modules/console.cpp
index 607d3ec0..2abf72a0 100644
--- a/modules/console.cpp
+++ b/modules/console.cpp
@@ -349,7 +349,7 @@ gjs_console_interact(JSContext *context,
         g_string_free(buffer, true);
 
         GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context);
-        ok = gjs->run_jobs() && ok;
+        ok = gjs->run_jobs_fallible() && ok;
 
         if (!ok) {
             /* If this was an uncatchable exception, throw another uncatchable


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]