diff --git a/gi/object.cpp b/gi/object.cpp index 3fdfceddb47f86ced8ac6e5bc8c7287497f36eda..606a91899a07ac7948be77199fad4a759eecfe56 100644 --- a/gi/object.cpp +++ b/gi/object.cpp @@ -1001,8 +1001,30 @@ handle_toggle_down(GObject *gobj) * collected by the GC */ if (priv->keep_alive.rooted()) { + GjsContext *context; + gjs_debug_lifecycle(GJS_DEBUG_GOBJECT, "Unrooting object"); priv->keep_alive.switch_to_unrooted(); + + /* During a GC, the collector asks each object which other + * objects that it wants to hold on to so if there's an entire + * section of the heap graph that's not connected to anything + * else, and not reachable from the root set, then it can be + * trashed all at once. + * + * GObjects, however, don't work like that, there's only a + * reference count but no notion of who owns the reference so, + * a JS object that's proxying a GObject is unconditionally held + * alive as long as the GObject has >1 references. + * + * Since we cannot know how many more wrapped GObjects are going + * be marked for garbage collection after the owner is destroyed, + * always queue a garbage collection when a toggle reference goes + * down. + */ + context = gjs_context_get_current(); + if (!_gjs_context_destroying(context)) + _gjs_context_schedule_gc(context); } } diff --git a/gjs/context-private.h b/gjs/context-private.h index 6dbe6690b60682874b50dceed3d3c9d06a43a7e8..49c0cf9e1557ff346cc7976fd902bb1b2d23534c 100644 --- a/gjs/context-private.h +++ b/gjs/context-private.h @@ -36,6 +36,8 @@ bool _gjs_context_destroying (GjsContext *js_context); void _gjs_context_schedule_gc_if_needed (GjsContext *js_context); +void _gjs_context_schedule_gc(GjsContext *js_context); + void _gjs_context_exit(GjsContext *js_context, uint8_t exit_code); diff --git a/gjs/context.cpp b/gjs/context.cpp index c509943bd71328aaedb59100e699ecaad94a1617..a2ce34ae3ba7e30f71e2d719e764c1a0827243c5 100644 --- a/gjs/context.cpp +++ b/gjs/context.cpp @@ -90,6 +90,7 @@ struct _GjsContext { uint8_t exit_code; guint auto_gc_id; + bool force_gc; std::array const_strings; @@ -592,21 +593,43 @@ trigger_gc_if_needed (gpointer user_data) { GjsContext *js_context = GJS_CONTEXT(user_data); js_context->auto_gc_id = 0; - gjs_gc_if_needed(js_context->context); + + if (js_context->force_gc) + JS_GC(js_context->context); + else + gjs_gc_if_needed(js_context->context); + + js_context->force_gc = false; + return G_SOURCE_REMOVE; } -void -_gjs_context_schedule_gc_if_needed (GjsContext *js_context) + +static void +_gjs_context_schedule_gc_internal(GjsContext *js_context, + bool force_gc) { if (js_context->auto_gc_id > 0) return; + js_context->force_gc |= force_gc; js_context->auto_gc_id = g_idle_add_full(G_PRIORITY_LOW, trigger_gc_if_needed, js_context, NULL); } +void +_gjs_context_schedule_gc(GjsContext *js_context) +{ + _gjs_context_schedule_gc_internal(js_context, true); +} + +void +_gjs_context_schedule_gc_if_needed(GjsContext *js_context) +{ + _gjs_context_schedule_gc_internal(js_context, false); +} + void _gjs_context_exit(GjsContext *js_context, uint8_t exit_code)