leaks: Allow tracing Gst(Mini)Object reffing operations
authorThibault Saunier <tsaunier@gnome.org>
Thu, 1 Dec 2016 20:35:45 +0000 (17:35 -0300)
committerThibault Saunier <thibault.saunier@osg.samsung.com>
Tue, 20 Dec 2016 18:29:10 +0000 (15:29 -0300)
It makes it much simpler to later debug refcount issues.

https://bugzilla.gnome.org/show_bug.cgi?id=775541

gst/gstminiobject.c
gst/gstobject.c
gst/gsttracerutils.c
gst/gsttracerutils.h
plugins/tracers/gstleaks.c
plugins/tracers/gstleaks.h

index db571ab5f7346e54f63cc6b29da6fd8d1cb6c5b0..4662f2655d8f1ce52e1bff617eb82c9877277837 100644 (file)
@@ -344,6 +344,7 @@ gst_mini_object_ref (GstMiniObject * mini_object)
    g_return_val_if_fail (mini_object->refcount > 0, NULL);
    */
 
+  GST_TRACER_MINI_OBJECT_REFFED (mini_object, mini_object->refcount + 1);
   GST_CAT_TRACE (GST_CAT_REFCOUNTING, "%p ref %d->%d", mini_object,
       GST_MINI_OBJECT_REFCOUNT_VALUE (mini_object),
       GST_MINI_OBJECT_REFCOUNT_VALUE (mini_object) + 1);
@@ -433,6 +434,7 @@ gst_mini_object_unref (GstMiniObject * mini_object)
   if (G_UNLIKELY (g_atomic_int_dec_and_test (&mini_object->refcount))) {
     gboolean do_free;
 
+    GST_TRACER_MINI_OBJECT_UNREFFED (mini_object, mini_object->refcount);
     if (mini_object->dispose)
       do_free = mini_object->dispose (mini_object);
     else
@@ -453,6 +455,8 @@ gst_mini_object_unref (GstMiniObject * mini_object)
       if (mini_object->free)
         mini_object->free (mini_object);
     }
+  } else {
+    GST_TRACER_MINI_OBJECT_UNREFFED (mini_object, mini_object->refcount);
   }
 }
 
index 5c5c90afe0fec50799c21075421f1487dd9c5089..36da2a039c6eb3dd9b4618798f6b4e2a57d00e81 100644 (file)
@@ -244,6 +244,7 @@ gst_object_ref (gpointer object)
 {
   g_return_val_if_fail (object != NULL, NULL);
 
+  GST_TRACER_OBJECT_REFFED (object, ((GObject *) object)->ref_count + 1);
 #ifdef DEBUG_REFCOUNT
   GST_CAT_TRACE_OBJECT (GST_CAT_REFCOUNTING, object, "%p ref %d->%d", object,
       ((GObject *) object)->ref_count, ((GObject *) object)->ref_count + 1);
@@ -270,6 +271,7 @@ gst_object_unref (gpointer object)
   g_return_if_fail (object != NULL);
   g_return_if_fail (((GObject *) object)->ref_count > 0);
 
+  GST_TRACER_OBJECT_UNREFFED (object, ((GObject *) object)->ref_count - 1);
 #ifdef DEBUG_REFCOUNT
   GST_CAT_TRACE_OBJECT (GST_CAT_REFCOUNTING, object, "%p unref %d->%d", object,
       ((GObject *) object)->ref_count, ((GObject *) object)->ref_count - 1);
@@ -1282,7 +1284,7 @@ gst_object_get_control_binding (GstObject * object, const gchar * property_name)
  * @binding: the binding
  *
  * Removes the corresponding #GstControlBinding. If it was the
- * last ref of the binding, it will be disposed.  
+ * last ref of the binding, it will be disposed.
  *
  * Returns: %TRUE if the binding could be removed.
  */
@@ -1356,7 +1358,7 @@ gst_object_get_value (GstObject * object, const gchar * property_name,
  * This function is useful if one wants to e.g. draw a graph of the control
  * curve or apply a control curve sample by sample.
  *
- * The values are unboxed and ready to be used. The similar function 
+ * The values are unboxed and ready to be used. The similar function
  * gst_object_get_g_value_array() returns the array as #GValues and is
  * better suites for bindings.
  *
index d46deffe1990c505d1c6c30fda9aeda45e3c5067..c7786001f4fcabf5eea37c92e54575d1973757e6 100644 (file)
@@ -55,7 +55,8 @@ static const gchar *_quark_strings[] = {
   "pad-link-pre", "pad-link-post", "pad-unlink-pre", "pad-unlink-post",
   "element-change-state-pre", "element-change-state-post",
   "mini-object-created", "mini-object-destroyed", "object-created",
-  "object-destroyed",
+  "object-destroyed", "mini-object-reffed", "mini-object-unreffed",
+  "object-reffed", "object-unreffed",
 };
 
 GQuark _priv_gst_tracer_quark_table[GST_TRACER_QUARK_MAX];
index e61d66d414b3ab7cb55719558d2ef0f3f784193f..9521b6ecf769a15c2c6e3f65997694a26c4f61b7 100644 (file)
@@ -75,6 +75,10 @@ typedef enum /*< skip >*/
   GST_TRACER_QUARK_HOOK_MINI_OBJECT_DESTROYED,
   GST_TRACER_QUARK_HOOK_OBJECT_CREATED,
   GST_TRACER_QUARK_HOOK_OBJECT_DESTROYED,
+  GST_TRACER_QUARK_HOOK_MINI_OBJECT_REFFED,
+  GST_TRACER_QUARK_HOOK_MINI_OBJECT_UNREFFED,
+  GST_TRACER_QUARK_HOOK_OBJECT_REFFED,
+  GST_TRACER_QUARK_HOOK_OBJECT_UNREFFED,
   GST_TRACER_QUARK_MAX
 } GstTracerQuarkId;
 
@@ -591,6 +595,74 @@ typedef void (*GstTracerHookMiniObjectDestroyed) (GObject *self, GstClockTime ts
     GstTracerHookMiniObjectDestroyed, (GST_TRACER_ARGS, object)); \
 }G_STMT_END
 
+/**
+ * GstTracerHookObjectUnreffed:
+ * @self: the tracer instance
+ * @ts: the current timestamp
+ * @object: the object being unreffed
+ * @refcount: the new refcount after unrefing @object
+ *
+ * Hook called when a #GstObject is being unreffed named
+ * "object-unreffed"
+ */
+typedef void (*GstTracerHookObjectUnreffed) (GObject *self, GstClockTime ts,
+    GstObject *object, gint new_refcount);
+#define GST_TRACER_OBJECT_UNREFFED(object, new_refcount) G_STMT_START{ \
+  GST_TRACER_DISPATCH(GST_TRACER_QUARK(HOOK_OBJECT_UNREFFED), \
+    GstTracerHookObjectUnreffed, (GST_TRACER_ARGS, object, new_refcount)); \
+}G_STMT_END
+
+/**
+ * GstTracerHookObjectReffed:
+ * @self: the tracer instance
+ * @ts: the current timestamp
+ * @object: the object being reffed
+ * @refcount: the new refcount after refing @object
+ *
+ * Hook called when a #GstObject is being reffed named
+ * "object-reffed".
+ */
+typedef void (*GstTracerHookObjectReffed) (GObject *self, GstClockTime ts,
+    GstObject *object, gint new_refcount);
+#define GST_TRACER_OBJECT_REFFED(object, new_refcount) G_STMT_START{ \
+  GST_TRACER_DISPATCH(GST_TRACER_QUARK(HOOK_OBJECT_REFFED), \
+    GstTracerHookObjectReffed, (GST_TRACER_ARGS, object, new_refcount)); \
+}G_STMT_END
+
+/**
+ * GstTracerHookMiniObjectUnreffed:
+ * @self: the tracer instance
+ * @ts: the current timestamp
+ * @object: the mini object being unreffed
+ * @refcount: the new refcount after unrefing @object
+ *
+ * Hook called when a #GstMiniObject is being unreffed named
+ * "mini-object-unreffed".
+ */
+typedef void (*GstTracerHookMiniObjectUnreffed) (GObject *self, GstClockTime ts,
+    GstMiniObject *object, gint new_refcount);
+#define GST_TRACER_MINI_OBJECT_UNREFFED(object, new_refcount) G_STMT_START{ \
+  GST_TRACER_DISPATCH(GST_TRACER_QUARK(HOOK_MINI_OBJECT_UNREFFED), \
+    GstTracerHookMiniObjectUnreffed, (GST_TRACER_ARGS, object, new_refcount)); \
+}G_STMT_END
+
+/**
+ * GstTracerHookMiniObjectReffed:
+ * @self: the tracer instance
+ * @ts: the current timestamp
+ * @object: the mini object being reffed
+ * @refcount: the new refcount after refing @object
+ *
+ * Hook called when a #GstMiniObject is being reffed named
+ * "mini-object-reffed".
+ */
+typedef void (*GstTracerHookMiniObjectReffed) (GObject *self, GstClockTime ts,
+    GstMiniObject *object, gint new_refcount);
+#define GST_TRACER_MINI_OBJECT_REFFED(object, new_refcount) G_STMT_START{ \
+  GST_TRACER_DISPATCH(GST_TRACER_QUARK(HOOK_MINI_OBJECT_REFFED), \
+    GstTracerHookMiniObjectReffed, (GST_TRACER_ARGS, object, new_refcount)); \
+}G_STMT_END
+
 /**
  * GstTracerHookObjectCreated:
  * @self: the tracer instance
@@ -654,8 +726,12 @@ typedef void (*GstTracerHookObjectDestroyed) (GObject *self, GstClockTime ts,
 #define GST_TRACER_PAD_UNLINK_POST(srcpad, sinkpad, res)
 #define GST_TRACER_MINI_OBJECT_CREATED(object)
 #define GST_TRACER_MINI_OBJECT_DESTROYED(object)
+#define GST_TRACER_MINI_OBJECT_REFFED(object, new_refcount)
+#define GST_TRACER_MINI_OBJECT_UNREF(object, new_refcount)
 #define GST_TRACER_OBJECT_CREATED(object)
 #define GST_TRACER_OBJECT_DESTROYED(object)
+#define GST_TRACER_OBJECT_REFFED(object, new_refcount)
+#define GST_TRACER_OBJECT_UNREFFED(object, new_refcount)
 
 #endif /* GST_DISABLE_GST_TRACER_HOOKS */
 
index 08cb3f58b95e6d98bca20385c92f1311ec71bb1a..950d9206916ada21714943aef5b4ac26152fff3d 100644 (file)
@@ -48,12 +48,45 @@ G_DEFINE_TYPE_WITH_CODE (GstLeaksTracer, gst_leaks_tracer,
     GST_TYPE_TRACER, _do_init);
 
 static GstTracerRecord *tr_alive;
+static GstTracerRecord *tr_refings;
 #ifdef G_OS_UNIX
 static GstTracerRecord *tr_added = NULL;
 static GstTracerRecord *tr_removed = NULL;
 #endif /* G_OS_UNIX */
 static GQueue instances = G_QUEUE_INIT;
 
+typedef struct
+{
+  gboolean reffed;
+  gchar *trace;
+  gint new_refcount;
+  GstClockTime ts;
+} ObjectRefingInfo;
+
+typedef struct
+{
+  gchar *creation_trace;
+
+  GList *refing_infos;
+} ObjectRefingInfos;
+
+static void
+object_refing_info_free (ObjectRefingInfo * refinfo)
+{
+  g_free (refinfo->trace);
+  g_free (refinfo);
+}
+
+static void
+object_refing_infos_free (ObjectRefingInfos * infos)
+{
+  g_list_free_full (infos->refing_infos,
+      (GDestroyNotify) object_refing_info_free);
+
+  g_free (infos->creation_trace);
+  g_free (infos);
+}
+
 static void
 set_print_stack_trace (GstLeaksTracer * self, GstStructure * params)
 {
@@ -120,6 +153,7 @@ set_params_from_structure (GstLeaksTracer * self, GstStructure * params)
 
   if (filters)
     set_filters (self, filters);
+  gst_structure_get_boolean (params, "check-refs", &self->check_refs);
 }
 
 static void
@@ -261,11 +295,13 @@ static void
 handle_object_created (GstLeaksTracer * self, gpointer object, GType type,
     gboolean gobject)
 {
-  gchar *trace = NULL;
+  ObjectRefingInfos *infos;
+
 
   if (!should_handle_object_type (self, type))
     return;
 
+  infos = g_malloc0 (sizeof (ObjectRefingInfos));
   if (gobject)
     g_object_weak_ref ((GObject *) object, object_weak_cb, self);
   else
@@ -273,11 +309,10 @@ handle_object_created (GstLeaksTracer * self, gpointer object, GType type,
         mini_object_weak_cb, self);
 
   GST_OBJECT_LOCK (self);
-  if (self->log_stack_trace) {
-    trace = gst_debug_get_stack_trace (0);
-  }
+  if (self->log_stack_trace)
+    infos->creation_trace = gst_debug_get_stack_trace (0);
 
-  g_hash_table_insert (self->objects, object, trace);
+  g_hash_table_insert (self->objects, object, infos);
 
 #ifdef G_OS_UNIX
   if (self->added)
@@ -308,10 +343,75 @@ object_created_cb (GstTracer * tracer, GstClockTime ts, GstObject * object)
   handle_object_created (self, object, object_type, TRUE);
 }
 
+static void
+handle_object_reffed (GstLeaksTracer * self, gpointer object, gint new_refcount,
+    gboolean reffed, GstClockTime ts)
+{
+  ObjectRefingInfos *infos;
+  ObjectRefingInfo *refinfo;
+
+  if (!self->check_refs)
+    return;
+
+  GST_OBJECT_LOCK (self);
+  infos = g_hash_table_lookup (self->objects, object);
+  if (!infos)
+    goto out;
+
+  refinfo = g_malloc0 (sizeof (ObjectRefingInfo));
+  refinfo->ts = ts;
+  refinfo->new_refcount = new_refcount;
+  refinfo->reffed = reffed;
+  if (self->log_stack_trace)
+    refinfo->trace = gst_debug_get_stack_trace (0);
+
+  infos->refing_infos = g_list_prepend (infos->refing_infos, refinfo);
+
+out:
+  GST_OBJECT_UNLOCK (self);
+}
+
+static void
+object_reffed_cb (GstTracer * tracer, GstClockTime ts, GstObject * object,
+    gint new_refcount)
+{
+  GstLeaksTracer *self = GST_LEAKS_TRACER_CAST (tracer);
+
+  handle_object_reffed (self, object, new_refcount, TRUE, ts);
+}
+
+static void
+object_unreffed_cb (GstTracer * tracer, GstClockTime ts, GstObject * object,
+    gint new_refcount)
+{
+  GstLeaksTracer *self = GST_LEAKS_TRACER_CAST (tracer);
+
+  handle_object_reffed (self, object, new_refcount, FALSE, ts);
+}
+
+static void
+mini_object_reffed_cb (GstTracer * tracer, GstClockTime ts,
+    GstMiniObject * object, gint new_refcount)
+{
+  GstLeaksTracer *self = GST_LEAKS_TRACER_CAST (tracer);
+
+  handle_object_reffed (self, object, new_refcount, TRUE, ts);
+}
+
+static void
+mini_object_unreffed_cb (GstTracer * tracer, GstClockTime ts,
+    GstMiniObject * object, gint new_refcount)
+{
+  GstLeaksTracer *self = GST_LEAKS_TRACER_CAST (tracer);
+
+  handle_object_reffed (self, object, new_refcount, FALSE, ts);
+}
+
 static void
 gst_leaks_tracer_init (GstLeaksTracer * self)
 {
-  self->objects = g_hash_table_new_full (NULL, NULL, NULL, g_free);
+  self->objects = g_hash_table_new_full (NULL, NULL, NULL,
+      (GDestroyNotify) object_refing_infos_free);
 
   g_queue_push_tail (&instances, self);
 }
@@ -329,6 +429,17 @@ gst_leaks_tracer_constructed (GObject * object)
   gst_tracing_register_hook (tracer, "object-created",
       G_CALLBACK (object_created_cb));
 
+  if (self->check_refs) {
+    gst_tracing_register_hook (tracer, "object-reffed",
+        G_CALLBACK (object_reffed_cb));
+    gst_tracing_register_hook (tracer, "mini-object-reffed",
+        G_CALLBACK (mini_object_reffed_cb));
+    gst_tracing_register_hook (tracer, "mini-object-unreffed",
+        G_CALLBACK (mini_object_unreffed_cb));
+    gst_tracing_register_hook (tracer, "object-unreffed",
+        G_CALLBACK (object_unreffed_cb));
+  }
+
   /* We rely on weak pointers rather than (mini-)object-destroyed hooks so we
    * are notified of objects being destroyed even during the shuting down of
    * the tracing system. */
@@ -342,13 +453,13 @@ typedef struct
   const gchar *type_name;
   guint ref_count;
   gchar *desc;
-  const gchar *trace;
+  ObjectRefingInfos *infos;
 } Leak;
 
 /* The content of the returned Leak struct is valid until the self->objects
  * hash table has been modified. */
 static Leak *
-leak_new (gpointer obj, GType type, guint ref_count, const gchar * trace)
+leak_new (gpointer obj, GType type, guint ref_count, ObjectRefingInfos * infos)
 {
   Leak *leak = g_slice_new (Leak);
 
@@ -356,7 +467,7 @@ leak_new (gpointer obj, GType type, guint ref_count, const gchar * trace)
   leak->type_name = g_type_name (type);
   leak->ref_count = ref_count;
   leak->desc = gst_info_strdup_printf ("%" GST_PTR_FORMAT, obj);
-  leak->trace = trace;
+  leak->infos = infos;
 
   return leak;
 }
@@ -381,10 +492,10 @@ create_leaks_list (GstLeaksTracer * self)
 {
   GList *l = NULL;
   GHashTableIter iter;
-  gpointer obj, trace;
+  gpointer obj, infos;
 
   g_hash_table_iter_init (&iter, self->objects);
-  while (g_hash_table_iter_next (&iter, &obj, &trace)) {
+  while (g_hash_table_iter_next (&iter, &obj, &infos)) {
     GType type;
     guint ref_count;
 
@@ -402,7 +513,7 @@ create_leaks_list (GstLeaksTracer * self)
       ref_count = ((GstMiniObject *) obj)->refcount;
     }
 
-    l = g_list_prepend (l, leak_new (obj, type, ref_count, trace));
+    l = g_list_prepend (l, leak_new (obj, type, ref_count, infos));
   }
 
   /* Sort leaks by type name so they are grouped together making the output
@@ -416,7 +527,7 @@ create_leaks_list (GstLeaksTracer * self)
 static gboolean
 log_leaked (GstLeaksTracer * self)
 {
-  GList *leaks, *l;
+  GList *ref, *leaks, *l;
 
   leaks = create_leaks_list (self);
   if (!leaks)
@@ -426,7 +537,17 @@ log_leaked (GstLeaksTracer * self)
     Leak *leak = l->data;
 
     gst_tracer_record_log (tr_alive, leak->type_name, leak->obj, leak->desc,
-        leak->ref_count, leak->trace ? leak->trace : "");
+        leak->ref_count,
+        leak->infos->creation_trace ? leak->infos->creation_trace : "");
+
+    leak->infos->refing_infos = g_list_reverse (leak->infos->refing_infos);
+    for (ref = leak->infos->refing_infos; ref; ref = ref->next) {
+      ObjectRefingInfo *refinfo = (ObjectRefingInfo *) ref->data;
+
+      gst_tracer_record_log (tr_refings, refinfo->ts, leak->type_name,
+          leak->obj, refinfo->reffed ? "reffed" : "unreffed",
+          refinfo->new_refcount, refinfo->trace ? refinfo->trace : "");
+    }
   }
 
   g_list_free_full (leaks, (GDestroyNotify) leak_free);
@@ -473,6 +594,11 @@ gst_leaks_tracer_finalize (GObject * object)
   ((GObjectClass *) gst_leaks_tracer_parent_class)->finalize (object);
 }
 
+#define RECORD_FIELD_TYPE_TS \
+    "ts", GST_TYPE_STRUCTURE, gst_structure_new ("value", \
+        "type", G_TYPE_GTYPE, GST_TYPE_CLOCK_TIME, \
+        "related-to", GST_TYPE_TRACER_VALUE_SCOPE, GST_TRACER_VALUE_SCOPE_PROCESS, \
+        NULL)
 #define RECORD_FIELD_TYPE_NAME \
     "type-name", GST_TYPE_STRUCTURE, gst_structure_new ("value", \
         "type", G_TYPE_GTYPE, G_TYPE_STRING, \
@@ -601,6 +727,11 @@ gst_leaks_tracer_class_init (GstLeaksTracerClass * klass)
       RECORD_FIELD_REF_COUNT, RECORD_FIELD_TRACE, NULL);
   GST_OBJECT_FLAG_SET (tr_alive, GST_OBJECT_FLAG_MAY_BE_LEAKED);
 
+  tr_refings = gst_tracer_record_new ("object-refings.class",
+      RECORD_FIELD_TYPE_TS, RECORD_FIELD_TYPE_NAME, RECORD_FIELD_ADDRESS,
+      RECORD_FIELD_DESC, RECORD_FIELD_REF_COUNT, RECORD_FIELD_TRACE, NULL);
+  GST_OBJECT_FLAG_SET (tr_alive, GST_OBJECT_FLAG_MAY_BE_LEAKED);
+
   if (g_getenv ("GST_LEAKS_TRACER_SIG")) {
 #ifdef G_OS_UNIX
     setup_signals ();
index 6dd3def9327defe15f797960e7671dcbb661dc26..7539b26b48bc0bf4d9c0290fe6070814edc14ed4 100644 (file)
@@ -68,6 +68,8 @@ struct _GstLeaksTracer {
   gint unhandled_filter_count;
   gboolean done;
 
+  gboolean check_refs;
+
   gboolean log_stack_trace;
 };