gtask: Track pending GTasks if G_ENABLE_DEBUG is defined

Track the `GTask`s which are still alive (not finalised) in a shared
list, and provide a secret debugging function for printing that list.

Too often when debugging apps, I have found that a ‘leaked’ object is
actually still (validly) referenced by an ongoing `GTask` which hasn’t
completed for whatever reason. Or I have found that an operation has
obviously stalled, but there are no pointers available to the `GTask`
which is stalled, because it’s being tracked as a collection of closure
pointers from some `GSource` which is hard to get to in the debugger.

It will be very useful for debugging apps, if there’s a list of all the
still alive `GTask`s somewhere. This is that list.

The code is disabled if `G_ENABLE_DEBUG` is not defined, to avoid every
`GTask` construction/finalisation imposing a global locking penalty.

To use the new list, break in `gdb` while running your app, and call
`g_task_print_alive_tasks()`, or inspect the `task_list` manually:
```
(gdb) print g_task_print_alive_tasks()
16:44:17:788 GLib-GIO 5 GTasks still alive:
 • GTask 0x6100000ac740, gs_plugin_appstream_setup_async, ref count: 1, ever_returned: 0, completed: 0
 • GTask 0x6100000bf940, [gio] D-Bus read, ref count: 2, ever_returned: 0, completed: 0
 • GTask 0x6100000aac40, gs_plugin_loader_setup_async, ref count: 1, ever_returned: 0, completed: 0
 • GTask 0x61000006d940, gs_plugin_loader_job_process_async GsPluginJobRefine, ref count: 1, ever_returned: 0, completed: 0
 • GTask 0x610000118c40, [gio] D-Bus read, ref count: 2, ever_returned: 0, completed: 0
```

Signed-off-by: Philip Withnall <pwithnall@endlessos.org>
This commit is contained in:
Philip Withnall 2023-04-05 15:10:10 +01:00
parent 8d527919b4
commit 06eecda823
2 changed files with 71 additions and 0 deletions

View File

@ -676,6 +676,58 @@ g_task_init (GTask *task)
task->check_cancellable = TRUE;
}
#ifdef G_ENABLE_DEBUG
G_LOCK_DEFINE_STATIC (task_list);
static GPtrArray *task_list = NULL;
void
g_task_print_alive_tasks (void)
{
GString *message_str = g_string_new ("");
G_LOCK (task_list);
if (task_list != NULL)
{
g_string_append_printf (message_str, "%u GTasks still alive:", task_list->len);
for (guint i = 0; i < task_list->len; i++)
{
GTask *task = g_ptr_array_index (task_list, i);
const gchar *name = g_task_get_name (task);
g_string_append_printf (message_str,
"\n • GTask %p, %s, ref count: %u, ever_returned: %u, completed: %u",
task, (name != NULL) ? name : "(no name set)",
((GObject *) task)->ref_count,
task->ever_returned, task->completed);
}
}
else
{
g_string_append (message_str, "No GTasks still alive");
}
G_UNLOCK (task_list);
g_message ("%s", message_str->str);
g_string_free (message_str, TRUE);
}
static void
g_task_constructed (GObject *object)
{
GTask *task = G_TASK (object);
G_OBJECT_CLASS (g_task_parent_class)->constructed (object);
/* Track pending tasks for debugging purposes */
G_LOCK (task_list);
if (G_UNLIKELY (task_list == NULL))
task_list = g_ptr_array_new ();
g_ptr_array_add (task_list, task);
G_UNLOCK (task_list);
}
#endif /* G_ENABLE_DEBUG */
static void
g_task_finalize (GObject *object)
{
@ -732,6 +784,16 @@ g_task_finalize (GObject *object)
g_cond_clear (&task->cond);
}
/* Track pending tasks for debugging purposes */
#ifdef G_ENABLE_DEBUG
G_LOCK (task_list);
g_assert (task_list != NULL);
g_ptr_array_remove_fast (task_list, task);
if (G_UNLIKELY (task_list->len == 0))
g_clear_pointer (&task_list, g_ptr_array_unref);
G_UNLOCK (task_list);
#endif /* G_ENABLE_DEBUG */
G_OBJECT_CLASS (g_task_parent_class)->finalize (object);
}
@ -2316,6 +2378,9 @@ g_task_class_init (GTaskClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
#ifdef G_ENABLE_DEBUG
gobject_class->constructed = g_task_constructed;
#endif
gobject_class->get_property = g_task_get_property;
gobject_class->finalize = g_task_finalize;

View File

@ -194,6 +194,12 @@ gboolean g_task_had_error (GTask *task);
GIO_AVAILABLE_IN_2_44
gboolean g_task_get_completed (GTask *task);
/*< private >*/
#ifndef __GTK_DOC_IGNORE__
/* Debugging API, not part of the public API */
void g_task_print_alive_tasks (void);
#endif /* !__GTK_DOC_IGNORE__ */
G_END_DECLS
#endif /* __G_TASK_H__ */