[gjs/gnome-3-8-js17] object: defer toggle notifications to idle



commit 06ae32c5aaf65bb68a994af211a542b7efb0d1d2
Author: Ray Strode <rstrode redhat com>
Date:   Tue Apr 2 16:43:36 2013 -0400

    object: defer toggle notifications to idle
    
    We don't want to be mucking with javascript from two threads at once.
    This commit defers toggle notification processing to the main
    thread, and introduces a mutex to prevent the garbage collector
    and toggle notifications from running at the same time.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=670200
    (cherry picked from commit 50a3d86790547ca18b0fbca97aed3ed04720d5a5)

 gi/object.c      |  300 ++++++++++++++++++++++++++++++++++++++++++++++++------
 gjs/context.c    |    4 +
 gjs/jsapi-util.c |   26 +++++
 gjs/jsapi-util.h |    4 +
 4 files changed, 302 insertions(+), 32 deletions(-)
---
diff --git a/gi/object.c b/gi/object.c
index 49b417d..6e8f373 100644
--- a/gi/object.c
+++ b/gi/object.c
@@ -65,6 +65,20 @@ typedef struct {
     GClosure *closure;
 } ConnectData;
 
+typedef enum
+{
+  TOGGLE_DOWN,
+  TOGGLE_UP,
+} ToggleDirection;
+
+typedef struct
+{
+    JSContext       *context;
+    GObject         *gobj;
+    ToggleDirection  direction;
+    guint            needs_unref : 1;
+} ToggleRefNotifyOperation;
+
 enum {
     PROP_0,
     PROP_JS_CONTEXT,
@@ -128,6 +142,26 @@ gjs_object_priv_quark (void)
     return val;
 }
 
+static GQuark
+gjs_toggle_down_quark (void)
+{
+    static GQuark val = 0;
+    if (G_UNLIKELY (!val))
+        val = g_quark_from_static_string ("gjs::toggle-down-quark");
+
+    return val;
+}
+
+static GQuark
+gjs_toggle_up_quark (void)
+{
+    static GQuark val = 0;
+    if (G_UNLIKELY (!val))
+        val = g_quark_from_static_string ("gjs::toggle-up-quark");
+
+    return val;
+}
+
 /* Plain g_type_query fails and leaves @query uninitialized for
    dynamic types.
    See https://bugzilla.gnome.org/show_bug.cgi?id=687184 and
@@ -765,6 +799,216 @@ gobj_no_longer_kept_alive_func(JSObject *obj,
     priv->keep_alive = NULL;
 }
 
+static GQuark
+get_qdata_key_for_toggle_direction(ToggleDirection direction)
+{
+    GQuark quark;
+
+    switch (direction) {
+        case TOGGLE_UP:
+            quark = gjs_toggle_up_quark();
+            break;
+        case TOGGLE_DOWN:
+            quark = gjs_toggle_down_quark();
+            break;
+        default:
+            g_assert_not_reached();
+    }
+
+    return quark;
+}
+
+static gboolean
+clear_toggle_idle_source(GObject          *gobj,
+                         ToggleDirection   direction)
+{
+    GQuark qdata_key;
+
+    qdata_key = get_qdata_key_for_toggle_direction(direction);
+
+    return g_object_steal_qdata(gobj, qdata_key) != NULL;
+}
+
+static gboolean
+cancel_toggle_idle(GObject         *gobj,
+                   ToggleDirection  direction)
+{
+    GQuark qdata_key;
+    GSource *source;
+
+    qdata_key = get_qdata_key_for_toggle_direction(direction);
+
+    source = g_object_steal_qdata(gobj, qdata_key);
+
+    if (source)
+        g_source_destroy(source);
+
+    return source != 0;
+}
+
+static void
+handle_toggle_down(JSContext *context,
+                   GObject   *gobj)
+{
+    ObjectInstance *priv;
+    JSObject *obj;
+
+    obj = peek_js_obj(context, gobj);
+
+    priv = priv_from_js(context, obj);
+
+    gjs_debug_lifecycle(GJS_DEBUG_GOBJECT,
+                        "Toggle notify gobj %p obj %p is_last_ref TRUE keep-alive %p",
+                        gobj, obj, priv->keep_alive);
+
+    /* Change to weak ref so the wrapper-wrappee pair can be
+     * collected by the GC
+     */
+    if (priv->keep_alive != NULL) {
+        gjs_debug_lifecycle(GJS_DEBUG_GOBJECT, "Removing object from keep alive");
+        gjs_keep_alive_remove_child(context, priv->keep_alive,
+                                    gobj_no_longer_kept_alive_func,
+                                    obj,
+                                    priv);
+        priv->keep_alive = NULL;
+    }
+}
+
+static void
+handle_toggle_up(JSContext *context,
+                 GObject   *gobj)
+{
+    ObjectInstance *priv;
+    JSObject *obj;
+
+    /* We need to root the JSObject associated with the passed in GObject so it
+     * doesn't get garbage collected (and lose any associated javascript state
+     * such as custom properties).
+     *
+     * Note it's possible that the garbage collector is running in a secondary
+     * thread right now. If it is, we need to wait for it to finish, then block
+     * it from starting again while we root the object. After it's blocked we need
+     * to check if the associated JSObject was reaped. If it was we need to
+     * abort mission.
+     */
+    gjs_block_gc();
+
+    obj = peek_js_obj(context, gobj);
+
+    if (!obj) {
+        /* Object already GC'd */
+        goto out;
+    }
+
+    priv = priv_from_js(context, obj);
+
+    gjs_debug_lifecycle(GJS_DEBUG_GOBJECT,
+                        "Toggle notify gobj %p obj %p is_last_ref FALSEd keep-alive %p",
+                        gobj, obj, priv->keep_alive);
+
+    /* Change to strong ref so the wrappee keeps the wrapper alive
+     * in case the wrapper has data in it that the app cares about
+     */
+    if (priv->keep_alive == NULL) {
+        gjs_debug_lifecycle(GJS_DEBUG_GOBJECT, "Adding object to keep alive");
+        priv->keep_alive = gjs_keep_alive_get_for_import_global(context);
+        gjs_keep_alive_add_child(context, priv->keep_alive,
+                                 gobj_no_longer_kept_alive_func,
+                                 obj,
+                                 priv);
+    }
+
+out:
+    gjs_unblock_gc();
+}
+
+static gboolean
+idle_handle_toggle(gpointer data)
+{
+    ToggleRefNotifyOperation *operation = data;
+
+    if (!clear_toggle_idle_source(operation->gobj, operation->direction)) {
+        /* Already cleared, the JSObject is going away, abort mission */
+        goto out;
+    }
+
+    switch (operation->direction) {
+        case TOGGLE_UP:
+            handle_toggle_up(operation->context, operation->gobj);
+            break;
+        case TOGGLE_DOWN:
+            handle_toggle_down(operation->context, operation->gobj);
+            break;
+        default:
+            g_assert_not_reached();
+    }
+
+out:
+    return FALSE;
+}
+
+static void
+toggle_ref_notify_operation_free(ToggleRefNotifyOperation *operation)
+{
+    if (operation->needs_unref)
+        g_object_unref (operation->gobj);
+    g_slice_free(ToggleRefNotifyOperation, operation);
+}
+
+static void
+queue_toggle_idle(GObject         *gobj,
+                  JSContext       *context,
+                  ToggleDirection  direction)
+{
+    ToggleRefNotifyOperation *operation;
+    GQuark qdata_key;
+    GSource *source;
+
+    operation = g_slice_new0(ToggleRefNotifyOperation);
+    operation->context = context;
+    operation->direction = direction;
+
+    switch (direction) {
+        case TOGGLE_UP:
+            /* If we're toggling up we take a reference to the object now,
+             * so it won't toggle down before we process it. This ensures we
+             * only ever have at most two toggle notifications queued.
+             * (either only up, or down-up)
+             */
+            operation->gobj = g_object_ref(gobj);
+            operation->needs_unref = TRUE;
+            break;
+        case TOGGLE_DOWN:
+            /* If we're toggling down, we don't need to take a reference since
+             * the associated JSObject already has one, and that JSObject won't
+             * get finalized until we've completed toggling (since it's rooted,
+             * until we unroot it when we dispatch the toggle down idle).
+             *
+             * Taking a reference now would be bad anyway, since it would force
+             * the object to toggle back up again.
+             */
+            operation->gobj = gobj;
+            break;
+        default:
+            g_assert_not_reached();
+    }
+
+    qdata_key = get_qdata_key_for_toggle_direction(direction);
+
+    source = g_idle_source_new();
+    g_source_set_priority(source, G_PRIORITY_HIGH);
+    g_source_set_callback(source,
+                          idle_handle_toggle,
+                          operation,
+                          (GDestroyNotify) toggle_ref_notify_operation_free);
+
+    g_object_set_qdata (gobj, qdata_key, source);
+    g_source_attach (source, NULL);
+
+    /* object qdata is piggy-backing off the main loop's ref of the source */
+    g_source_unref (source);
+}
+
 static void
 wrapped_gobj_toggle_notify(gpointer      data,
                            GObject      *gobj,
@@ -772,8 +1016,6 @@ wrapped_gobj_toggle_notify(gpointer      data,
 {
     JSRuntime *runtime;
     JSContext *context;
-    JSObject *obj;
-    ObjectInstance *priv;
 
     runtime = data;
 
@@ -787,40 +1029,25 @@ wrapped_gobj_toggle_notify(gpointer      data,
     if (!context)
         return;
 
-    obj = peek_js_obj(context, gobj);
-
-    g_assert(obj != NULL);
-
-    priv = priv_from_js(context, obj);
-
-    gjs_debug_lifecycle(GJS_DEBUG_GOBJECT,
-                        "Toggle notify gobj %p obj %p is_last_ref %d keep-alive %p",
-                        gobj, obj, is_last_ref, priv->keep_alive);
-
+    /* We only want to touch javascript from one thread.
+     * If we're not in that thread, then we need to defer processing
+     * to it. We also don't want to touch javascript if a GC is going
+     * on in the same thread as us.
+     * For simplicity, we always defer to idle
+     */
     if (is_last_ref) {
-        /* Change to weak ref so the wrapper-wrappee pair can be
-         * collected by the GC
+        /* We've transitions from 2 -> 1 references,
+         * The JSObject is rooted and we need to unroot it so it
+         * can be garbage collected
          */
-        if (priv->keep_alive != NULL) {
-            gjs_debug_lifecycle(GJS_DEBUG_GOBJECT, "Removing object from keep alive");
-            gjs_keep_alive_remove_child(context, priv->keep_alive,
-                                        gobj_no_longer_kept_alive_func,
-                                        obj,
-                                        priv);
-            priv->keep_alive = NULL;
-        }
+        queue_toggle_idle(gobj, context, TOGGLE_DOWN);
     } else {
-        /* Change to strong ref so the wrappee keeps the wrapper alive
-         * in case the wrapper has data in it that the app cares about
+        /* We've transitioned from 1 -> 2 references.
+         *
+         * The JSObject associated with the gobject is not rooted,
+         * but it needs to be. we'll root it on idle.
          */
-        if (priv->keep_alive == NULL) {
-            gjs_debug_lifecycle(GJS_DEBUG_GOBJECT, "Adding object to keep alive");
-            priv->keep_alive = gjs_keep_alive_get_for_import_global(context);
-            gjs_keep_alive_add_child(context, priv->keep_alive,
-                                     gobj_no_longer_kept_alive_func,
-                                     obj,
-                                     priv);
-        }
+        queue_toggle_idle(gobj, context, TOGGLE_UP);
     }
 }
 
@@ -1062,6 +1289,15 @@ object_instance_finalize(JSContext *context,
                     priv->info ? g_base_info_get_namespace((GIBaseInfo*) priv->info) : "",
                     priv->info ? g_base_info_get_name((GIBaseInfo*) priv->info) : g_type_name(priv->gtype));
         }
+
+        cancel_toggle_idle(priv->gobj, TOGGLE_UP);
+
+        if (G_UNLIKELY (cancel_toggle_idle(priv->gobj, TOGGLE_DOWN))) {
+            g_error("Finalizing proxy for an object that's scheduled to be unrooted: %s.%s\n",
+                    priv->info ? g_base_info_get_namespace((GIBaseInfo*) priv->info) : "",
+                    priv->info ? g_base_info_get_name((GIBaseInfo*) priv->info) : g_type_name(priv->gtype));
+        }
+
         set_js_obj(context, priv->gobj, NULL);
         g_object_remove_toggle_ref(priv->gobj, wrapped_gobj_toggle_notify,
                                    JS_GetRuntime(context));
diff --git a/gjs/context.c b/gjs/context.c
index a77c678..5c40ecc 100644
--- a/gjs/context.c
+++ b/gjs/context.c
@@ -895,7 +895,11 @@ gjs_on_context_gc (JSRuntime *rt,
     GjsContext *gjs_context = JS_GetContextPrivate(context);
 
     switch (status) {
+        case JSGC_BEGIN:
+            gjs_enter_gc();
+            break;
         case JSGC_END:
+            gjs_leave_gc();
             if (gjs_context->gc_notifications_enabled) {
                 g_mutex_lock(&gc_idle_lock);
                 if (gjs_context->idle_emit_gc_id == 0)
diff --git a/gjs/jsapi-util.c b/gjs/jsapi-util.c
index f97b35e..4a8c34c 100644
--- a/gjs/jsapi-util.c
+++ b/gjs/jsapi-util.c
@@ -35,6 +35,8 @@
 #include <string.h>
 #include <math.h>
 
+static GMutex gc_lock;
+
 GQuark
 gjs_util_error_quark (void)
 {
@@ -1201,3 +1203,27 @@ gjs_maybe_gc (JSContext *context)
     }
 #endif
 }
+
+void
+gjs_enter_gc(void)
+{
+    g_mutex_lock(&gc_lock);
+}
+
+void
+gjs_leave_gc(void)
+{
+    g_mutex_unlock(&gc_lock);
+}
+
+void
+gjs_block_gc(void)
+{
+    g_mutex_lock(&gc_lock);
+}
+
+void
+gjs_unblock_gc(void)
+{
+    g_mutex_unlock(&gc_lock);
+}
diff --git a/gjs/jsapi-util.h b/gjs/jsapi-util.h
index ce72963..926d8d8 100644
--- a/gjs/jsapi-util.h
+++ b/gjs/jsapi-util.h
@@ -355,6 +355,10 @@ void              gjs_unroot_value_locations  (JSContext        *context,
 /* Functions intended for more "internal" use */
 
 void gjs_maybe_gc (JSContext *context);
+void gjs_enter_gc (void);
+void gjs_leave_gc (void);
+void gjs_block_gc (void);
+void gjs_unblock_gc (void);
 
 G_END_DECLS
 


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