[gjs/ewlsh/main-loop-hooks] Propagate the exit code from runAsync using Gio.ApplicationError




commit 057e060af70e28b812fdf413f240c3385d82e71f
Author: Evan Welsh <contact evanwelsh com>
Date:   Sat Mar 5 18:03:30 2022 -0800

    Propagate the exit code from runAsync using Gio.ApplicationError

 gjs/console.cpp               |  7 +++++--
 gjs/context.cpp               | 46 ++++++++++++++++++++++++++++++++++++++++---
 gjs/error-types.h             |  1 +
 modules/core/overrides/Gio.js | 21 +++++++++++++++++---
 modules/core/overrides/Gtk.js | 14 +------------
 5 files changed, 68 insertions(+), 21 deletions(-)
---
diff --git a/gjs/console.cpp b/gjs/console.cpp
index 49c822992..856f27513 100644
--- a/gjs/console.cpp
+++ b/gjs/console.cpp
@@ -184,13 +184,16 @@ int define_argv_and_eval_script(GjsContext* js_context, int argc,
             !gjs_context_eval_module(js_context, uri, &code_u8, &error)) {
             code = code_u8;
 
-            if (!g_error_matches(error, GJS_ERROR, GJS_ERROR_SYSTEM_EXIT))
+            if (!g_error_matches(error, GJS_ERROR, GJS_ERROR_SYSTEM_EXIT) &&
+                !g_error_matches(error, GJS_ERROR,
+                                 GJS_ERROR_APPLICATION_EXIT_CODE))
                 g_critical("%s", error->message);
             g_clear_error(&error);
         }
     } else if (!gjs_context_eval(js_context, script, len, filename, &code,
                                  &error)) {
-        if (!g_error_matches(error, GJS_ERROR, GJS_ERROR_SYSTEM_EXIT))
+        if (!g_error_matches(error, GJS_ERROR, GJS_ERROR_SYSTEM_EXIT) &&
+            !g_error_matches(error, GJS_ERROR, GJS_ERROR_APPLICATION_EXIT_CODE))
             g_critical("%s", error->message);
         g_clear_error(&error);
     }
diff --git a/gjs/context.cpp b/gjs/context.cpp
index 9917b2c27..6c22ddd10 100644
--- a/gjs/context.cpp
+++ b/gjs/context.cpp
@@ -621,7 +621,8 @@ GjsContextPrivate::GjsContextPrivate(JSContext* cx, GjsContext* public_context)
       m_cx(cx),
       m_owner_thread(std::this_thread::get_id()),
       m_dispatcher(this),
-      m_environment_preparer(cx) {
+      m_environment_preparer(cx),
+      m_exit_code(0) {
     JS_SetGCCallback(
         cx,
         [](JSContext*, JSGCStatus status, JS::GCReason reason, void* data) {
@@ -1304,6 +1305,34 @@ void GjsContextPrivate::auto_profile_exit(bool auto_profile) {
         gjs_profiler_stop(m_profiler);
 }
 
+[[nodiscard]] static bool error_has_exit_code(JSContext* cx,
+                                              JS::HandleValue thrown_value,
+                                              uint8_t* exit_code) {
+    if (!thrown_value.isObject())
+        return false;
+
+    JS::AutoSaveExceptionState saved_exc(cx);
+    JS::RootedObject exc(cx, &thrown_value.toObject());
+    JS::RootedValue exc_error_code(cx);
+    bool retval = false;
+
+    if (!JS_GetProperty(cx, exc, "exitCode", &exc_error_code))
+        goto out;
+
+    if (!exc_error_code.isNumber())
+        goto out;
+
+    retval = true;
+
+    if (!exit_code)
+        goto out;
+
+    *exit_code = exc_error_code.toNumber();
+out:
+    saved_exc.restore();
+    return retval;
+}
+
 bool GjsContextPrivate::handle_exit_code(bool no_sync_error_pending,
                                          const char* source_type,
                                          const char* identifier,
@@ -1323,6 +1352,17 @@ bool GjsContextPrivate::handle_exit_code(bool no_sync_error_pending,
     // be pending even if the script returned
     // true synchronously
     if (JS_IsExceptionPending(m_cx)) {
+        JS::RootedValue exc(m_cx);
+        if (JS_GetPendingException(m_cx, &exc) &&
+            error_has_exit_code(m_cx, exc, exit_code)) {
+            // If the exception specifies exitCode don't log it as unhandled
+            gjs_log_exception(m_cx);
+
+            g_set_error(error, GJS_ERROR, GJS_ERROR_APPLICATION_EXIT_CODE,
+                        "Exit with code %d", code);
+            return false;
+        }
+
         g_set_error(error, GJS_ERROR, GJS_ERROR_FAILED,
                     "%s %s threw an exception", source_type, identifier);
         gjs_log_exception_uncaught(m_cx);
@@ -1379,7 +1419,7 @@ bool GjsContextPrivate::eval(const char* script, size_t script_len,
 
     /**
      * If there are no errors and the mainloop hook
-     * is set, cal it.
+     * is set, call it.
      */
     if (ok && m_main_loop_hook)
         ok = run_main_loop_hook();
@@ -1484,7 +1524,7 @@ bool GjsContextPrivate::eval_module(const char* identifier,
 
     /**
      * If there are no errors and the mainloop hook
-     * is set, cal it.
+     * is set, call it.
      */
     if (ok && m_main_loop_hook)
         ok = run_main_loop_hook();
diff --git a/gjs/error-types.h b/gjs/error-types.h
index 4c470b5c7..0c69abcdc 100644
--- a/gjs/error-types.h
+++ b/gjs/error-types.h
@@ -25,6 +25,7 @@ GQuark gjs_error_quark(void);
 typedef enum {
     GJS_ERROR_FAILED,
     GJS_ERROR_SYSTEM_EXIT,
+    GJS_ERROR_APPLICATION_EXIT_CODE,
 } GjsError;
 
 GJS_EXPORT
diff --git a/modules/core/overrides/Gio.js b/modules/core/overrides/Gio.js
index 6b382bfc8..fb72e4e9e 100644
--- a/modules/core/overrides/Gio.js
+++ b/modules/core/overrides/Gio.js
@@ -7,6 +7,15 @@ var Signals = imports.signals;
 var {setMainLoopHook} = imports._promiseNative;
 var Gio;
 
+class ApplicationError extends Error {
+    constructor(message, exitCode, options) {
+        super(message, options);
+
+        this.name = 'ApplicationError';
+        this.exitCode = exitCode;
+    }
+}
+
 // Ensures that a Gio.UnixFDList being passed into or out of a DBus method with
 // a parameter type that includes 'h' somewhere, actually has entries in it for
 // each of the indices being passed as an 'h' parameter.
@@ -443,14 +452,20 @@ function _promisify(proto, asyncFunc,
 function _init() {
     Gio = this;
 
+    Gio.ApplicationError = ApplicationError;
+
     Gio.Application.prototype.runAsync = function (...args) {
         return new Promise((resolve, reject) => {
             setMainLoopHook(() => {
                 try {
-                    this.run(...args);
-                    resolve();
+                    const exitCode = this.run(...args);
+
+                    if (exitCode)
+                        reject(new ApplicationError('Application exited with unknown error', exitCode));
+                    else
+                        resolve();
                 } catch (error) {
-                    reject(error);
+                    reject(new ApplicationError('Application exited with error', 1, {cause: error}));
                 }
             });
         });
diff --git a/modules/core/overrides/Gtk.js b/modules/core/overrides/Gtk.js
index 5dfc0e69a..a749669bb 100644
--- a/modules/core/overrides/Gtk.js
+++ b/modules/core/overrides/Gtk.js
@@ -5,7 +5,6 @@
 const Legacy = imports._legacy;
 const {Gio, GjsPrivate, GObject} = imports.gi;
 const {_registerType} = imports._common;
-const {setMainLoopHook} = imports._promiseNative;
 
 let Gtk;
 let BuilderScope;
@@ -13,18 +12,7 @@ let BuilderScope;
 function _init() {
     Gtk = this;
 
-    Gtk.Application.prototype.runAsync = function (...args) {
-        return new Promise((resolve, reject) => {
-            setMainLoopHook(() => {
-                try {
-                    this.run(...args);
-                    resolve();
-                } catch (error) {
-                    reject(error);
-                }
-            });
-        });
-    };
+    Gtk.ApplicationError = Gio.ApplicationError;
 
     Gtk.children = GObject.__gtkChildren__;
     Gtk.cssName = GObject.__gtkCssName__;


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