[evolution-data-server] [Camel] Add some helper functions for ref count issues debugging
- From: Milan Crha <mcrha src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [evolution-data-server] [Camel] Add some helper functions for ref count issues debugging
- Date: Thu, 19 Nov 2015 18:25:47 +0000 (UTC)
commit 5b89433a5d3d2b73781d8fefc1387be454cd924e
Author: Milan Crha <mcrha redhat com>
Date: Thu Nov 19 19:25:07 2015 +0100
[Camel] Add some helper functions for ref count issues debugging
camel/camel-debug.c | 487 +++++++++++++++++++++++++++++++
camel/camel-debug.h | 8 +
docs/reference/camel/camel-sections.txt | 3 +
3 files changed, 498 insertions(+), 0 deletions(-)
---
diff --git a/camel/camel-debug.c b/camel/camel-debug.c
index 0fe2b05..9c9e6e4 100644
--- a/camel/camel-debug.c
+++ b/camel/camel-debug.c
@@ -34,6 +34,8 @@
#endif
#endif
+#include <glib-object.h>
+
#include "camel-debug.h"
gint camel_verbose_debug;
@@ -690,3 +692,488 @@ camel_debug_get_backtrace (void)
{
return get_current_backtrace ();
}
+
+G_LOCK_DEFINE_STATIC (ref_unref_backtraces);
+static GQueue *ref_unref_backtraces = NULL;
+static guint total_ref_unref_backtraces = 0;
+
+typedef struct _BacktraceLine
+{
+ gchar *function;
+ gchar *file;
+ gint lineno;
+} BacktraceLine;
+
+static void
+backtrace_line_free (gpointer ptr)
+{
+ BacktraceLine *btline = ptr;
+
+ if (btline) {
+ g_free (btline->function);
+ g_free (btline->file);
+ g_free (btline);
+ }
+}
+
+typedef enum {
+ BACKTRACE_TYPE_OTHER,
+ BACKTRACE_TYPE_REF,
+ BACKTRACE_TYPE_UNREF
+} BacktraceType;
+
+typedef struct _Backtrace
+{
+ BacktraceType type;
+ guint object_ref_count;
+ GSList *lines; /* BacktraceLine */
+} Backtrace;
+
+static void
+backtrace_free (gpointer ptr)
+{
+ Backtrace *bt = ptr;
+
+ if (bt) {
+ g_slist_free_full (bt->lines, backtrace_line_free);
+ g_free (bt);
+ }
+}
+
+static BacktraceLine *
+parse_backtrace_line (const gchar *line)
+{
+ gchar **parts;
+ gint ii, lineno = 0;
+ gchar *function = NULL, *filename = NULL;
+
+ if (!line)
+ return NULL;
+
+ while (*line == ' ' || *line == '\t')
+ line++;
+
+ parts = g_strsplit (line, " ", -1);
+ if (!parts || !*parts) {
+ g_strfreev (parts);
+ return NULL;
+ }
+
+ for (ii = 0; parts[ii]; ii++) {
+ const gchar *part = parts[ii];
+
+ if (!*part)
+ continue;
+
+ if (ii == 1) {
+ function = g_strdup (part);
+ } else if (ii == 3 && function) {
+ gchar **file;
+
+ file = g_strsplit (part, ":", -1);
+ if (file && file[0] && file[1]) {
+ filename = g_strdup (file[0]);
+ lineno = g_ascii_strtoll (file[1], NULL, 10);
+ } else {
+ filename = g_strdup (part);
+ }
+
+ g_strfreev (file);
+ }
+ }
+
+ g_strfreev (parts);
+
+ if (function) {
+ BacktraceLine *btline;
+
+ btline = g_new0 (BacktraceLine, 1);
+ btline->function = function;
+ btline->file = filename;
+ btline->lineno = lineno;
+
+ return btline;
+ }
+
+ g_free (function);
+ g_free (filename);
+
+ return NULL;
+}
+
+static Backtrace *
+parse_backtrace (const GString *backtrace,
+ guint object_ref_count)
+{
+ Backtrace *bt;
+ gchar **btlines;
+ gint ii;
+
+ if (!backtrace)
+ return NULL;
+
+ btlines = g_strsplit (backtrace->str, "\n", -1);
+ if (!btlines || !*btlines) {
+ g_strfreev (btlines);
+ return NULL;
+ }
+
+ bt = g_new0 (Backtrace, 1);
+ bt->object_ref_count = object_ref_count;
+
+ for (ii = 0; btlines[ii]; ii++) {
+ if (ii >= 1) {
+ BacktraceLine *btline = parse_backtrace_line (btlines[ii]);
+
+ if (!btline)
+ continue;
+
+ bt->lines = g_slist_prepend (bt->lines, btline);
+
+ if (ii == 1) {
+ if (g_strcmp0 (btline->function, "g_object_ref()") == 0) {
+ bt->type = BACKTRACE_TYPE_REF;
+ } else if (g_strcmp0 (btline->function, "g_object_unref()") == 0) {
+ bt->type = BACKTRACE_TYPE_UNREF;
+ } else {
+ bt->type = BACKTRACE_TYPE_OTHER;
+ }
+ }
+ }
+ }
+
+ g_strfreev (btlines);
+
+ bt->lines = g_slist_reverse (bt->lines);
+
+ return bt;
+}
+
+static void
+print_backtrace (Backtrace *bt,
+ gint index)
+{
+ GSList *link;
+
+ if (!bt)
+ return;
+
+ g_print (" Backtrace[%d] %s %d~>%d:\n", index,
+ bt->type == BACKTRACE_TYPE_REF ? "ref" :
+ bt->type == BACKTRACE_TYPE_UNREF ? "unref" : "other",
+ bt->object_ref_count, bt->object_ref_count + (bt->type == BACKTRACE_TYPE_REF ? 1 : bt->type
== BACKTRACE_TYPE_UNREF ? -1 : 0));
+
+ for (link = bt->lines; link; link = g_slist_next (link)) {
+ BacktraceLine *btline = link->data;
+
+ g_print (" %s %s", link == bt->lines ? "at" : "by", btline->function);
+
+ if (btline->file) {
+ if (btline->lineno > 0) {
+ g_print (" at %s:%d\n", btline->file, btline->lineno);
+ } else {
+ g_print (" at %s\n", btline->file);
+ }
+ } else {
+ g_print ("\n");
+ }
+ }
+
+ g_print ("\n");
+}
+
+static gboolean
+backtrace_matches (const Backtrace *match_bt,
+ const Backtrace *find_bt,
+ gint lines_tolerance)
+{
+ GSList *mlink, *flink;
+ gint lines_matched = 0;
+ gint lt, ii;
+
+ if (!match_bt || !find_bt || !find_bt->lines || !match_bt->lines)
+ return FALSE;
+
+ flink = find_bt->lines;
+ mlink = NULL;
+ lt = lines_tolerance;
+ do {
+ gboolean found = FALSE;
+ BacktraceLine *fline = flink->data;
+
+ if (!fline)
+ return FALSE;
+
+ for (mlink = match_bt->lines, ii = 0; mlink && ii <= lines_tolerance; mlink = g_slist_next
(mlink), ii++) {
+ BacktraceLine *mline = mlink->data;
+
+ if (!mline)
+ return FALSE;
+
+ found = g_strcmp0 (fline->function, mline->function) == 0;
+ if (found)
+ break;
+ }
+
+ if (found)
+ break;
+
+ lt--;
+ if (lt >= 0)
+ flink = g_slist_next (flink);
+ else
+ flink = NULL;
+ } while (flink);
+
+ if (!flink)
+ return FALSE;
+
+ for (mlink = g_slist_next (mlink), flink = g_slist_next (flink);
+ mlink && flink;
+ mlink = g_slist_next (mlink), flink = g_slist_next (flink)) {
+ BacktraceLine *mline, *fline;
+
+ mline = mlink->data;
+ fline = flink->data;
+
+ if (!mline || !mline)
+ break;
+
+ if (g_strcmp0 (mline->function, fline->function) != 0 ||
+ g_strcmp0 (mline->file, fline->file) != 0 ||
+ mline->lineno != fline->lineno) {
+ break;
+ }
+
+ lines_matched++;
+ }
+
+ return (!mlink && !flink) || (lines_matched > 40 && (!mlink || !flink));
+}
+
+static gboolean
+backtrace_matches_ref (const Backtrace *match_bt,
+ const Backtrace *find_bt,
+ gint lines_tolerance)
+{
+ if (!match_bt || match_bt->type != BACKTRACE_TYPE_REF)
+ return FALSE;
+
+ return backtrace_matches (match_bt, find_bt, lines_tolerance);
+}
+
+static gboolean
+remove_matching_ref_backtrace (GQueue *backtraces,
+ const Backtrace *unref_backtrace)
+{
+ GList *link;
+ gint up_bts = 2, up_lines = 1;
+
+ g_return_val_if_fail (backtraces != NULL, FALSE);
+ g_return_val_if_fail (unref_backtrace != NULL, FALSE);
+ g_return_val_if_fail (unref_backtrace->type != BACKTRACE_TYPE_REF, FALSE);
+
+ if (unref_backtrace->lines && unref_backtrace->lines->next && unref_backtrace->lines->next->data) {
+ BacktraceLine *btline = unref_backtrace->lines->next->data;
+
+ if (g_strcmp0 ("g_value_object_free_value()", btline->function) == 0)
+ up_lines = 5;
+ else if (g_strcmp0 ("g_object_notify()", btline->function) == 0)
+ up_bts = 5;
+ }
+
+ for (link = g_queue_peek_tail_link (backtraces); link && up_bts > 0; link = g_list_previous (link),
up_bts--) {
+ Backtrace *bt = link->data;
+ gint inc_up_lines = 0;
+
+ if (!bt || bt->type != BACKTRACE_TYPE_REF)
+ continue;
+
+ if (bt->lines && bt->lines->next && bt->lines->next->data) {
+ BacktraceLine *btline = bt->lines->next->data;
+
+ if (g_strcmp0 ("g_weak_ref_get()", btline->function) == 0)
+ inc_up_lines = 1;
+ }
+
+ if (backtrace_matches_ref (bt, unref_backtrace, up_lines + inc_up_lines)) {
+ g_queue_delete_link (backtraces, link);
+ backtrace_free (bt);
+
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+dump_ref_unref_backtraces (gboolean is_at_exit)
+{
+ G_LOCK (ref_unref_backtraces);
+
+ if (ref_unref_backtraces) {
+ g_print ("\n----------------------------------------------------------\n");
+ if (g_queue_get_length (ref_unref_backtraces) == 0) {
+ g_print (" All ref/unref backtraces were properly matched\n");
+ } else {
+ guint count = g_queue_get_length (ref_unref_backtraces), refs = 0, unrefs = 0, others
= 0;
+ GList *link;
+
+ for (link = g_queue_peek_head_link (ref_unref_backtraces); link; link = g_list_next
(link)) {
+ Backtrace *bt = link->data;
+
+ if (!bt)
+ continue;
+
+ if (bt->type == BACKTRACE_TYPE_REF)
+ refs++;
+ else if (bt->type == BACKTRACE_TYPE_UNREF)
+ unrefs++;
+ else
+ others++;
+ }
+
+ g_print (" Left %u (ref(%u)/unref(%u)/other(%u)) backtraces of %u pushed total:\n",
count, refs, unrefs, others, total_ref_unref_backtraces);
+
+ for (count = 0, link = g_queue_peek_head_link (ref_unref_backtraces); link; link =
g_list_next (link), count++) {
+ Backtrace *bt = link->data;
+
+ if (!bt)
+ continue;
+
+ print_backtrace (bt, count);
+ }
+ }
+ g_print ("----------------------------------------------------------\n");
+ } else if (!is_at_exit) {
+ g_print ("\n----------------------------------------------------------\n");
+ g_print (" Did not receive any ref/unref backtraces yet\n");
+ g_print ("----------------------------------------------------------\n");
+ }
+
+ G_UNLOCK (ref_unref_backtraces);
+}
+
+static void
+dump_left_ref_unref_backtraces_at_exit_cb (void)
+{
+ dump_ref_unref_backtraces (TRUE);
+
+ G_LOCK (ref_unref_backtraces);
+
+ if (ref_unref_backtraces) {
+ g_queue_free_full (ref_unref_backtraces, backtrace_free);
+ ref_unref_backtraces = NULL;
+ }
+
+ G_UNLOCK (ref_unref_backtraces);
+}
+
+/**
+ * camel_debug_ref_unref_push_backtrace:
+ * @backtrace: a backtrace to push, taken from camel_debug_get_backtrace()
+ * @object_ref_count: the current object reference count when the push is done
+ *
+ * Adds this backtrace into the set of backtraces related to some object
+ * reference counting issues debugging. This is usually called inside g_object_ref()
+ * and g_object_unref(). If the backtrace corresponds to a g_object_unref()
+ * call, and a corresponding g_object_ref() backtrace is found in the current list,
+ * then the previous backtrace is removed and this one is skipped.
+ *
+ * Any left backtraces in the list are printed at the application end.
+ *
+ * A convenient function camel_debug_ref_unref_push_backtrace_for_object()
+ * is provided too.
+ *
+ * Since: 3.20
+ **/
+void
+camel_debug_ref_unref_push_backtrace (const GString *backtrace,
+ guint object_ref_count)
+{
+ Backtrace *bt;
+
+ g_return_if_fail (backtrace != NULL);
+
+ G_LOCK (ref_unref_backtraces);
+
+ total_ref_unref_backtraces++;
+
+ bt = parse_backtrace (backtrace, object_ref_count);
+ if (!bt) {
+ G_UNLOCK (ref_unref_backtraces);
+ g_warn_if_fail (bt != NULL);
+ return;
+ }
+
+ if (!ref_unref_backtraces) {
+ ref_unref_backtraces = g_queue_new ();
+ atexit (dump_left_ref_unref_backtraces_at_exit_cb);
+ }
+
+ if (bt->type != BACKTRACE_TYPE_UNREF || !remove_matching_ref_backtrace (ref_unref_backtraces, bt)) {
+ g_queue_push_tail (ref_unref_backtraces, bt);
+ } else {
+ backtrace_free (bt);
+ }
+
+ G_UNLOCK (ref_unref_backtraces);
+}
+
+/**
+ * camel_debug_ref_unref_push_backtrace_for_object:
+ * @_object: a #GObject, for which add the backtrace
+ *
+ * Gets current backtrace of this call and adds it to the list
+ * of backtraces with camel_debug_ref_unref_push_backtrace().
+ *
+ * Usual usage would be, once GNOME bug 758358 is applied to the GLib sources,
+ * or a patched GLib is used, to call this function in an object init() function,
+ * like this:
+ *
+ * static void
+ * my_object_init (MyObject *obj)
+ * {
+ * camel_debug_ref_unref_push_backtrace_for_object (obj);
+ * g_track_object_ref_unref (obj, (GFunc) camel_debug_ref_unref_push_backtrace_for_object, NULL);
+ * }
+ *
+ * Note that the g_track_object_ref_unref() can track only one pointer, thus make
+ * sure you track the right one (add some logic if multiple objects are created at once).
+ *
+ * Since: 3.20
+ **/
+void
+camel_debug_ref_unref_push_backtrace_for_object (gpointer _object)
+{
+ GString *backtrace;
+ GObject *object;
+
+ g_return_if_fail (G_IS_OBJECT (_object));
+
+ object = G_OBJECT (_object);
+
+ backtrace = camel_debug_get_backtrace ();
+ if (backtrace) {
+ camel_debug_ref_unref_push_backtrace (backtrace, object->ref_count);
+ g_string_free (backtrace, TRUE);
+ }
+}
+
+/**
+ * camel_debug_ref_unref_dump_backtraces:
+ *
+ * Prints current backtraces stored with camel_debug_ref_unref_push_backtrace()
+ * or with camel_debug_ref_unref_push_backtrace_for_object().
+ *
+ * It's usually not needed to use this function, as the left backtraces, if any,
+ * are printed at the end of the application.
+ *
+ * Since: 3.20
+ **/
+void
+camel_debug_ref_unref_dump_backtraces (void)
+{
+ dump_ref_unref_backtraces (FALSE);
+}
diff --git a/camel/camel-debug.h b/camel/camel-debug.h
index 4f3d5ae..d39725f 100644
--- a/camel/camel-debug.h
+++ b/camel/camel-debug.h
@@ -140,6 +140,14 @@ void camel_pointer_tracker_dump (void);
GString * camel_debug_get_backtrace (void);
+void camel_debug_ref_unref_push_backtrace
+ (const GString *backtrace,
+ guint object_ref_count);
+void camel_debug_ref_unref_push_backtrace_for_object
+ (gpointer _object);
+void camel_debug_ref_unref_dump_backtraces
+ (void);
+
G_END_DECLS
#endif /* CAMEL_DEBUG_H */
diff --git a/docs/reference/camel/camel-sections.txt b/docs/reference/camel/camel-sections.txt
index 8af238f..212d940 100644
--- a/docs/reference/camel/camel-sections.txt
+++ b/docs/reference/camel/camel-sections.txt
@@ -2657,6 +2657,9 @@ camel_pointer_tracker_track_with_info
camel_pointer_tracker_untrack
camel_pointer_tracker_dump
camel_debug_get_backtrace
+camel_debug_ref_unref_push_backtrace
+camel_debug_ref_unref_push_backtrace_for_object
+camel_debug_ref_unref_dump_backtraces
</SECTION>
<SECTION>
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]