summaryrefslogtreecommitdiff
path: root/gio/gtask.c
diff options
context:
space:
mode:
Diffstat (limited to 'gio/gtask.c')
-rw-r--r--gio/gtask.c193
1 files changed, 172 insertions, 21 deletions
diff --git a/gio/gtask.c b/gio/gtask.c
index d0f8b4e33..c436110aa 100644
--- a/gio/gtask.c
+++ b/gio/gtask.c
@@ -64,6 +64,10 @@
* #GTask was constructed to be running at least until the task has completed
* and its data has been freed.
*
+ * If a #GTask has been constructed and its callback set, it is an error to
+ * not call `g_task_return_*()` on it. GLib will warn at runtime if this happens
+ * (since 2.76).
+ *
* Here is an example for using GTask as a GAsyncResult:
* |[<!-- language="C" -->
* typedef struct {
@@ -538,6 +542,24 @@
* having come from the `_async()` wrapper
* function (for "short-circuit" results, such as when passing
* 0 to g_input_stream_read_async()).
+ *
+ * ## Thread-safety considerations
+ *
+ * Due to some infelicities in the API design, there is a
+ * thread-safety concern that users of GTask have to be aware of:
+ *
+ * If the `main` thread drops its last reference to the source object
+ * or the task data before the task is finalized, then the finalizers
+ * of these objects may be called on the worker thread.
+ *
+ * This is a problem if the finalizers use non-threadsafe API, and
+ * can lead to hard-to-debug crashes. Possible workarounds include:
+ *
+ * - Clear task data in a signal handler for `notify::completed`
+ *
+ * - Keep iterating a main context in the main thread and defer
+ * dropping the reference to the source object to that main
+ * context when the task is finalized
*/
/**
@@ -573,21 +595,22 @@ struct _GTask {
gboolean thread_cancelled;
/* Protected by the lock when task is threaded: */
- gboolean thread_complete : 1;
- gboolean return_on_cancel : 1;
- gboolean : 0;
+ guint thread_complete : 1;
+ guint return_on_cancel : 1;
+ guint : 0;
/* Unprotected, but written to when task runs in thread: */
- gboolean completed : 1;
- gboolean had_error : 1;
- gboolean result_set : 1;
- gboolean ever_returned : 1;
- gboolean : 0;
+ guint completed : 1;
+ guint had_error : 1;
+ guint result_set : 1;
+ guint ever_returned : 1;
+ guint : 0;
/* Read-only once task runs in thread: */
- gboolean check_cancellable : 1;
- gboolean synchronous : 1;
- gboolean blocking_other_task : 1;
+ guint check_cancellable : 1;
+ guint synchronous : 1;
+ guint blocking_other_task : 1;
+ guint name_is_static : 1;
GError *error;
union {
@@ -653,14 +676,95 @@ 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)
{
GTask *task = G_TASK (object);
+ /* Warn if a #GTask is finalised without g_task_return() ever having been
+ * called on it.
+ *
+ * Tasks without a callback or which are run in g_task_run_in_thread{,_sync}()
+ * only trigger a debug message as that’s sometimes used as a pattern for
+ * running work in a worker thread without caring about the result. */
+ if (!task->ever_returned)
+ {
+ gchar *owned_task_name = NULL;
+ const gchar *task_name = g_task_get_name (task);
+
+ if (task_name == NULL)
+ task_name = owned_task_name = g_strdup_printf ("%p", task);
+
+ if (task->callback != NULL && !G_TASK_IS_THREADED (task))
+ g_critical ("GTask %s (source object: %p, source tag: %p) finalized without "
+ "ever returning (using g_task_return_*()). This potentially "
+ "indicates a bug in the program.",
+ task_name, task->source_object, task->source_tag);
+ else
+ g_debug ("GTask %s (source object: %p, source tag: %p) finalized without "
+ "ever returning (using g_task_return_*()). This potentially "
+ "indicates a bug in the program.",
+ task_name, task->source_object, task->source_tag);
+
+ g_free (owned_task_name);
+ }
+
g_clear_object (&task->source_object);
g_clear_object (&task->cancellable);
- g_free (task->name);
+ if (!task->name_is_static)
+ g_free (task->name);
if (task->context)
g_main_context_unref (task->context);
@@ -680,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);
}
@@ -769,7 +883,7 @@ g_task_report_error (gpointer source_object,
task = g_task_new (source_object, NULL, callback, callback_data);
g_task_set_source_tag (task, source_tag);
- g_task_set_name (task, G_STRFUNC);
+ g_task_set_static_name (task, G_STRFUNC);
g_task_return_error (task, error);
g_object_unref (task);
}
@@ -1028,16 +1142,41 @@ void
* Since: 2.60
*/
void
-g_task_set_name (GTask *task,
- const gchar *name)
+(g_task_set_name) (GTask *task,
+ const char *name)
{
- gchar *new_name;
+ char *new_name;
g_return_if_fail (G_IS_TASK (task));
new_name = g_strdup (name);
- g_free (task->name);
+ if (!task->name_is_static)
+ g_free (task->name);
task->name = g_steal_pointer (&new_name);
+ task->name_is_static = FALSE;
+}
+
+/**
+ * g_task_set_static_name:
+ * @task: a #GTask
+ * @name: (nullable): a human readable name for the task. Must be a string literal
+ *
+ * Sets @task’s name, used in debugging and profiling.
+ *
+ * This is a variant of g_task_set_name() that avoids copying @name.
+ *
+ * Since: 2.76
+ */
+void
+g_task_set_static_name (GTask *task,
+ const char *name)
+{
+ g_return_if_fail (G_IS_TASK (task));
+
+ if (!task->name_is_static)
+ g_free (task->name);
+ task->name = (char *) name;
+ task->name_is_static = TRUE;
}
/**
@@ -1136,7 +1275,7 @@ g_task_get_context (GTask *task)
*
* Gets @task's #GCancellable
*
- * Returns: (transfer none): @task's #GCancellable
+ * Returns: (nullable) (transfer none): @task's #GCancellable
*
* Since: 2.36
*/
@@ -1530,7 +1669,8 @@ g_task_start_task_thread (GTask *task,
g_signal_connect_data (task->cancellable, "cancelled",
G_CALLBACK (task_thread_cancelled),
g_object_ref (task),
- task_thread_cancelled_disconnect_notify, 0);
+ task_thread_cancelled_disconnect_notify,
+ G_CONNECT_DEFAULT);
}
if (g_private_get (&task_private))
@@ -1556,6 +1696,13 @@ g_task_start_task_thread (GTask *task,
* tasks), but don't want them to all run at once, you should only queue a
* limited number of them (around ten) at a time.
*
+ * Be aware that if your task depends on other tasks to complete, use of this
+ * function could lead to a livelock if the other tasks also use this function
+ * and enough of them (around 10) execute in a dependency chain, as that will
+ * exhaust the thread pool. If this situation is possible, consider using a
+ * separate worker thread or thread pool explicitly, rather than using
+ * g_task_run_in_thread().
+ *
* Since: 2.36
*/
void
@@ -1644,7 +1791,8 @@ g_task_run_in_thread_sync (GTask *task,
* callback to @callback, with @task as the callback's `user_data`.
*
* It will set the @source’s name to the task’s name (as set with
- * g_task_set_name()), if one has been set.
+ * g_task_set_name()), if one has been set on the task and the source doesn’t
+ * yet have a name.
*
* This takes a reference on @task until @source is destroyed.
*
@@ -1660,7 +1808,7 @@ g_task_attach_source (GTask *task,
g_source_set_callback (source, callback,
g_object_ref (task), g_object_unref);
g_source_set_priority (source, task->priority);
- if (task->name != NULL)
+ if (task->name != NULL && g_source_get_name (source) == NULL)
g_source_set_name (source, task->name);
g_source_attach (source, task->context);
@@ -2230,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;