2 * Copyright (C) 2016 Collabora Ltd. <guillaume.desmottes@collabora.co.uk>
4 * gstleaks.c: tracing module detecting object leaks
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Library General Public License for more details.
16 * You should have received a copy of the GNU Library General Public
17 * License along with this library; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19 * Boston, MA 02110-1301, USA.
23 * @short_description: detect GstObject and GstMiniObject leaks
25 * A tracing module tracking the lifetime of objects by logging those still
26 * alive when program is exiting and raising a warning.
27 * The type of objects tracked can be filtered using the parameters of the
28 * tracer, for example: GST_TRACERS="leaks(GstEvent,GstMessage)"
39 #endif /* G_OS_UNIX */
41 GST_DEBUG_CATEGORY_STATIC (gst_leaks_debug);
42 #define GST_CAT_DEFAULT gst_leaks_debug
45 GST_DEBUG_CATEGORY_INIT (gst_leaks_debug, "leaks", 0, "leaks tracer");
46 #define gst_leaks_tracer_parent_class parent_class
47 G_DEFINE_TYPE_WITH_CODE (GstLeaksTracer, gst_leaks_tracer,
48 GST_TYPE_TRACER, _do_init);
50 static GstTracerRecord *tr_alive;
52 static GstTracerRecord *tr_added = NULL;
53 static GstTracerRecord *tr_removed = NULL;
54 #endif /* G_OS_UNIX */
55 static GQueue instances = G_QUEUE_INIT;
58 set_filtering (GstLeaksTracer * self)
64 g_object_get (self, "params", ¶ms, NULL);
68 tmp = g_strsplit (params, ",", -1);
70 self->filter = g_array_sized_new (FALSE, FALSE, sizeof (GType),
72 for (i = 0; tmp[i]; i++) {
75 type = g_type_from_name (tmp[i]);
77 /* The type may not yet be known by the type system, typically because
78 * the plugin implementing it as not yet be loaded. Save it for now as
79 * it will have another chance to be added to the filter later in
80 * should_handle_object_type() when/if the object type is actually
82 if (!self->unhandled_filter)
83 self->unhandled_filter = g_hash_table_new (NULL, NULL);
85 g_hash_table_add (self->unhandled_filter,
86 GUINT_TO_POINTER (g_quark_from_string (tmp[i])));
87 g_atomic_int_inc (&self->unhandled_filter_count);
91 GST_DEBUG_OBJECT (self, "add filter on %s", tmp[i]);
93 g_array_append_val (self->filter, type);
101 should_handle_object_type (GstLeaksTracer * self, GType object_type)
106 /* No filtering, handle all types */
109 if (g_atomic_int_get (&self->unhandled_filter_count)) {
110 GST_OBJECT_LOCK (self);
111 if (self->unhandled_filter) {
114 q = g_type_qname (object_type);
115 if (g_hash_table_contains (self->unhandled_filter, GUINT_TO_POINTER (q))) {
116 g_array_append_val (self->filter, object_type);
117 g_hash_table_remove (self->unhandled_filter, GUINT_TO_POINTER (q));
119 if (g_atomic_int_dec_and_test (&self->unhandled_filter_count))
120 g_clear_pointer (&self->unhandled_filter, g_hash_table_unref);
122 GST_OBJECT_UNLOCK (self);
126 GST_OBJECT_UNLOCK (self);
129 len = self->filter->len;
130 for (i = 0; i < len; i++) {
131 GType type = g_array_index (self->filter, GType, i);
133 if (g_type_is_a (object_type, type))
141 /* The object may be destroyed when we log it using the checkpointing system so
142 * we have to save its type name */
146 const gchar *type_name;
150 object_log_new (gpointer obj)
152 ObjectLog *o = g_slice_new (ObjectLog);
156 if (G_IS_OBJECT (obj))
157 o->type_name = G_OBJECT_TYPE_NAME (obj);
159 o->type_name = g_type_name (GST_MINI_OBJECT_TYPE (obj));
165 object_log_free (ObjectLog * obj)
167 g_slice_free (ObjectLog, obj);
169 #endif /* G_OS_UNIX */
172 handle_object_destroyed (GstLeaksTracer * self, gpointer object)
174 GST_OBJECT_LOCK (self);
177 ("object %p destroyed while the leaks tracer was finalizing. Some threads are still running?",
182 g_hash_table_remove (self->objects, object);
185 g_hash_table_add (self->removed, object_log_new (object));
186 #endif /* G_OS_UNIX */
188 GST_OBJECT_UNLOCK (self);
192 object_weak_cb (gpointer data, GObject * object)
194 GstLeaksTracer *self = data;
196 handle_object_destroyed (self, object);
200 mini_object_weak_cb (gpointer data, GstMiniObject * object)
202 GstLeaksTracer *self = data;
204 handle_object_destroyed (self, object);
208 handle_object_created (GstLeaksTracer * self, gpointer object, GType type,
213 if (!should_handle_object_type (self, type))
217 g_object_weak_ref ((GObject *) object, object_weak_cb, self);
219 gst_mini_object_weak_ref (GST_MINI_OBJECT_CAST (object),
220 mini_object_weak_cb, self);
222 GST_OBJECT_LOCK (self);
223 if (self->log_stack_trace) {
224 trace = gst_debug_get_stack_trace (GST_STACK_TRACE_SHOW_FULL);
227 g_hash_table_insert (self->objects, object, trace);
231 g_hash_table_add (self->added, object_log_new (object));
232 #endif /* G_OS_UNIX */
233 GST_OBJECT_UNLOCK (self);
237 mini_object_created_cb (GstTracer * tracer, GstClockTime ts,
238 GstMiniObject * object)
240 GstLeaksTracer *self = GST_LEAKS_TRACER_CAST (tracer);
242 handle_object_created (self, object, GST_MINI_OBJECT_TYPE (object), FALSE);
246 object_created_cb (GstTracer * tracer, GstClockTime ts, GstObject * object)
248 GstLeaksTracer *self = GST_LEAKS_TRACER_CAST (tracer);
249 GType object_type = G_OBJECT_TYPE (object);
251 /* Can't track tracers as they may be disposed after the leak tracer itself */
252 if (g_type_is_a (object_type, GST_TYPE_TRACER))
255 handle_object_created (self, object, object_type, TRUE);
259 gst_leaks_tracer_init (GstLeaksTracer * self)
261 self->objects = g_hash_table_new_full (NULL, NULL, NULL, g_free);
263 if (g_getenv ("GST_LEAKS_TRACER_STACK_TRACE")) {
266 /* Test if we can retrieve backtrace */
267 trace = gst_debug_get_stack_trace (GST_STACK_TRACE_SHOW_FULL);
269 self->log_stack_trace = TRUE;
272 g_warning ("Can't retrieve backtrace on this system");
276 g_queue_push_tail (&instances, self);
280 gst_leaks_tracer_constructed (GObject * object)
282 GstLeaksTracer *self = GST_LEAKS_TRACER (object);
283 GstTracer *tracer = GST_TRACER (object);
285 set_filtering (self);
287 gst_tracing_register_hook (tracer, "mini-object-created",
288 G_CALLBACK (mini_object_created_cb));
289 gst_tracing_register_hook (tracer, "object-created",
290 G_CALLBACK (object_created_cb));
292 /* We rely on weak pointers rather than (mini-)object-destroyed hooks so we
293 * are notified of objects being destroyed even during the shuting down of
294 * the tracing system. */
296 ((GObjectClass *) gst_leaks_tracer_parent_class)->constructed (object);
302 const gchar *type_name;
308 /* The content of the returned Leak struct is valid until the self->objects
309 * hash table has been modified. */
311 leak_new (gpointer obj, GType type, guint ref_count, const gchar * trace)
313 Leak *leak = g_slice_new (Leak);
316 leak->type_name = g_type_name (type);
317 leak->ref_count = ref_count;
318 leak->desc = gst_info_strdup_printf ("%" GST_PTR_FORMAT, obj);
325 leak_free (Leak * leak)
328 g_slice_free (Leak, leak);
332 sort_leaks (gconstpointer _a, gconstpointer _b)
334 const Leak *a = _a, *b = _b;
336 return g_strcmp0 (a->type_name, b->type_name);
340 create_leaks_list (GstLeaksTracer * self)
346 g_hash_table_iter_init (&iter, self->objects);
347 while (g_hash_table_iter_next (&iter, &obj, &trace)) {
351 if (GST_IS_OBJECT (obj)) {
352 if (GST_OBJECT_FLAG_IS_SET (obj, GST_OBJECT_FLAG_MAY_BE_LEAKED))
355 type = G_OBJECT_TYPE (obj);
356 ref_count = ((GObject *) obj)->ref_count;
358 if (GST_MINI_OBJECT_FLAG_IS_SET (obj, GST_MINI_OBJECT_FLAG_MAY_BE_LEAKED))
361 type = GST_MINI_OBJECT_TYPE (obj);
362 ref_count = ((GstMiniObject *) obj)->refcount;
365 l = g_list_prepend (l, leak_new (obj, type, ref_count, trace));
368 /* Sort leaks by type name so they are grouped together making the output
370 l = g_list_sort (l, sort_leaks);
375 /* Return TRUE if at least one leaked object has been logged */
377 log_leaked (GstLeaksTracer * self)
381 leaks = create_leaks_list (self);
385 for (l = leaks; l != NULL; l = g_list_next (l)) {
386 Leak *leak = l->data;
388 gst_tracer_record_log (tr_alive, leak->type_name, leak->obj, leak->desc,
389 leak->ref_count, leak->trace ? leak->trace : "");
392 g_list_free_full (leaks, (GDestroyNotify) leak_free);
398 gst_leaks_tracer_finalize (GObject * object)
400 GstLeaksTracer *self = GST_LEAKS_TRACER (object);
407 /* Tracers are destroyed as part of gst_deinit() so now is a good time to
408 * report all the objects which are still alive. */
409 leaks = log_leaked (self);
411 /* Remove weak references */
412 g_hash_table_iter_init (&iter, self->objects);
413 while (g_hash_table_iter_next (&iter, &obj, NULL)) {
414 if (GST_IS_OBJECT (obj))
415 g_object_weak_unref (obj, object_weak_cb, self);
417 gst_mini_object_weak_unref (GST_MINI_OBJECT_CAST (obj),
418 mini_object_weak_cb, self);
421 g_clear_pointer (&self->objects, g_hash_table_unref);
423 g_array_free (self->filter, TRUE);
424 g_clear_pointer (&self->added, g_hash_table_unref);
425 g_clear_pointer (&self->removed, g_hash_table_unref);
426 g_clear_pointer (&self->unhandled_filter, g_hash_table_unref);
428 g_queue_remove (&instances, self);
431 g_warning ("Leaks detected");
433 ((GObjectClass *) gst_leaks_tracer_parent_class)->finalize (object);
436 #define RECORD_FIELD_TYPE_NAME \
437 "type-name", GST_TYPE_STRUCTURE, gst_structure_new ("value", \
438 "type", G_TYPE_GTYPE, G_TYPE_STRING, \
439 "related-to", GST_TYPE_TRACER_VALUE_SCOPE, GST_TRACER_VALUE_SCOPE_PROCESS, \
441 #define RECORD_FIELD_ADDRESS \
442 "address", GST_TYPE_STRUCTURE, gst_structure_new ("value", \
443 "type", G_TYPE_GTYPE, G_TYPE_POINTER, \
444 "related-to", GST_TYPE_TRACER_VALUE_SCOPE, \
445 GST_TRACER_VALUE_SCOPE_PROCESS, \
447 #define RECORD_FIELD_DESC \
448 "description", GST_TYPE_STRUCTURE, gst_structure_new ("value", \
449 "type", G_TYPE_GTYPE, G_TYPE_STRING, \
450 "related-to", GST_TYPE_TRACER_VALUE_SCOPE, GST_TRACER_VALUE_SCOPE_PROCESS, \
452 #define RECORD_FIELD_REF_COUNT \
453 "ref-count", GST_TYPE_STRUCTURE, gst_structure_new ("value", \
454 "type", G_TYPE_GTYPE, G_TYPE_UINT, \
455 "related-to", GST_TYPE_TRACER_VALUE_SCOPE, GST_TRACER_VALUE_SCOPE_PROCESS, \
457 #define RECORD_FIELD_TRACE \
458 "trace", GST_TYPE_STRUCTURE, gst_structure_new ("value", \
459 "type", G_TYPE_GTYPE, G_TYPE_STRING, \
460 "related-to", GST_TYPE_TRACER_VALUE_SCOPE, GST_TRACER_VALUE_SCOPE_PROCESS, \
465 sig_usr1_handler_foreach (gpointer data, gpointer user_data)
467 GstLeaksTracer *tracer = data;
469 GST_OBJECT_LOCK (tracer);
470 GST_TRACE_OBJECT (tracer, "start listing currently alive objects");
472 GST_TRACE_OBJECT (tracer, "done listing currently alive objects");
473 GST_OBJECT_UNLOCK (tracer);
477 sig_usr1_handler (G_GNUC_UNUSED int signal)
479 g_queue_foreach (&instances, sig_usr1_handler_foreach, NULL);
483 log_checkpoint (GHashTable * hash, GstTracerRecord * record)
488 g_hash_table_iter_init (&iter, hash);
489 while (g_hash_table_iter_next (&iter, &o, NULL)) {
492 gst_tracer_record_log (record, obj->type_name, obj->object);
497 do_checkpoint (GstLeaksTracer * self)
499 GST_TRACE_OBJECT (self, "listing objects created since last checkpoint");
500 log_checkpoint (self->added, tr_added);
501 GST_TRACE_OBJECT (self, "listing objects removed since last checkpoint");
502 log_checkpoint (self->removed, tr_removed);
504 g_hash_table_remove_all (self->added);
505 g_hash_table_remove_all (self->removed);
509 sig_usr2_handler_foreach (gpointer data, gpointer user_data)
511 GstLeaksTracer *tracer = data;
513 GST_OBJECT_LOCK (tracer);
515 if (!tracer->added) {
516 GST_TRACE_OBJECT (tracer, "First checkpoint, start tracking objects");
518 tracer->added = g_hash_table_new_full (NULL, NULL,
519 (GDestroyNotify) object_log_free, NULL);
520 tracer->removed = g_hash_table_new_full (NULL, NULL,
521 (GDestroyNotify) object_log_free, NULL);
523 do_checkpoint (tracer);
526 GST_OBJECT_UNLOCK (tracer);
530 sig_usr2_handler (G_GNUC_UNUSED int signal)
532 g_queue_foreach (&instances, sig_usr2_handler_foreach, NULL);
538 tr_added = gst_tracer_record_new ("object-added.class",
539 RECORD_FIELD_TYPE_NAME, RECORD_FIELD_ADDRESS, NULL);
540 GST_OBJECT_FLAG_SET (tr_added, GST_OBJECT_FLAG_MAY_BE_LEAKED);
542 tr_removed = gst_tracer_record_new ("object-removed.class",
543 RECORD_FIELD_TYPE_NAME, RECORD_FIELD_ADDRESS, NULL);
544 GST_OBJECT_FLAG_SET (tr_removed, GST_OBJECT_FLAG_MAY_BE_LEAKED);
546 signal (SIGUSR1, sig_usr1_handler);
547 signal (SIGUSR2, sig_usr2_handler);
549 #endif /* G_OS_UNIX */
552 gst_leaks_tracer_class_init (GstLeaksTracerClass * klass)
554 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
556 gobject_class->constructed = gst_leaks_tracer_constructed;
557 gobject_class->finalize = gst_leaks_tracer_finalize;
559 tr_alive = gst_tracer_record_new ("object-alive.class",
560 RECORD_FIELD_TYPE_NAME, RECORD_FIELD_ADDRESS, RECORD_FIELD_DESC,
561 RECORD_FIELD_REF_COUNT, RECORD_FIELD_TRACE, NULL);
562 GST_OBJECT_FLAG_SET (tr_alive, GST_OBJECT_FLAG_MAY_BE_LEAKED);
564 if (g_getenv ("GST_LEAKS_TRACER_SIG")) {
568 g_warning ("System doesn't support POSIX signals");
569 #endif /* G_OS_UNIX */