diff options
author | Philip Chimento <philip.chimento@gmail.com> | 2018-04-18 05:25:55 +0000 |
---|---|---|
committer | Philip Chimento <philip.chimento@gmail.com> | 2018-04-18 05:25:55 +0000 |
commit | a6b6fc1342b76e6c536483a655f3c54dd8d27653 (patch) | |
tree | ff46df00205637e33b9671861a911ed53a524140 | |
parent | ead6462582e77ea9b312cde9bcdd73e692da232f (diff) | |
parent | 72d970b4d199982979bb879c409f4c08619798e4 (diff) | |
download | gjs-a6b6fc1342b76e6c536483a655f3c54dd8d27653.tar.gz |
Merge branch 'wip/gcampax/679688-make-gc-much-more-aggressive' into 'master'
object: don't use toggle references unless necessary
Closes #62
See merge request GNOME/gjs!50
-rw-r--r-- | gi/object.cpp | 83 |
1 files changed, 64 insertions, 19 deletions
diff --git a/gi/object.cpp b/gi/object.cpp index b20d8b90..3fdfcedd 100644 --- a/gi/object.cpp +++ b/gi/object.cpp @@ -70,6 +70,11 @@ struct ObjectInstance { unsigned js_object_finalized : 1; unsigned g_object_finalized : 1; + + /* True if this object has visible JS state, and thus its lifecycle is + * managed using toggle references. False if this object just keeps a + * hard ref on the underlying GObject, and may be finalized at will. */ + bool uses_toggle_ref : 1; }; static std::stack<JS::PersistentRootedObject> object_init_list; @@ -85,6 +90,7 @@ extern struct JSClass gjs_object_instance_class; GJS_DEFINE_PRIV_FROM_JS(ObjectInstance, gjs_object_instance_class) static void disassociate_js_gobject (GObject *gobj); +static void ensure_uses_toggle_ref(JSContext *cx, ObjectInstance *priv); typedef enum { SOME_ERROR_OCCURRED = false, @@ -152,7 +158,7 @@ get_object_qdata(GObject *gobj) auto priv = static_cast<ObjectInstance *>(g_object_get_qdata(gobj, gjs_object_priv_quark())); - if (priv && G_UNLIKELY(priv->js_object_finalized)) { + if (priv && priv->uses_toggle_ref && G_UNLIKELY(priv->js_object_finalized)) { g_critical("Object %p (a %s) resurfaced after the JS wrapper was finalized. " "This is some library doing dubious memory management inside dispose()", gobj, g_type_name(G_TYPE_FROM_INSTANCE(gobj))); @@ -430,6 +436,9 @@ set_g_param_from_prop(JSContext *context, case SOME_ERROR_OCCURRED: return false; case NO_SUCH_G_PROPERTY: + /* We need to keep the wrapper alive in order not to lose custom + * "expando" properties */ + ensure_uses_toggle_ref(context, priv); return result.succeed(); case VALUE_WAS_SET: default: @@ -496,14 +505,7 @@ object_instance_set_prop(JSContext *context, bool ret = true; bool g_param_was_set = false; - if (!gjs_get_string_id(context, id, &name)) - return result.succeed(); /* not resolved, but no error */ - priv = priv_from_js(context, obj); - gjs_debug_jsprop(GJS_DEBUG_GOBJECT, - "Set prop '%s' hook obj %p priv %p", - name.get(), obj.get(), priv); - if (priv == nullptr) /* see the comment in object_instance_get_prop() on this */ return result.succeed(); @@ -521,6 +523,18 @@ object_instance_set_prop(JSContext *context, return result.succeed(); } + if (!gjs_get_string_id(context, id, &name)) { + /* We need to keep the wrapper alive in order not to lose custom + * "expando" properties. In this case if gjs_get_string_id() is false + * then a number or symbol property was probably set. */ + ensure_uses_toggle_ref(context, priv); + return result.succeed(); /* not resolved, but no error */ + } + + gjs_debug_jsprop(GJS_DEBUG_GOBJECT, + "Set prop '%s' hook obj %p priv %p", + name.get(), obj.get(), priv); + ret = set_g_param_from_prop(context, priv, name, g_param_was_set, value_p, result); if (g_param_was_set || !ret) return ret; @@ -1126,7 +1140,10 @@ static void release_native_object (ObjectInstance *priv) { priv->keep_alive.reset(); - g_object_remove_toggle_ref(priv->gobj, wrapped_gobj_toggle_notify, NULL); + if (priv->uses_toggle_ref) + g_object_remove_toggle_ref(priv->gobj, wrapped_gobj_toggle_notify, nullptr); + else + g_object_unref(priv->gobj); priv->gobj = NULL; } @@ -1256,16 +1273,28 @@ associate_js_gobject (JSContext *context, ObjectInstance *priv; priv = priv_from_js(context, object); + priv->uses_toggle_ref = false; priv->gobj = gobj; g_assert(!priv->keep_alive.rooted()); set_object_qdata(gobj, priv); + priv->keep_alive = object; ensure_weak_pointer_callback(context); wrapped_gobject_list.insert(priv); g_object_weak_ref(gobj, wrapped_gobj_dispose_notify, priv); +} + +static void +ensure_uses_toggle_ref(JSContext *cx, + ObjectInstance *priv) +{ + if (priv->uses_toggle_ref) + return; + + g_assert(!priv->keep_alive.rooted()); /* OK, here is where things get complicated. We want the * wrapped gobj to keep the JSObject* wrapper alive, because @@ -1278,8 +1307,14 @@ associate_js_gobject (JSContext *context, * the wrapper to be garbage collected (and thus unref the * wrappee). */ - priv->keep_alive.root(context, object, gobj_no_longer_kept_alive_func, priv); - g_object_add_toggle_ref(gobj, wrapped_gobj_toggle_notify, NULL); + priv->uses_toggle_ref = true; + priv->keep_alive.switch_to_rooted(cx, gobj_no_longer_kept_alive_func, priv); + g_object_add_toggle_ref(priv->gobj, wrapped_gobj_toggle_notify, nullptr); + + /* We now have both a ref and a toggle ref, we only want the toggle ref. + * This may immediately remove the GC root we just added, since refcount + * may drop to 1. */ + g_object_unref(priv->gobj); } static void @@ -1322,11 +1357,16 @@ disassociate_js_gobject(GObject *gobj) gobj, G_OBJECT_TYPE_NAME(gobj)); } + /* Fist, remove the wrapper pointer from the wrapped GObject */ + set_object_qdata(gobj, nullptr); + + /* Now release all the resources the current wrapper has */ invalidate_all_closures(priv); release_native_object(priv); /* Mark that a JS object once existed, but it doesn't any more */ priv->js_object_finalized = true; + priv->keep_alive = nullptr; } static void @@ -1383,6 +1423,7 @@ G_GNUC_END_IGNORE_DEPRECATIONS * we're not actually using it, so just let it get collected. Avoiding * this would require a non-trivial amount of work. * */ + ensure_uses_toggle_ref(context, other_priv); object.set(other_priv->keep_alive); g_object_unref(gobj); /* We already own a reference */ gobj = NULL; @@ -1410,11 +1451,6 @@ G_GNUC_END_IGNORE_DEPRECATIONS if (priv->gobj == NULL) associate_js_gobject(context, object, gobj); - /* We now have both a ref and a toggle ref, we only want the - * toggle ref. This may immediately remove the GC root - * we just added, since refcount may drop to 1. - */ - g_object_unref(gobj); gjs_debug_lifecycle(GJS_DEBUG_GOBJECT, "JSObject created with GObject %p (%s)", priv->gobj, G_OBJECT_TYPE_NAME(priv->gobj)); @@ -1556,6 +1592,9 @@ object_instance_finalize(JSFreeOp *fop, GJS_DEC_COUNTER(object); priv->~ObjectInstance(); g_slice_free(ObjectInstance, priv); + + /* Remove the ObjectInstance pointer from the JSObject */ + JS_SetPrivate(obj, nullptr); } static JSObject * @@ -1683,6 +1722,8 @@ real_connect_func(JSContext *context, return true; } + ensure_uses_toggle_ref(context, priv); + if (argc != 2 || !argv[0].isString() || !JS::IsCallable(&argv[1].toObject())) { gjs_throw(context, "connect() takes two args, the signal name and the callback"); return false; @@ -2123,9 +2164,6 @@ gjs_object_from_g_object(JSContext *context, g_object_ref_sink(gobj); associate_js_gobject(context, obj, gobj); - /* see the comment in init_object_instance() for this */ - g_object_unref(gobj); - g_assert(priv->keep_alive == obj.get()); } @@ -2657,6 +2695,10 @@ gjs_object_custom_init(GTypeInstance *instance, associate_js_gobject(context, object, G_OBJECT (instance)); + /* Custom JS objects will most likely have visible state, so + * just do this from the start */ + ensure_uses_toggle_ref(context, priv); + JS::RootedValue v(context); if (!gjs_object_get_property(context, object, GJS_STRING_INSTANCE_INIT, &v)) { @@ -3109,6 +3151,9 @@ gjs_object_associate_closure(JSContext *cx, if (!priv) return false; + if (priv->gobj) + ensure_uses_toggle_ref(cx, priv); + do_associate_closure(priv, closure); return true; } |