[glib] gtask: Add a GTask:completed property



commit 4f1f68e6bebc0694208bc7bf43816bde58229fd6
Author: Philip Withnall <philip withnall collabora co uk>
Date:   Thu Aug 21 19:04:04 2014 +0100

    gtask: Add a GTask:completed property
    
    This can be used to query whether the task has completed, in the sense
    that it has had a result set on it, and has already – or will soon –
    invoke its callback function.
    
    Notifications for this property are emitted immediately after the task’s
    main callback, in the same main context as that callback. This allows
    for multiple bits of code to listen for completion of the GTask, which
    opens the door for blocking on cancellation of the GTask and improved
    handling of ‘pending’ behaviour.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=743636

 docs/reference/gio/gio-sections.txt |    1 +
 gio/gtask.c                         |   88 +++++++++++++++++-
 gio/gtask.h                         |    2 +
 gio/tests/task.c                    |  175 +++++++++++++++++++++++++++++++----
 4 files changed, 245 insertions(+), 21 deletions(-)
---
diff --git a/docs/reference/gio/gio-sections.txt b/docs/reference/gio/gio-sections.txt
index d3517c6..eedeb22 100644
--- a/docs/reference/gio/gio-sections.txt
+++ b/docs/reference/gio/gio-sections.txt
@@ -4233,6 +4233,7 @@ g_task_propagate_boolean
 g_task_propagate_int
 g_task_propagate_pointer
 g_task_had_error
+g_task_get_completed
 <SUBSECTION>
 g_task_run_in_thread
 g_task_run_in_thread_sync
diff --git a/gio/gtask.c b/gio/gtask.c
index 51259bd..566f7cb 100644
--- a/gio/gtask.c
+++ b/gio/gtask.c
@@ -23,6 +23,8 @@
 #include "gasyncresult.h"
 #include "gcancellable.h"
 
+#include "glibintl.h"
+
 /**
  * SECTION:gtask
  * @short_description: Cancellable synchronous or asynchronous task
@@ -547,6 +549,7 @@ struct _GTask {
 
   GAsyncReadyCallback callback;
   gpointer callback_data;
+  gboolean completed;
 
   GTaskThreadFunc task_func;
   GMutex lock;
@@ -574,6 +577,11 @@ struct _GTaskClass
   GObjectClass parent_class;
 };
 
+typedef enum
+{
+  PROP_COMPLETED = 1,
+} GTaskProperty;
+
 static void g_task_thread_pool_resort (void);
 
 static void g_task_async_result_iface_init (GAsyncResultIface *iface);
@@ -1074,9 +1082,17 @@ static void
 g_task_return_now (GTask *task)
 {
   g_main_context_push_thread_default (task->context);
-  task->callback (task->source_object,
-                  G_ASYNC_RESULT (task),
-                  task->callback_data);
+
+  if (task->callback != NULL)
+    {
+      task->callback (task->source_object,
+                      G_ASYNC_RESULT (task),
+                      task->callback_data);
+    }
+
+  task->completed = TRUE;
+  g_object_notify (G_OBJECT (task), "completed");
+
   g_main_context_pop_thread_default (task->context);
 }
 
@@ -1103,7 +1119,7 @@ g_task_return (GTask           *task,
   if (type == G_TASK_RETURN_SUCCESS)
     task->result_set = TRUE;
 
-  if (task->synchronous || !task->callback)
+  if (task->synchronous)
     return;
 
   /* Normally we want to invoke the task's callback when its return
@@ -1354,6 +1370,7 @@ g_task_run_in_thread (GTask           *task,
  * Normally this is used with tasks created with a %NULL
  * `callback`, but note that even if the task does
  * have a callback, it will not be invoked when @task_func returns.
+ * #GTask:completed will be set to %TRUE just before this function returns.
  *
  * Since: 2.36
  */
@@ -1372,6 +1389,11 @@ g_task_run_in_thread_sync (GTask           *task,
     g_cond_wait (&task->cond, &task->lock);
 
   g_mutex_unlock (&task->lock);
+
+  /* Notify of completion in this thread. */
+  task->completed = TRUE;
+  g_object_notify (G_OBJECT (task), "completed");
+
   g_object_unref (task);
 }
 
@@ -1719,6 +1741,26 @@ g_task_had_error (GTask *task)
 }
 
 /**
+ * g_task_get_completed:
+ * @task: a #GTask.
+ *
+ * Gets the value of #GTask:completed. This changes from %FALSE to %TRUE after
+ * the task’s callback is invoked, and will return %FALSE if called from inside
+ * the callback.
+ *
+ * Returns: %TRUE if the task has completed, %FALSE otherwise.
+ *
+ * Since: 2.44
+ */
+gboolean
+g_task_get_completed (GTask *task)
+{
+  g_return_val_if_fail (G_IS_TASK (task), FALSE);
+
+  return task->completed;
+}
+
+/**
  * g_task_is_valid:
  * @result: (type Gio.AsyncResult): A #GAsyncResult
  * @source_object: (allow-none) (type GObject): the source object
@@ -1791,11 +1833,49 @@ g_task_thread_pool_resort (void)
 }
 
 static void
+g_task_get_property (GObject    *object,
+                     guint       prop_id,
+                     GValue     *value,
+                     GParamSpec *pspec)
+{
+  GTask *task = G_TASK (object);
+
+  switch ((GTaskProperty) prop_id)
+    {
+    case PROP_COMPLETED:
+      g_value_set_boolean (value, task->completed);
+      break;
+    }
+}
+
+static void
 g_task_class_init (GTaskClass *klass)
 {
   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
 
+  gobject_class->get_property = g_task_get_property;
   gobject_class->finalize = g_task_finalize;
+
+  /**
+   * GTask:completed:
+   *
+   * Whether the task has completed, meaning its callback (if set) has been
+   * invoked. This can only happen after g_task_return_pointer(),
+   * g_task_return_error() or one of the other return functions have been called
+   * on the task.
+   *
+   * This property is guaranteed to change from %FALSE to %TRUE exactly once.
+   *
+   * The #GObject::notify signal for this change is emitted in the same main
+   * context as the task’s callback, immediately after that callback is invoked.
+   *
+   * Since: 2.44
+   */
+  g_object_class_install_property (gobject_class, PROP_COMPLETED,
+    g_param_spec_boolean ("completed",
+                          P_("Task completed"),
+                          P_("Whether the task has completed yet"),
+                          FALSE, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
 }
 
 static gpointer
diff --git a/gio/gtask.h b/gio/gtask.h
index a4fd77e..446c85b 100644
--- a/gio/gtask.h
+++ b/gio/gtask.h
@@ -152,6 +152,8 @@ gssize        g_task_propagate_int             (GTask           *task,
                                                 GError         **error);
 GLIB_AVAILABLE_IN_2_36
 gboolean      g_task_had_error                 (GTask           *task);
+GLIB_AVAILABLE_IN_2_44
+gboolean      g_task_get_completed             (GTask           *task);
 
 G_END_DECLS
 
diff --git a/gio/tests/task.c b/gio/tests/task.c
index 1a1dd15..9493fed 100644
--- a/gio/tests/task.c
+++ b/gio/tests/task.c
@@ -20,6 +20,44 @@ static gssize magic;
  */
 #define g_dummy_object_new g_socket_client_new
 
+static gboolean
+idle_quit_loop (gpointer user_data)
+{
+  g_main_loop_quit (loop);
+  return FALSE;
+}
+
+static void
+completed_cb (GObject    *gobject,
+              GParamSpec *pspec,
+              gpointer    user_data)
+{
+       gboolean *notification_emitted = user_data;
+       *notification_emitted = TRUE;
+}
+
+static void
+wait_for_completed_notification (GTask *task)
+{
+  gboolean notification_emitted = FALSE;
+  gboolean is_completed = FALSE;
+
+  /* Hold a ref. so we can check the :completed property afterwards. */
+  g_object_ref (task);
+
+  g_signal_connect (task, "notify::completed",
+                    (GCallback) completed_cb, &notification_emitted);
+  g_idle_add (idle_quit_loop, NULL);
+  g_main_loop_run (loop);
+  g_assert_true (notification_emitted);
+
+  g_assert_true (g_task_get_completed (task));
+  g_object_get (G_OBJECT (task), "completed", &is_completed, NULL);
+  g_assert_true (is_completed);
+
+  g_object_unref (task);
+}
+
 /* test_basic */
 
 static void
@@ -34,6 +72,7 @@ basic_callback (GObject      *object,
   g_assert (g_task_is_valid (result, object));
   g_assert (g_async_result_get_user_data (result) == user_data);
   g_assert (!g_task_had_error (G_TASK (result)));
+  g_assert_false (g_task_get_completed (G_TASK (result)));
 
   *result_out = g_task_propagate_int (G_TASK (result), &error);
   g_assert_no_error (error);
@@ -66,16 +105,20 @@ test_basic (void)
   GTask *task;
   gssize result;
   gboolean task_data_destroyed = FALSE;
+  gboolean notification_emitted = FALSE;
 
   task = g_task_new (NULL, NULL, basic_callback, &result);
   g_task_set_task_data (task, &task_data_destroyed, basic_destroy_notify);
   g_object_add_weak_pointer (G_OBJECT (task), (gpointer *)&task);
+  g_signal_connect (task, "notify::completed",
+                    (GCallback) completed_cb, &notification_emitted);
 
   g_idle_add (basic_return, task);
   g_main_loop_run (loop);
 
   g_assert_cmpint (result, ==, magic);
   g_assert (task_data_destroyed == TRUE);
+  g_assert_true (notification_emitted);
   g_assert (task == NULL);
 }
 
@@ -93,6 +136,7 @@ error_callback (GObject      *object,
   g_assert (g_task_is_valid (result, object));
   g_assert (g_async_result_get_user_data (result) == user_data);
   g_assert (g_task_had_error (G_TASK (result)));
+  g_assert_false (g_task_get_completed (G_TASK (result)));
 
   *result_out = g_task_propagate_int (G_TASK (result), &error);
   g_assert_error (error, G_IO_ERROR, G_IO_ERROR_FAILED);
@@ -129,9 +173,12 @@ test_error (void)
   gssize result;
   gboolean first_task_data_destroyed = FALSE;
   gboolean second_task_data_destroyed = FALSE;
+  gboolean notification_emitted = FALSE;
 
   task = g_task_new (NULL, NULL, error_callback, &result);
   g_object_add_weak_pointer (G_OBJECT (task), (gpointer *)&task);
+  g_signal_connect (task, "notify::completed",
+                    (GCallback) completed_cb, &notification_emitted);
 
   g_assert (first_task_data_destroyed == FALSE);
   g_task_set_task_data (task, &first_task_data_destroyed, error_destroy_notify);
@@ -147,6 +194,7 @@ test_error (void)
 
   g_assert_cmpint (result, ==, -1);
   g_assert (second_task_data_destroyed == TRUE);
+  g_assert_true (notification_emitted);
   g_assert (task == NULL);
 }
 
@@ -155,6 +203,7 @@ test_error (void)
  * next iteration.
  */
 gboolean same_result = FALSE;
+gboolean same_notification_emitted = FALSE;
 
 static void
 same_callback (GObject      *object,
@@ -168,6 +217,7 @@ same_callback (GObject      *object,
   g_assert (g_task_is_valid (result, object));
   g_assert (g_async_result_get_user_data (result) == user_data);
   g_assert (!g_task_had_error (G_TASK (result)));
+  g_assert_false (g_task_get_completed (G_TASK (result)));
 
   *result_out = g_task_propagate_boolean (G_TASK (result), &error);
   g_assert_no_error (error);
@@ -184,6 +234,8 @@ same_start (gpointer user_data)
   task = g_task_new (NULL, NULL, same_callback, &same_result);
   *weak_pointer = task;
   g_object_add_weak_pointer (G_OBJECT (task), weak_pointer);
+  g_signal_connect (task, "notify::completed",
+                    (GCallback) completed_cb, &same_notification_emitted);
 
   g_task_return_boolean (task, TRUE);
   g_object_unref (task);
@@ -191,6 +243,7 @@ same_start (gpointer user_data)
   /* same_callback should not have been invoked yet */
   g_assert (same_result == FALSE);
   g_assert (*weak_pointer == task);
+  g_assert_false (same_notification_emitted);
 
   return FALSE;
 }
@@ -205,11 +258,13 @@ test_return_from_same_iteration (void)
 
   g_assert (same_result == TRUE);
   g_assert (weak_pointer == NULL);
+  g_assert_true (same_notification_emitted);
 }
 
 /* test_return_from_toplevel: calling g_task_return_* from outside any
  * main loop completes the task inside the main loop.
  */
+gboolean toplevel_notification_emitted = FALSE;
 
 static void
 toplevel_callback (GObject      *object,
@@ -223,6 +278,7 @@ toplevel_callback (GObject      *object,
   g_assert (g_task_is_valid (result, object));
   g_assert (g_async_result_get_user_data (result) == user_data);
   g_assert (!g_task_had_error (G_TASK (result)));
+  g_assert_false (g_task_get_completed (G_TASK (result)));
 
   *result_out = g_task_propagate_boolean (G_TASK (result), &error);
   g_assert_no_error (error);
@@ -238,6 +294,8 @@ test_return_from_toplevel (void)
 
   task = g_task_new (NULL, NULL, toplevel_callback, &result);
   g_object_add_weak_pointer (G_OBJECT (task), (gpointer *)&task);
+  g_signal_connect (task, "notify::completed",
+                    (GCallback) completed_cb, &toplevel_notification_emitted);
 
   g_task_return_boolean (task, TRUE);
   g_object_unref (task);
@@ -245,11 +303,13 @@ test_return_from_toplevel (void)
   /* toplevel_callback should not have been invoked yet */
   g_assert (result == FALSE);
   g_assert (task != NULL);
+  g_assert_false (toplevel_notification_emitted);
 
   g_main_loop_run (loop);
 
   g_assert (result == TRUE);
   g_assert (task == NULL);
+  g_assert_true (toplevel_notification_emitted);
 }
 
 /* test_return_from_anon_thread: calling g_task_return_* from a
@@ -257,6 +317,7 @@ test_return_from_toplevel (void)
  * task in the task's context/thread.
  */
 
+gboolean anon_thread_notification_emitted = FALSE;
 GThread *anon_thread;
 
 static void
@@ -271,6 +332,7 @@ anon_callback (GObject      *object,
   g_assert (g_task_is_valid (result, object));
   g_assert (g_async_result_get_user_data (result) == user_data);
   g_assert (!g_task_had_error (G_TASK (result)));
+  g_assert_false (g_task_get_completed (G_TASK (result)));
 
   g_assert (g_thread_self () == main_thread);
 
@@ -309,6 +371,9 @@ test_return_from_anon_thread (void)
 
   task = g_task_new (NULL, NULL, anon_callback, &result);
   g_object_add_weak_pointer (G_OBJECT (task), (gpointer *)&task);
+  g_signal_connect (task, "notify::completed",
+                    (GCallback) completed_cb,
+                    &anon_thread_notification_emitted);
 
   g_idle_add (anon_start, task);
   g_main_loop_run (loop);
@@ -317,6 +382,7 @@ test_return_from_anon_thread (void)
 
   g_assert_cmpint (result, ==, magic);
   g_assert (task == NULL);
+  g_assert_true (anon_thread_notification_emitted);
 }
 
 /* test_return_from_wrong_thread: calling g_task_return_* from a
@@ -324,6 +390,7 @@ test_return_from_anon_thread (void)
  * task in the task's context/thread.
  */
 
+gboolean wrong_thread_notification_emitted = FALSE;
 GThread *wrong_thread;
 
 static void
@@ -338,6 +405,7 @@ wrong_callback (GObject      *object,
   g_assert (g_task_is_valid (result, object));
   g_assert (g_async_result_get_user_data (result) == user_data);
   g_assert (!g_task_had_error (G_TASK (result)));
+  g_assert_false (g_task_get_completed (G_TASK (result)));
 
   g_assert (g_thread_self () == main_thread);
 
@@ -385,6 +453,9 @@ test_return_from_wrong_thread (void)
 
   task = g_task_new (NULL, NULL, wrong_callback, &result);
   g_object_add_weak_pointer (G_OBJECT (task), (gpointer *)&task);
+  g_signal_connect (task, "notify::completed",
+                    (GCallback) completed_cb,
+                    &wrong_thread_notification_emitted);
 
   g_idle_add (wrong_start, task);
   g_main_loop_run (loop);
@@ -393,6 +464,7 @@ test_return_from_wrong_thread (void)
 
   g_assert_cmpint (result, ==, magic);
   g_assert (task == NULL);
+  g_assert_true (wrong_thread_notification_emitted);
 }
 
 /* test_no_callback */
@@ -408,16 +480,17 @@ test_no_callback (void)
   g_task_return_boolean (task, TRUE);
   g_object_unref (task);
 
-  /* Since there's no callback, g_task_return_boolean() will
-   * not have queued an idle source and taken a ref on task,
-   * so we just dropped the last ref.
-   */
-  g_assert (task == NULL);
+  /* Even though there’s no callback, the :completed notification has to
+   * happen in an idle handler. */
+  g_assert_nonnull (task);
+  wait_for_completed_notification (task);
+  g_assert_null (task);
 }
 
 /* test_report_error */
 
 static void test_report_error (void);
+gboolean error_notification_emitted = FALSE;
 
 static void
 report_callback (GObject      *object,
@@ -434,6 +507,7 @@ report_callback (GObject      *object,
   g_assert (g_async_result_is_tagged (result, test_report_error));
   g_assert (g_task_get_source_tag (G_TASK (result)) == test_report_error);
   g_assert (g_task_had_error (G_TASK (result)));
+  g_assert_false (g_task_get_completed (G_TASK (result)));
 
   ret = g_task_propagate_int (G_TASK (result), &error);
   g_assert_error (error, G_IO_ERROR, G_IO_ERROR_FAILED);
@@ -442,6 +516,8 @@ report_callback (GObject      *object,
 
   *weak_pointer = result;
   g_object_add_weak_pointer (G_OBJECT (result), weak_pointer);
+  g_signal_connect (result, "notify::completed",
+                    (GCallback) completed_cb, &error_notification_emitted);
 
   g_main_loop_quit (loop);
 }
@@ -458,6 +534,7 @@ test_report_error (void)
   g_main_loop_run (loop);
 
   g_assert (weak_pointer == NULL);
+  g_assert_true (error_notification_emitted);
 }
 
 /* test_priority: tasks complete in priority order */
@@ -476,6 +553,7 @@ priority_callback (GObject      *object,
   g_assert (g_task_is_valid (result, object));
   g_assert (g_async_result_get_user_data (result) == user_data);
   g_assert (!g_task_had_error (G_TASK (result)));
+  g_assert_false (g_task_get_completed (G_TASK (result)));
 
   g_task_propagate_boolean (G_TASK (result), &error);
   g_assert_no_error (error);
@@ -614,6 +692,7 @@ return_if_cancelled_callback (GObject      *object,
   g_assert (g_task_is_valid (result, object));
   g_assert (g_async_result_get_user_data (result) == user_data);
   g_assert (g_task_had_error (G_TASK (result)));
+  g_assert_false (g_task_get_completed (G_TASK (result)));
 
   g_task_propagate_boolean (G_TASK (result), &error);
   g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
@@ -628,24 +707,37 @@ test_return_if_cancelled (void)
   GTask *task;
   GCancellable *cancellable;
   gboolean cancelled;
+  gboolean notification_emitted = FALSE;
 
   cancellable = g_cancellable_new ();
 
   task = g_task_new (NULL, cancellable, return_if_cancelled_callback, NULL);
+  g_signal_connect (task, "notify::completed",
+                    (GCallback) completed_cb, &notification_emitted);
+
   g_cancellable_cancel (cancellable);
   cancelled = g_task_return_error_if_cancelled (task);
   g_assert (cancelled);
+  g_assert_false (notification_emitted);
   g_main_loop_run (loop);
   g_object_unref (task);
+  g_assert_true (notification_emitted);
   g_cancellable_reset (cancellable);
 
+  notification_emitted = FALSE;
+
   task = g_task_new (NULL, cancellable, return_if_cancelled_callback, NULL);
+  g_signal_connect (task, "notify::completed",
+                    (GCallback) completed_cb, &notification_emitted);
+
   g_task_set_check_cancellable (task, FALSE);
   g_cancellable_cancel (cancellable);
   cancelled = g_task_return_error_if_cancelled (task);
   g_assert (cancelled);
+  g_assert_false (notification_emitted);
   g_main_loop_run (loop);
   g_object_unref (task);
+  g_assert_true (notification_emitted);
   g_object_unref (cancellable);
 }
 
@@ -681,6 +773,7 @@ run_in_thread_callback (GObject      *object,
   g_assert (g_task_is_valid (result, object));
   g_assert (g_async_result_get_user_data (result) == user_data);
   g_assert (!g_task_had_error (G_TASK (result)));
+  g_assert_false (g_task_get_completed (G_TASK (result)));
 
   ret = g_task_propagate_int (G_TASK (result), &error);
   g_assert_no_error (error);
@@ -701,6 +794,7 @@ run_in_thread_thread (GTask        *task,
   g_assert (source_object == g_task_get_source_object (task));
   g_assert (task_data == g_task_get_task_data (task));
   g_assert (cancellable == g_task_get_cancellable (task));
+  g_assert_false (g_task_get_completed (task));
 
   g_assert (g_thread_self () != main_thread);
 
@@ -718,10 +812,13 @@ test_run_in_thread (void)
   GTask *task;
   volatile gboolean thread_ran = FALSE;
   volatile gboolean weak_notify_ran = FALSE;
+  gboolean notification_emitted = FALSE;
   gboolean done = FALSE;
 
   task = g_task_new (NULL, NULL, run_in_thread_callback, &done);
   g_object_weak_ref (G_OBJECT (task), task_weak_notify, (gpointer)&weak_notify_ran);
+  g_signal_connect (task, "notify::completed",
+                    (GCallback) completed_cb, &notification_emitted);
 
   g_task_set_task_data (task, (gpointer)&thread_ran, NULL);
   g_task_run_in_thread (task, run_in_thread_thread);
@@ -738,6 +835,7 @@ test_run_in_thread (void)
   g_main_loop_run (loop);
 
   g_assert (done == TRUE);
+  g_assert_true (notification_emitted);
 
   g_mutex_lock (&run_in_thread_mutex);
   while (!weak_notify_ran)
@@ -767,6 +865,7 @@ run_in_thread_sync_thread (GTask        *task,
   g_assert (source_object == g_task_get_source_object (task));
   g_assert (task_data == g_task_get_task_data (task));
   g_assert (cancellable == g_task_get_cancellable (task));
+  g_assert_false (g_task_get_completed (task));
 
   g_assert (g_thread_self () != main_thread);
 
@@ -780,9 +879,13 @@ test_run_in_thread_sync (void)
   GTask *task;
   gboolean thread_ran = FALSE;
   gssize ret;
+  gboolean notification_emitted = FALSE;
   GError *error = NULL;
 
   task = g_task_new (NULL, NULL, run_in_thread_sync_callback, NULL);
+  g_signal_connect (task, "notify::completed",
+                    (GCallback) completed_cb,
+                    &notification_emitted);
 
   g_task_set_task_data (task, &thread_ran, NULL);
   g_task_run_in_thread_sync (task, run_in_thread_sync_thread);
@@ -790,6 +893,8 @@ test_run_in_thread_sync (void)
   g_assert (thread_ran == TRUE);
   g_assert (task != NULL);
   g_assert (!g_task_had_error (task));
+  g_assert_true (g_task_get_completed (task));
+  g_assert_true (notification_emitted);
 
   ret = g_task_propagate_int (task, &error);
   g_assert_no_error (error);
@@ -817,6 +922,7 @@ quit_main_loop_callback (GObject      *object,
   g_assert (g_task_is_valid (result, object));
   g_assert (g_async_result_get_user_data (result) == user_data);
   g_assert (!g_task_had_error (G_TASK (result)));
+  g_assert_false (g_task_get_completed (G_TASK (result)));
 
   ret = g_task_propagate_boolean (G_TASK (result), &error);
   g_assert_no_error (error);
@@ -1018,6 +1124,7 @@ return_on_cancel_callback (GObject      *object,
   g_assert (g_task_is_valid (result, object));
   g_assert (g_async_result_get_user_data (result) == user_data);
   g_assert (g_task_had_error (G_TASK (result)));
+  g_assert_false (g_task_get_completed (G_TASK (result)));
 
   ret = g_task_propagate_int (G_TASK (result), &error);
   g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
@@ -1070,6 +1177,7 @@ test_return_on_cancel (void)
   volatile ThreadState thread_state;
   volatile gboolean weak_notify_ran = FALSE;
   gboolean callback_ran;
+  gboolean notification_emitted = FALSE;
 
   cancellable = g_cancellable_new ();
 
@@ -1079,6 +1187,8 @@ test_return_on_cancel (void)
   callback_ran = FALSE;
   thread_state = THREAD_STARTING;
   task = g_task_new (NULL, cancellable, return_on_cancel_callback, &callback_ran);
+  g_signal_connect (task, "notify::completed",
+                    (GCallback) completed_cb, &notification_emitted);
 
   g_task_set_task_data (task, (gpointer)&thread_state, NULL);
   g_mutex_lock (&roc_init_mutex);
@@ -1099,14 +1209,18 @@ test_return_on_cancel (void)
 
   g_assert (thread_state == THREAD_COMPLETED);
   g_assert (callback_ran == TRUE);
+  g_assert_true (notification_emitted);
 
   g_cancellable_reset (cancellable);
 
   /* If return-on-cancel is TRUE, it does return early */
   callback_ran = FALSE;
+  notification_emitted = FALSE;
   thread_state = THREAD_STARTING;
   task = g_task_new (NULL, cancellable, return_on_cancel_callback, &callback_ran);
   g_object_weak_ref (G_OBJECT (task), task_weak_notify, (gpointer)&weak_notify_ran);
+  g_signal_connect (task, "notify::completed",
+                    (GCallback) completed_cb, &notification_emitted);
   g_task_set_return_on_cancel (task, TRUE);
 
   g_task_set_task_data (task, (gpointer)&thread_state, NULL);
@@ -1139,14 +1253,18 @@ test_return_on_cancel (void)
     g_cond_wait (&run_in_thread_cond, &run_in_thread_mutex);
   g_mutex_unlock (&run_in_thread_mutex);
 
+  g_assert_true (notification_emitted);
   g_cancellable_reset (cancellable);
 
   /* If the task is already cancelled before it starts, it returns
    * immediately, but the thread func still runs.
    */
   callback_ran = FALSE;
+  notification_emitted = FALSE;
   thread_state = THREAD_STARTING;
   task = g_task_new (NULL, cancellable, return_on_cancel_callback, &callback_ran);
+  g_signal_connect (task, "notify::completed",
+                    (GCallback) completed_cb, &notification_emitted);
   g_task_set_return_on_cancel (task, TRUE);
 
   g_cancellable_cancel (cancellable);
@@ -1171,6 +1289,7 @@ test_return_on_cancel (void)
   g_mutex_unlock (&roc_finish_mutex);
 
   g_assert (thread_state == THREAD_CANCELLED);
+  g_assert_true (notification_emitted);
 
   g_object_unref (cancellable);
 }
@@ -1325,6 +1444,7 @@ return_on_cancel_atomic_callback (GObject      *object,
   g_assert (g_task_is_valid (result, object));
   g_assert (g_async_result_get_user_data (result) == user_data);
   g_assert (g_task_had_error (G_TASK (result)));
+  g_assert_false (g_task_get_completed (G_TASK (result)));
 
   ret = g_task_propagate_int (G_TASK (result), &error);
   g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
@@ -1335,13 +1455,6 @@ return_on_cancel_atomic_callback (GObject      *object,
   g_main_loop_quit (loop);
 }
 
-static gboolean
-idle_quit_loop (gpointer user_data)
-{
-  g_main_loop_quit (loop);
-  return FALSE;
-}
-
 static void
 return_on_cancel_atomic_thread (GTask        *task,
                                 gpointer      source_object,
@@ -1353,6 +1466,7 @@ return_on_cancel_atomic_thread (GTask        *task,
   g_assert (source_object == g_task_get_source_object (task));
   g_assert (task_data == g_task_get_task_data (task));
   g_assert (cancellable == g_task_get_cancellable (task));
+  g_assert_false (g_task_get_completed (task));
 
   g_assert (g_thread_self () != main_thread);
   g_assert_cmpint (*state, ==, 0);
@@ -1395,6 +1509,7 @@ test_return_on_cancel_atomic (void)
   GTask *task;
   GCancellable *cancellable;
   volatile gint state;
+  gboolean notification_emitted = FALSE;
   gboolean callback_ran;
 
   cancellable = g_cancellable_new ();
@@ -1406,6 +1521,8 @@ test_return_on_cancel_atomic (void)
   callback_ran = FALSE;
   task = g_task_new (NULL, cancellable, return_on_cancel_atomic_callback, &callback_ran);
   g_task_set_return_on_cancel (task, TRUE);
+  g_signal_connect (task, "notify::completed",
+                    (GCallback) completed_cb, &notification_emitted);
 
   g_task_set_task_data (task, (gpointer)&state, NULL);
   g_task_run_in_thread (task, return_on_cancel_atomic_thread);
@@ -1435,6 +1552,7 @@ test_return_on_cancel_atomic (void)
   g_assert (callback_ran == FALSE);
   g_main_loop_run (loop);
   g_assert (callback_ran == TRUE);
+  g_assert_true (notification_emitted);
 
   g_cancellable_reset (cancellable);
 
@@ -1445,8 +1563,11 @@ test_return_on_cancel_atomic (void)
    */
   state = 0;
   callback_ran = FALSE;
+  notification_emitted = FALSE;
   task = g_task_new (NULL, cancellable, return_on_cancel_atomic_callback, &callback_ran);
   g_task_set_return_on_cancel (task, TRUE);
+  g_signal_connect (task, "notify::completed",
+                    (GCallback) completed_cb, &notification_emitted);
 
   g_task_set_task_data (task, (gpointer)&state, NULL);
   g_task_run_in_thread (task, return_on_cancel_atomic_thread);
@@ -1476,6 +1597,7 @@ test_return_on_cancel_atomic (void)
 
   g_main_loop_run (loop);
   g_assert (callback_ran == TRUE);
+  g_assert_true (notification_emitted);
 
   while (state == 5)
     g_cond_wait (&roca_cond_2, &roca_mutex_2);
@@ -1508,9 +1630,15 @@ test_return_pointer (void)
   g_task_return_pointer (task, object, g_object_unref);
   g_assert_cmpint (object->ref_count, ==, 1);
 
+  /* Task and object are reffed until the :completed notification in idle. */
   g_object_unref (task);
-  g_assert (task == NULL);
-  g_assert (object == NULL);
+  g_assert_nonnull (task);
+  g_assert_nonnull (object);
+
+  wait_for_completed_notification (task);
+
+  g_assert_null (task);
+  g_assert_null (object);
 
   /* Likewise, if the return value is overwritten by an error */
   object = (GObject *)g_dummy_object_new ();
@@ -1533,8 +1661,13 @@ test_return_pointer (void)
 
   g_object_unref (task);
   g_object_unref (cancellable);
-  g_assert (task == NULL);
-  g_assert (object == NULL);
+  g_assert_nonnull (task);
+  g_assert_nonnull (object);
+
+  wait_for_completed_notification (task);
+
+  g_assert_null (task);
+  g_assert_null (object);
   
   /* If we read back the return value, we steal its ref */
   object = (GObject *)g_dummy_object_new ();
@@ -1552,10 +1685,13 @@ test_return_pointer (void)
   g_assert_cmpint (object->ref_count, ==, 1);
 
   g_object_unref (task);
-  g_assert (task == NULL);
+  g_assert_nonnull (task);
   g_assert_cmpint (object->ref_count, ==, 1);
   g_object_unref (object);
   g_assert (object == NULL);
+
+  wait_for_completed_notification (task);
+  g_assert_null (task);
 }
 
 /* test_object_keepalive: GTask takes a ref on its source object */
@@ -1574,6 +1710,7 @@ keepalive_callback (GObject      *object,
   g_assert (g_task_is_valid (result, object));
   g_assert (g_async_result_get_user_data (result) == user_data);
   g_assert (!g_task_had_error (G_TASK (result)));
+  g_assert_false (g_task_get_completed (G_TASK (result)));
 
   *result_out = g_task_propagate_int (G_TASK (result), &error);
   g_assert_no_error (error);
@@ -1588,12 +1725,15 @@ test_object_keepalive (void)
   GTask *task;
   gssize result;
   int ref_count;
+  gboolean notification_emitted = FALSE;
 
   keepalive_object = object = (GObject *)g_dummy_object_new ();
   g_object_add_weak_pointer (object, (gpointer *)&object);
 
   task = g_task_new (object, NULL, keepalive_callback, &result);
   g_object_add_weak_pointer (G_OBJECT (task), (gpointer *)&task);
+  g_signal_connect (task, "notify::completed",
+                    (GCallback) completed_cb, &notification_emitted);
 
   ref_count = object->ref_count;
   g_assert_cmpint (ref_count, >, 1);
@@ -1611,6 +1751,7 @@ test_object_keepalive (void)
 
   g_assert (object != NULL);
   g_assert_cmpint (result, ==, magic);
+  g_assert_true (notification_emitted);
 
   g_object_unref (task);
   g_assert (task == NULL);


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]