[gjs] Trace signal closures from the object instead of the context keep-alive
- From: Giovanni Campagna <gcampagna src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gjs] Trace signal closures from the object instead of the context keep-alive
- Date: Mon, 25 Jun 2012 08:25:37 +0000 (UTC)
commit 06aa616a8c9b6d356aefc95a6b0c1c317b86c46a
Author: Giovanni Campagna <gcampagna src gnome org>
Date: Wed Jun 20 22:30:52 2012 +0200
Trace signal closures from the object instead of the context keep-alive
Implement tracing for GObjects and use it so that signal handlers
are rooted to the objects they're connected, and not the global
object. This means that if the object goes away and the only thing
referring to it is the handler function, it is recognized as a
cycle by the GC and collected, reducing memory leaks.
Other closures, as well as callback trampolines and vfuncs, are still
rooted in the usual way.
https://bugzilla.gnome.org/show_bug.cgi?id=678504
gi/closure.c | 48 +++++++++++++++++++++++++++-----
gi/closure.h | 6 +++-
gi/object.c | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++-
gi/value.c | 4 +-
modules/dbus.c | 13 +++++----
modules/mainloop.c | 6 ++--
6 files changed, 132 insertions(+), 22 deletions(-)
---
diff --git a/gi/closure.c b/gi/closure.c
index 873c28c..96a7477 100644
--- a/gi/closure.c
+++ b/gi/closure.c
@@ -230,6 +230,17 @@ closure_invalidated(gpointer data,
}
static void
+closure_set_invalid(gpointer data,
+ GClosure *closure)
+{
+ Closure *self = (Closure*) closure;
+
+ self->obj = NULL;
+ self->context = NULL;
+ self->runtime = NULL;
+}
+
+static void
closure_finalized(gpointer data,
GClosure *closure)
{
@@ -317,10 +328,25 @@ gjs_closure_get_callable(GClosure *closure)
return c->obj;
}
+void
+gjs_closure_trace(GClosure *closure,
+ JSTracer *tracer)
+{
+ Closure *c;
+
+ c = (Closure*) closure;
+
+ if (c->obj == NULL)
+ return;
+
+ JS_CALL_OBJECT_TRACER(tracer, c->obj, "signal connection");
+}
+
GClosure*
gjs_closure_new(JSContext *context,
- JSObject *callable,
- const char *description)
+ JSObject *callable,
+ const char *description,
+ gboolean root_function)
{
Closure *c;
@@ -343,12 +369,19 @@ gjs_closure_new(JSContext *context,
*/
g_closure_add_finalize_notifier(&c->base, NULL, closure_finalized);
- gjs_keep_alive_add_global_child(context,
- global_context_finalized,
- c->obj,
- c);
+ if (root_function) {
+ /* Fully manage closure lifetime if so asked */
+ gjs_keep_alive_add_global_child(context,
+ global_context_finalized,
+ c->obj,
+ c);
- g_closure_add_invalidate_notifier(&c->base, NULL, closure_invalidated);
+ g_closure_add_invalidate_notifier(&c->base, NULL, closure_invalidated);
+ } else {
+ /* Only mark the closure as invalid if memory is managed
+ outside (i.e. by object.c for signals) */
+ g_closure_add_invalidate_notifier(&c->base, NULL, closure_set_invalid);
+ }
gjs_debug_closure("Create closure %p which calls object %p '%s'",
c, c->obj, description);
@@ -357,4 +390,3 @@ gjs_closure_new(JSContext *context,
return &c->base;
}
-
diff --git a/gi/closure.h b/gi/closure.h
index c8a821b..f894805 100644
--- a/gi/closure.h
+++ b/gi/closure.h
@@ -32,7 +32,8 @@ G_BEGIN_DECLS
GClosure* gjs_closure_new (JSContext *context,
JSObject *callable,
- const char *description);
+ const char *description,
+ gboolean root_function);
void gjs_closure_invoke (GClosure *closure,
int argc,
jsval *argv,
@@ -41,6 +42,9 @@ JSRuntime* gjs_closure_get_runtime (GClosure *closure);
gboolean gjs_closure_is_valid (GClosure *closure);
JSObject* gjs_closure_get_callable (GClosure *closure);
+void gjs_closure_trace (GClosure *closure,
+ JSTracer *tracer);
+
G_END_DECLS
#endif /* __GJS_CLOSURE_H__ */
diff --git a/gi/object.c b/gi/object.c
index 95109db..fead7cd 100644
--- a/gi/object.c
+++ b/gi/object.c
@@ -34,6 +34,7 @@
#include "param.h"
#include "value.h"
#include "keep-alive.h"
+#include "closure.h"
#include "gjs_gi_trace.h"
#include <gjs/gjs-module.h>
@@ -50,8 +51,17 @@ typedef struct {
GObject *gobj; /* NULL if we are the prototype and not an instance */
JSObject *keep_alive; /* NULL if we are not added to it */
GType gtype;
+
+ /* a list of all signal connections, used when tracing */
+ GList *signals;
} ObjectInstance;
+typedef struct {
+ ObjectInstance *obj;
+ GList *link;
+ GClosure *closure;
+} ConnectData;
+
enum {
PROP_0,
PROP_JS_CONTEXT,
@@ -1011,6 +1021,39 @@ GJS_NATIVE_CONSTRUCTOR_DECLARE(object_instance)
}
static void
+invalidate_all_signals(ObjectInstance *priv)
+{
+ GList *iter, *next;
+
+ for (iter = priv->signals; iter; ) {
+ ConnectData *cd = iter->data;
+ next = iter->next;
+
+ /* This will also free cd and iter, through
+ the closure invalidation mechanism */
+ g_closure_invalidate(cd->closure);
+
+ iter = next;
+ }
+}
+
+static void
+object_instance_trace(JSTracer *tracer,
+ JSObject *obj)
+{
+ ObjectInstance *priv;
+ GList *iter;
+
+ priv = priv_from_js(tracer->context, obj);
+
+ for (iter = priv->signals; iter; iter = iter->next) {
+ ConnectData *cd = iter->data;
+
+ gjs_closure_trace(cd->closure, tracer);
+ }
+}
+
+static void
object_instance_finalize(JSContext *context,
JSObject *obj)
{
@@ -1031,6 +1074,8 @@ object_instance_finalize(JSContext *context,
priv->info ? g_base_info_get_name ( (GIBaseInfo*) priv->info) : g_type_name(priv->gtype)));
if (priv->gobj) {
+ invalidate_all_signals (priv);
+
if (G_UNLIKELY (priv->gobj->ref_count <= 0)) {
g_error("Finalizing proxy for an already freed object of type: %s.%s\n",
priv->info ? g_base_info_get_namespace((GIBaseInfo*) priv->info) : "",
@@ -1084,6 +1129,17 @@ gjs_lookup_object_prototype(JSContext *context,
return proto;
}
+static void
+signal_connection_invalidated (gpointer user_data,
+ GClosure *closure)
+{
+ ConnectData *connect_data = user_data;
+
+ connect_data->obj->signals = g_list_delete_link(connect_data->obj->signals,
+ connect_data->link);
+ g_slice_free(ConnectData, connect_data);
+}
+
static JSBool
real_connect_func(JSContext *context,
uintN argc,
@@ -1099,6 +1155,7 @@ real_connect_func(JSContext *context,
char *signal_name;
GQuark signal_detail;
jsval retval;
+ ConnectData *connect_data;
JSBool ret = JS_FALSE;
priv = priv_from_js(context, obj);
@@ -1148,6 +1205,14 @@ real_connect_func(JSContext *context,
if (closure == NULL)
goto out;
+ connect_data = g_slice_new(ConnectData);
+ priv->signals = g_list_prepend(priv->signals, connect_data);
+ connect_data->obj = priv;
+ connect_data->link = priv->signals;
+ /* This is a weak reference, and will be cleared when the closure is invalidated */
+ connect_data->closure = closure;
+ g_closure_add_invalidate_notifier(closure, connect_data, signal_connection_invalidated);
+
id = g_signal_connect_closure(priv->gobj,
signal_name,
closure,
@@ -1392,7 +1457,8 @@ static struct JSClass gjs_object_instance_class = {
NULL, /* We copy this class struct with multiple names */
JSCLASS_HAS_PRIVATE |
JSCLASS_NEW_RESOLVE |
- JSCLASS_NEW_RESOLVE_GETS_START,
+ JSCLASS_NEW_RESOLVE_GETS_START |
+ JSCLASS_MARK_IS_TRACE,
JS_PropertyStub,
JS_PropertyStub,
object_instance_get_prop,
@@ -1401,7 +1467,14 @@ static struct JSClass gjs_object_instance_class = {
(JSResolveOp) object_instance_new_resolve, /* needs cast since it's the new resolve signature */
JS_ConvertStub,
object_instance_finalize,
- JSCLASS_NO_OPTIONAL_MEMBERS
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ JS_CLASS_TRACE(object_instance_trace),
+ NULL,
};
static JSBool
diff --git a/gi/value.c b/gi/value.c
index 4d02827..5d8fe16 100644
--- a/gi/value.c
+++ b/gi/value.c
@@ -154,7 +154,7 @@ gjs_closure_new_for_signal(JSContext *context,
{
GClosure *closure;
- closure = gjs_closure_new(context, callable, description);
+ closure = gjs_closure_new(context, callable, description, FALSE);
g_closure_set_meta_marshal(closure, GUINT_TO_POINTER(signal_id), closure_marshal);
@@ -168,7 +168,7 @@ gjs_closure_new_marshaled (JSContext *context,
{
GClosure *closure;
- closure = gjs_closure_new(context, callable, description);
+ closure = gjs_closure_new(context, callable, description, TRUE);
g_closure_set_marshal(closure, closure_marshal);
diff --git a/modules/dbus.c b/modules/dbus.c
index 92bfd2e..3a62025 100644
--- a/modules/dbus.c
+++ b/modules/dbus.c
@@ -429,7 +429,7 @@ gjs_js_dbus_call_async(JSContext *context,
* and deal with the GC root and other issues, even though we
* won't ever marshal via GValue
*/
- closure = gjs_closure_new(context, JSVAL_TO_OBJECT(argv[9]), "async call");
+ closure = gjs_closure_new(context, JSVAL_TO_OBJECT(argv[9]), "async call", TRUE);
if (closure == NULL) {
dbus_pending_call_unref(pending);
return JS_FALSE;
@@ -505,7 +505,8 @@ signal_handler_new(JSContext *context,
*/
handler->closure = gjs_closure_new(context,
JSVAL_TO_OBJECT(callable),
- "signal watch");
+ "signal watch",
+ TRUE);
if (handler->closure == NULL) {
g_free(handler);
return NULL;
@@ -1208,13 +1209,13 @@ gjs_js_dbus_acquire_name(JSContext *context,
owner->bus_type = bus_type;
owner->acquired_closure =
- gjs_closure_new(context, acquire_func, "acquired bus name");
+ gjs_closure_new(context, acquire_func, "acquired bus name", TRUE);
g_closure_ref(owner->acquired_closure);
g_closure_sink(owner->acquired_closure);
owner->lost_closure =
- gjs_closure_new(context, lost_func, "lost bus name");
+ gjs_closure_new(context, lost_func, "lost bus name", TRUE);
g_closure_ref(owner->lost_closure);
g_closure_sink(owner->lost_closure);
@@ -1440,13 +1441,13 @@ gjs_js_dbus_watch_name(JSContext *context,
watcher = g_slice_new0(GjsJSDBusNameWatcher);
watcher->appeared_closure =
- gjs_closure_new(context, appeared_func, "service appeared");
+ gjs_closure_new(context, appeared_func, "service appeared", TRUE);
g_closure_ref(watcher->appeared_closure);
g_closure_sink(watcher->appeared_closure);
watcher->vanished_closure =
- gjs_closure_new(context, vanished_func, "service vanished");
+ gjs_closure_new(context, vanished_func, "service vanished", TRUE);
g_closure_ref(watcher->vanished_closure);
g_closure_sink(watcher->vanished_closure);
diff --git a/modules/mainloop.c b/modules/mainloop.c
index 02b53c7..67912b2 100644
--- a/modules/mainloop.c
+++ b/modules/mainloop.c
@@ -198,7 +198,7 @@ gjs_timeout_add(JSContext *context,
"interval", &interval, "callback", &callback))
return JS_FALSE;
- closure = gjs_closure_new(context, callback, "timeout");
+ closure = gjs_closure_new(context, callback, "timeout", TRUE);
if (closure == NULL)
return JS_FALSE;
@@ -241,7 +241,7 @@ gjs_timeout_add_seconds(JSContext *context,
"interval", &interval, "callback", &callback))
return JS_FALSE;
- closure = gjs_closure_new(context, callback, "timeout_seconds");
+ closure = gjs_closure_new(context, callback, "timeout_seconds", TRUE);
if (closure == NULL)
return JS_FALSE;
@@ -288,7 +288,7 @@ gjs_idle_add(JSContext *context,
"callback", &callback, "priority", &priority))
return JS_FALSE;
- closure = gjs_closure_new(context, callback, "idle");
+ closure = gjs_closure_new(context, callback, "idle", TRUE);
if (closure == NULL)
return JS_FALSE;
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]