leaks: Allow tracing Gst(Mini)Object reffing operations

It makes it much simpler to later debug refcount issues.

https://bugzilla.gnome.org/show_bug.cgi?id=775541
This commit is contained in:
Thibault Saunier 2016-12-01 17:35:45 -03:00 committed by Thibault Saunier
parent 32b17a8f3d
commit 30133909ce
6 changed files with 233 additions and 17 deletions

View 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);
}
}

View 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);

View 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];

View 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 */

View 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 ();

View file

@ -68,6 +68,8 @@ struct _GstLeaksTracer {
gint unhandled_filter_count;
gboolean done;
gboolean check_refs;
gboolean log_stack_trace;
};