mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-28 20:51:13 +00:00
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:
parent
32b17a8f3d
commit
30133909ce
6 changed files with 233 additions and 17 deletions
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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 */
|
||||
|
||||
|
|
|
@ -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 ();
|
||||
|
|
|
@ -68,6 +68,8 @@ struct _GstLeaksTracer {
|
|||
gint unhandled_filter_count;
|
||||
gboolean done;
|
||||
|
||||
gboolean check_refs;
|
||||
|
||||
gboolean log_stack_trace;
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue