LCOV - code coverage report
Current view: top level - gjs - promise.cpp (source / functions) Coverage Total Hit
Test: gjs-1.87.90 Code Coverage Lines: 90.1 % 81 73
Test Date: 2026-04-07 00:59:09 Functions: 100.0 % 19 19
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 62.5 % 16 10

             Branch data     Line data    Source code
       1                 :             : // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
       2                 :             : // SPDX-FileCopyrightText: 2021 Evan Welsh <contact@evanwelsh.com>
       3                 :             : // SPDX-FileCopyrightText: 2021 Marco Trevisan <mail@3v1n0.net>
       4                 :             : 
       5                 :             : #include <config.h>
       6                 :             : 
       7                 :             : #include <stddef.h>  // for size_t
       8                 :             : 
       9                 :             : #include <string>  // for string methods
      10                 :             : 
      11                 :             : #include <gio/gio.h>
      12                 :             : #include <glib-object.h>
      13                 :             : 
      14                 :             : #include <js/CallAndConstruct.h>  // for JS::IsCallable
      15                 :             : #include <js/CallArgs.h>
      16                 :             : #include <js/PropertyAndElement.h>  // for JS_DefineFunctions
      17                 :             : #include <js/PropertySpec.h>
      18                 :             : #include <js/RootingAPI.h>
      19                 :             : #include <js/TypeDecls.h>
      20                 :             : #include <jsapi.h>  // for JS_NewPlainObject
      21                 :             : #include <jsfriendapi.h>  // for RunJobs
      22                 :             : 
      23                 :             : #include "gjs/auto.h"
      24                 :             : #include "gjs/context-private.h"
      25                 :             : #include "gjs/jsapi-util-args.h"
      26                 :             : #include "gjs/jsapi-util.h"
      27                 :             : #include "gjs/macros.h"
      28                 :             : #include "gjs/promise.h"
      29                 :             : #include "util/log.h"
      30                 :             : 
      31                 :             : /**
      32                 :             :  * promise.cpp - This file implements a custom GSource, PromiseJobQueueSource,
      33                 :             :  * which handles promise dispatching within GJS. Custom GSources are able to
      34                 :             :  * control under which conditions they dispatch. PromiseJobQueueSource will
      35                 :             :  * always dispatch if even a single Promise is enqueued and will continue
      36                 :             :  * dispatching until all Promises (also known as "Jobs" within SpiderMonkey)
      37                 :             :  * are run. While this does technically mean Promises can starve the mainloop
      38                 :             :  * if run recursively, this is intentional. Within JavaScript Promises are
      39                 :             :  * considered "microtasks" and a microtask must run before any other task
      40                 :             :  * continues.
      41                 :             :  *
      42                 :             :  * PromiseJobQueueSource is attached to the thread's default GMainContext with
      43                 :             :  * a default priority of -1000. This is 10x the priority of G_PRIORITY_HIGH and
      44                 :             :  * no application code should attempt to override this.
      45                 :             :  *
      46                 :             :  * See doc/Custom-GSources.md for more background information on custom
      47                 :             :  * GSources and microtasks
      48                 :             :  */
      49                 :             : 
      50                 :             : namespace Gjs {
      51                 :             : 
      52                 :             : /**
      53                 :             :  * PromiseJobDispatcher::Source:
      54                 :             :  *
      55                 :             :  * A custom GSource which handles draining our job queue.
      56                 :             :  */
      57                 :             : class PromiseJobDispatcher::Source : public GSource {
      58                 :             :     // The private GJS context this source runs within.
      59                 :             :     GjsContextPrivate* m_gjs;
      60                 :             :     // The main context this source attaches to.
      61                 :             :     AutoMainContext m_main_context;
      62                 :             :     // The cancellable that stops this source.
      63                 :             :     AutoUnref<GCancellable> m_cancellable;
      64                 :        3680 :     AutoPointer<GSource, GSource, g_source_unref> m_cancellable_source;
      65                 :             : 
      66                 :             :     // G_PRIORITY_HIGH is normally -100, we set 10 times that to ensure our
      67                 :             :     // source always has the greatest priority. This means our prepare will
      68                 :             :     // be called before other sources, and prepare will determine whether
      69                 :             :     // we dispatch.
      70                 :             :     static constexpr int PRIORITY = 10 * G_PRIORITY_HIGH;
      71                 :             : 
      72                 :             :     // GSource custom functions
      73                 :             :     static GSourceFuncs source_funcs;
      74                 :             : 
      75                 :             :     // Called to determine whether the source should run (dispatch) in the
      76                 :             :     // next event loop iteration. If the job queue is not empty we return true
      77                 :             :     // to schedule a dispatch.
      78                 :       11394 :     gboolean prepare(int* timeout [[maybe_unused]]) { return !m_gjs->empty(); }
      79                 :             : 
      80                 :         524 :     gboolean dispatch() {
      81         [ -  + ]:         524 :         if (g_cancellable_is_cancelled(m_cancellable))
      82                 :           0 :             return G_SOURCE_REMOVE;
      83                 :             : 
      84                 :             :         // The ready time is sometimes set to 0 to kick us out of polling,
      85                 :             :         // we need to reset the value here or this source will always be the
      86                 :             :         // next one to execute. (it will starve the other sources)
      87                 :         524 :         g_source_set_ready_time(this, -1);
      88                 :             : 
      89                 :             :         // Drain the job queue.
      90                 :         524 :         js::RunJobs(m_gjs->context());
      91                 :             : 
      92                 :         524 :         return G_SOURCE_CONTINUE;
      93                 :             :     }
      94                 :             : 
      95                 :             :  public:
      96                 :             :     /**
      97                 :             :      * Source::Source:
      98                 :             :      * @gjs: the GJS object
      99                 :             :      * @main_context: GLib main context to associate with the source
     100                 :             :      *
     101                 :             :      * Constructs a new GSource for the PromiseJobDispatcher and adds a
     102                 :             :      * reference to the associated main context.
     103                 :             :      */
     104                 :         255 :     Source(GjsContextPrivate* gjs, GMainContext* main_context)
     105                 :         255 :         : m_gjs(gjs),
     106                 :         255 :           m_main_context(main_context, TakeOwnership{}),
     107                 :         255 :           m_cancellable(g_cancellable_new()),
     108                 :         255 :           m_cancellable_source(g_cancellable_source_new(m_cancellable)) {
     109                 :         255 :         g_source_set_priority(this, PRIORITY);
     110                 :         255 :         g_source_set_static_name(this, "GjsPromiseJobQueueSource");
     111                 :             : 
     112                 :             :         // Add our cancellable source to our main source,
     113                 :             :         // this will trigger the main source if our cancellable
     114                 :             :         // is cancelled.
     115                 :         255 :         g_source_add_child_source(this, m_cancellable_source);
     116                 :         255 :     }
     117                 :             : 
     118                 :         255 :     void* operator new(size_t size) {
     119                 :         255 :         return g_source_new(&source_funcs, size);
     120                 :             :     }
     121                 :         253 :     void operator delete(void* p) { g_source_unref(static_cast<GSource*>(p)); }
     122                 :             : 
     123                 :        9935 :     bool is_running() { return !!g_source_get_context(this); }
     124                 :             : 
     125                 :             :     /**
     126                 :             :      * Source::cancel:
     127                 :             :      *
     128                 :             :      * Trigger the cancellable, detaching our source.
     129                 :             :      */
     130                 :        3695 :     void cancel() { g_cancellable_cancel(m_cancellable); }
     131                 :             :     /**
     132                 :             :      * Source::uncancel:
     133                 :             :      *
     134                 :             :      * Reset the cancellable and prevent the source from stopping, overriding a
     135                 :             :      * previous cancel() call. Called by PromiseJobDispatcher::start() to ensure
     136                 :             :      * the custom source will start.
     137                 :             :      */
     138                 :        5105 :     void uncancel() {
     139         [ +  + ]:        5105 :         if (!g_cancellable_is_cancelled(m_cancellable))
     140                 :        1678 :             return;
     141                 :             : 
     142                 :        3427 :         gjs_debug(GJS_DEBUG_MAINLOOP, "Uncancelling promise job dispatcher");
     143                 :             : 
     144         [ +  - ]:        3427 :         if (is_running())
     145                 :        3427 :             g_source_remove_child_source(this, m_cancellable_source);
     146                 :             :         else
     147                 :           0 :             g_source_destroy(m_cancellable_source);
     148                 :             : 
     149                 :             :         // Drop the old cancellable and create a new one, as per
     150                 :             :         // https://docs.gtk.org/gio/method.Cancellable.reset.html
     151                 :        3427 :         m_cancellable = g_cancellable_new();
     152                 :        3427 :         m_cancellable_source = g_cancellable_source_new(m_cancellable);
     153                 :        3427 :         g_source_add_child_source(this, m_cancellable_source);
     154                 :             :     }
     155                 :             : };
     156                 :             : 
     157                 :             : GSourceFuncs PromiseJobDispatcher::Source::source_funcs = {
     158                 :       11394 :     [](GSource* source, int* timeout) {
     159                 :       11394 :         return static_cast<Source*>(source)->prepare(timeout);
     160                 :             :     },
     161                 :             :     nullptr,  // check
     162                 :         524 :     [](GSource* source, GSourceFunc, void*) {
     163                 :         524 :         return static_cast<Source*>(source)->dispatch();
     164                 :             :     },
     165                 :         253 :     [](GSource* source) { static_cast<Source*>(source)->~Source(); },
     166                 :             : };
     167                 :             : 
     168                 :         255 : PromiseJobDispatcher::PromiseJobDispatcher(GjsContextPrivate* gjs)
     169                 :             :     // Acquire a guaranteed reference to this thread's default main context
     170                 :         255 :     : m_main_context(g_main_context_ref_thread_default()),
     171                 :             :       // Create and reference our custom GSource
     172                 :         255 :       m_source(std::make_unique<Source>(gjs, m_main_context)) {}
     173                 :             : 
     174                 :         253 : PromiseJobDispatcher::~PromiseJobDispatcher() {
     175                 :         253 :     g_source_destroy(m_source.get());
     176                 :         253 : }
     177                 :             : 
     178                 :        6508 : bool PromiseJobDispatcher::is_running() { return m_source->is_running(); }
     179                 :             : 
     180                 :        5105 : void PromiseJobDispatcher::start() {
     181                 :             :     // Reset the cancellable
     182                 :        5105 :     m_source->uncancel();
     183                 :             : 
     184                 :             :     // Don't re-attach if the task is already running
     185         [ +  + ]:        5105 :     if (is_running())
     186                 :        4850 :         return;
     187                 :             : 
     188                 :         255 :     gjs_debug(GJS_DEBUG_MAINLOOP, "Starting promise job dispatcher");
     189                 :         255 :     g_source_attach(m_source.get(), m_main_context);
     190                 :             : }
     191                 :             : 
     192                 :        3695 : void PromiseJobDispatcher::stop() {
     193                 :        3695 :     gjs_debug(GJS_DEBUG_MAINLOOP, "Stopping promise job dispatcher");
     194                 :        3695 :     m_source->cancel();
     195                 :        3695 : }
     196                 :             : 
     197                 :             : };  // namespace Gjs
     198                 :             : 
     199                 :             : GJS_JSAPI_RETURN_CONVENTION
     200                 :        9827 : bool drain_microtask_queue(JSContext* cx, unsigned argc, JS::Value* vp) {
     201                 :        9827 :     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
     202                 :             : 
     203                 :        9827 :     js::RunJobs(cx);
     204                 :             : 
     205                 :        9827 :     args.rval().setUndefined();
     206                 :        9827 :     return true;
     207                 :             : }
     208                 :             : 
     209                 :             : GJS_JSAPI_RETURN_CONVENTION
     210                 :          51 : bool set_main_loop_hook(JSContext* cx, unsigned argc, JS::Value* vp) {
     211                 :          51 :     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
     212                 :             : 
     213                 :          51 :     JS::RootedObject callback(cx);
     214         [ -  + ]:          51 :     if (!gjs_parse_call_args(cx, "setMainLoopHook", args, "o", "callback",
     215                 :             :                              &callback)) {
     216                 :           0 :         return false;
     217                 :             :     }
     218                 :             : 
     219         [ -  + ]:          51 :     if (!JS::IsCallable(callback)) {
     220                 :           0 :         gjs_throw(cx, "Main loop hook must be callable");
     221                 :           0 :         return false;
     222                 :             :     }
     223                 :             : 
     224                 :          51 :     gjs_debug(GJS_DEBUG_MAINLOOP, "Set main loop hook to %s",
     225                 :         102 :               gjs_debug_object(callback).c_str());
     226                 :             : 
     227                 :          51 :     GjsContextPrivate* priv = GjsContextPrivate::from_cx(cx);
     228         [ -  + ]:          51 :     if (!priv->set_main_loop_hook(callback)) {
     229                 :           0 :         gjs_throw(
     230                 :             :             cx,
     231                 :             :             "A mainloop is already running. Did you already call runAsync()?");
     232                 :           0 :         return false;
     233                 :             :     }
     234                 :             : 
     235                 :          51 :     args.rval().setUndefined();
     236                 :          51 :     return true;
     237                 :          51 : }
     238                 :             : 
     239                 :             : JSFunctionSpec gjs_native_promise_module_funcs[] = {
     240                 :             :     JS_FN("drainMicrotaskQueue", &drain_microtask_queue, 0, 0),
     241                 :             :     JS_FN("setMainLoopHook", &set_main_loop_hook, 1, 0), JS_FS_END};
     242                 :             : 
     243                 :          64 : bool gjs_define_native_promise_stuff(JSContext* cx,
     244                 :             :                                      JS::MutableHandleObject module) {
     245                 :          64 :     module.set(JS_NewPlainObject(cx));
     246         [ -  + ]:          64 :     if (!module)
     247                 :           0 :         return false;
     248                 :          64 :     return JS_DefineFunctions(cx, module, gjs_native_promise_module_funcs);
     249                 :             : }
        

Generated by: LCOV version 2.0-1