[glib/wip/task: 1/11] GTask: new GAsyncResult implementation / threaded task manager
- From: Dan Winship <danw src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [glib/wip/task: 1/11] GTask: new GAsyncResult implementation / threaded task manager
- Date: Thu, 2 Aug 2012 20:33:47 +0000 (UTC)
commit 8f87ab3a569455b31e7c376677198deea962d1b1
Author: Dan Winship <danw gnome org>
Date: Wed Oct 5 10:05:50 2011 -0400
GTask: new GAsyncResult implementation / threaded task manager
GTask is a replacement for GSimpleAsyncResult and GIOScheduler, that
also allows for making cancellable wrappers around non-cancellable
functions (as in GThreadedResolver).
https://bugzilla.gnome.org/show_bug.cgi?id=661767
docs/reference/gio/gio-docs.xml | 1 +
docs/reference/gio/gio-sections.txt | 52 ++
gio/Makefile.am | 2 +
gio/gio.h | 1 +
gio/gio.symbols | 31 +
gio/giotypes.h | 1 +
gio/gtask.c | 1667 ++++++++++++++++++++++++++++++++++
gio/gtask.h | 160 ++++
gio/tests/.gitignore | 1 +
gio/tests/Makefile.am | 4 +
gio/tests/task.c | 1668 +++++++++++++++++++++++++++++++++++
11 files changed, 3588 insertions(+), 0 deletions(-)
---
diff --git a/docs/reference/gio/gio-docs.xml b/docs/reference/gio/gio-docs.xml
index f5c599d..f1ad8a3 100644
--- a/docs/reference/gio/gio-docs.xml
+++ b/docs/reference/gio/gio-docs.xml
@@ -42,6 +42,7 @@
<xi:include href="xml/gasyncresult.xml"/>
<xi:include href="xml/gioscheduler.xml"/>
<xi:include href="xml/gsimpleasyncresult.xml"/>
+ <xi:include href="xml/gtask.xml"/>
</chapter>
<chapter id="conversion">
<title>Data conversion</title>
diff --git a/docs/reference/gio/gio-sections.txt b/docs/reference/gio/gio-sections.txt
index 991cc32..d478212 100644
--- a/docs/reference/gio/gio-sections.txt
+++ b/docs/reference/gio/gio-sections.txt
@@ -3906,3 +3906,55 @@ g_test_dbus_unset
g_test_dbus_get_type
g_test_dbus_flags_get_type
</SECTION>
+
+<SECTION>
+<FILE>gtask</FILE>
+<TITLE>GTask</TITLE>
+g_task_new
+g_task_set_task_data
+g_task_set_priority
+g_task_set_check_cancellable
+g_task_set_return_on_cancel
+g_task_set_source_tag
+<SUBSECTION>
+g_task_report_error
+g_task_report_new_error
+<SUBSECTION>
+g_task_get_task_data
+g_task_get_priority
+g_task_get_cancellable
+g_task_get_check_cancellable
+g_task_get_return_on_cancel
+g_task_get_context
+g_task_get_source_object
+g_task_get_source_tag
+<SUBSECTION>
+g_task_return_boolean
+g_task_return_int
+g_task_return_pointer
+g_task_return_error
+g_task_return_new_error
+g_task_return_error_if_cancelled
+<SUBSECTION>
+g_task_propagate_boolean
+g_task_propagate_int
+g_task_propagate_pointer
+g_task_had_error
+<SUBSECTION>
+g_task_run_in_thread
+g_task_run_in_thread_sync
+GTaskThreadFunc
+g_task_attach_source
+<SUBSECTION>
+g_task_is_valid
+<SUBSECTION Standard>
+GTaskClass
+GTaskPrivate
+G_TYPE_TASK
+G_TASK
+G_IS_TASK
+G_TASK_CLASS
+G_IS_TASK_CLASS
+G_TASK_GET_CLASS
+g_task_get_type
+</SECTION>
diff --git a/gio/Makefile.am b/gio/Makefile.am
index 20bcd81..cbafbf6 100644
--- a/gio/Makefile.am
+++ b/gio/Makefile.am
@@ -430,6 +430,7 @@ libgio_2_0_la_SOURCES = \
gproxyaddressenumerator.c \
gsocketservice.c \
gsrvtarget.c \
+ gtask.c \
gtcpconnection.c \
gtcpwrapperconnection.c \
gthreadedsocketservice.c\
@@ -599,6 +600,7 @@ gio_headers = \
gsocketlistener.h \
gsocketservice.h \
gsrvtarget.h \
+ gtask.h \
gtcpconnection.h \
gtcpwrapperconnection.h \
gthreadedsocketservice.h\
diff --git a/gio/gio.h b/gio/gio.h
index 3fb914d..1998a64 100644
--- a/gio/gio.h
+++ b/gio/gio.h
@@ -122,6 +122,7 @@
#include <gio/gsocketlistener.h>
#include <gio/gsocketservice.h>
#include <gio/gsrvtarget.h>
+#include <gio/gtask.h>
#include <gio/gtcpconnection.h>
#include <gio/gtcpwrapperconnection.h>
#include <gio/gtestdbus.h>
diff --git a/gio/gio.symbols b/gio/gio.symbols
index a60e1e9..5650b62 100644
--- a/gio/gio.symbols
+++ b/gio/gio.symbols
@@ -1751,3 +1751,34 @@ g_test_dbus_down
g_test_dbus_stop
g_test_dbus_up
g_test_dbus_unset
+g_task_attach_source
+g_task_get_cancellable
+g_task_get_check_cancellable
+g_task_get_context
+g_task_get_priority
+g_task_get_return_on_cancel
+g_task_get_source_object
+g_task_get_source_tag
+g_task_get_task_data
+g_task_get_type
+g_task_had_error
+g_task_is_valid
+g_task_new
+g_task_propagate_boolean
+g_task_propagate_int
+g_task_propagate_pointer
+g_task_run_in_thread
+g_task_run_in_thread_sync
+g_task_set_check_cancellable
+g_task_set_priority
+g_task_set_return_on_cancel
+g_task_set_source_tag
+g_task_set_task_data
+g_task_report_error
+g_task_report_new_error
+g_task_return_boolean
+g_task_return_error
+g_task_return_error_if_cancelled
+g_task_return_int
+g_task_return_new_error
+g_task_return_pointer
diff --git a/gio/giotypes.h b/gio/giotypes.h
index 876d856..82845c5 100644
--- a/gio/giotypes.h
+++ b/gio/giotypes.h
@@ -199,6 +199,7 @@ typedef struct _GSocketAddress GSocketAddress;
typedef struct _GSocketAddressEnumerator GSocketAddressEnumerator;
typedef struct _GSocketConnectable GSocketConnectable;
typedef struct _GSrvTarget GSrvTarget;
+typedef struct _GTask GTask;
/**
* GTcpConnection:
*
diff --git a/gio/gtask.c b/gio/gtask.c
new file mode 100644
index 0000000..7c17f83
--- /dev/null
+++ b/gio/gtask.c
@@ -0,0 +1,1667 @@
+/* GIO - GLib Input, Output and Streaming Library
+ *
+ * Copyright 2011 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include "gtask.h"
+
+#include "gasyncresult.h"
+#include "gcancellable.h"
+
+/**
+ * SECTION:gtask
+ * @short_description: Cancellable synchronous or asynchronous task and result
+ * @include: gio/gio.h
+ * @see_also: #GAsyncResult
+ *
+ * <para>
+ * A #GTask represents and manages a cancellable "task".
+ * </para>
+ * <refsect2>
+ * <title>Asynchronous operations</title>
+ * <para>
+ * The most common usage of #GTask is as a #GAsyncResult, to
+ * manage data during an asynchronous operation. You call
+ * g_task_new() in the "start" method, followed by
+ * g_task_set_task_data() and the like if you need to keep some
+ * additional data associated with the task, and then pass the
+ * task object around through your asynchronous operation.
+ * Eventually, you will call a method such as
+ * g_task_return_pointer() or g_task_return_error(), which will
+ * save the value you give it and then invoke the task's callback
+ * function (waiting until the next next iteration of the main
+ * loop first, if necessary). The caller will pass the #GTask back
+ * to the operation's finish function (as a #GAsyncResult), and
+ * you can use g_task_propagate_pointer() or the like to extract
+ * the return value.
+ * </para>
+ * <example id="gtask-async"><title>GTask as a GAsyncResult</title>
+ * <programlisting>
+ * typedef struct {
+ * CakeFrostingType frosting;
+ * char *message;
+ * } DecorationData;
+ *
+ * static void
+ * decoration_data_free (DecorationData *decoration)
+ * {
+ * g_free (decoration->message);
+ * g_slice_free (DecorationData, decoration);
+ * }
+ *
+ * static void
+ * baked_cb (Cake *cake,
+ * gpointer user_data)
+ * {
+ * GTask *task = user_data;
+ * DecorationData *decoration = g_task_get_task_data (task);
+ * GError *error = NULL;
+ *
+ * if (g_task_return_error_if_cancelled (g_task_get_cancellable (task)))
+ * {
+ * g_object_unref (task);
+ * return;
+ * }
+ *
+ * if (cake == NULL)
+ * {
+ * g_task_return_new_error (task, BAKER_ERROR, BAKER_ERROR_NO_FLOUR,
+ * "Go to the supermarket");
+ * g_object_unref (task);
+ * return;
+ * }
+ *
+ * if (!cake_decorate (cake, decoration->frosting, decoration->message, &error))
+ * {
+ * /* g_task_return_error() takes ownership of error */
+ * g_task_return_error (cake, error);
+ * g_object_unref (task);
+ * return;
+ * }
+ *
+ * /* In this example, this callback is not given a reference to the cake, so
+ * * the GTask has to add a reference to it.
+ * */
+ * g_task_return_pointer (result, g_object_ref (cake), g_object_unref);
+ * g_object_unref (task);
+ * }
+ *
+ * void
+ * baker_bake_cake_async (Baker *self,
+ * guint radius,
+ * CakeFlavor flavor,
+ * CakeFrostingType frosting,
+ * const char *message,
+ * GCancellable *cancellable,
+ * GAsyncReadyCallback callback,
+ * gpointer user_data)
+ * {
+ * GTask *task;
+ * DecorationData *decoration;
+ * Cake *cake;
+ *
+ * task = g_task_new (self, cancellable, callback, user_data);
+ * if (radius < 3)
+ * {
+ * g_task_return_new_error (task, BAKER_ERROR, BAKER_ERROR_TOO_SMALL,
+ * "%ucm radius cakes are silly",
+ * radius);
+ * g_object_unref (task);
+ * return;
+ * }
+ *
+ * cake = _baker_get_cached_cake (self, radius, flavor, frosting, message);
+ * if (cake != NULL)
+ * {
+ * /* _baker_get_cached_cake() returns a reffed cake */
+ * g_task_return_pointer (task, cake, g_object_unref);
+ * g_object_unref (task);
+ * return;
+ * }
+ *
+ * decoration = g_slice_new (DecorationData);
+ * decoration->frosting = frosting;
+ * decoration->message = g_strdup (message);
+ * g_task_set_task_data (task, decoration, (GDestroyNotify) decoration_data_free);
+ *
+ * _baker_begin_cake (self, radius, flavor, cancellable, baked_cb, task);
+ * }
+ *
+ * Cake *
+ * baker_bake_cake_finish (Baker *self,
+ * GAsyncResult *result,
+ * GError **error)
+ * {
+ * g_return_val_if_fail (g_task_is_valid (result, self), NULL);
+ *
+ * return g_task_propagate_pointer (G_TASK (result), error);
+ * }
+ * </programlisting>
+ * </example>
+ * </refsect2>
+ * <refsect2>
+ * <title>Asynchronous operations from synchronous ones</title>
+ * <para>
+ * You can also use g_task_run_in_thread() to turn a synchronous
+ * operation into an asynchronous one, by running it in a thread
+ * which will then dispatch the result back to the caller's
+ * #GMainContext when it completes.
+ * </para>
+ * <example id="gtask-run-in-thread"><title>g_task_run_in_thread()</title>
+ * <programlisting>
+ * typedef struct {
+ * guint radius;
+ * CakeFlavor flavor;
+ * CakeFrostingType frosting;
+ * char *message;
+ * } CakeData;
+ *
+ * static void
+ * cake_data_free (CakeData *cake_data)
+ * {
+ * g_free (cake_data->message);
+ * g_slice_free (CakeData, cake_data);
+ * }
+ *
+ * static void
+ * bake_cake_thread (GTask *task,
+ * gpointer source_object,
+ * gpointer task_data,
+ * GCancellable *cancellable)
+ * {
+ * Baker *self = source_object;
+ * CakeData *cake_data = task_data;
+ * Cake *cake;
+ * GError *error = NULL;
+ *
+ * cake = bake_cake (baker, cake_data->radius, cake_data->flavor,
+ * cake_data->frosting, cake_data->message,
+ * cancellable, &error);
+ * if (cake)
+ * g_task_return_pointer (task, cake, g_object_unref);
+ * else
+ * g_task_return_error (task, error);
+ * }
+ *
+ * void
+ * baker_bake_cake_async (Baker *self,
+ * guint radius,
+ * CakeFlavor flavor,
+ * CakeFrostingType frosting,
+ * const char *message,
+ * GCancellable *cancellable,
+ * GAsyncReadyCallback callback,
+ * gpointer user_data)
+ * {
+ * CakeData *cake_data;
+ * GTask *task;
+ *
+ * cake_data = g_slice_new (CakeData);
+ * cake_data->radius = radius;
+ * cake_data->flavor = flavor;
+ * cake_data->frosting = frosting;
+ * cake_data->message = g_strdup (message);
+ * task = g_task_new (self, cancellable, callback, user_data);
+ * g_task_set_task_data (task, cake_data, (GDestroyNotify) cake_data_free);
+ * g_task_run_in_thread (task, bake_cake_thread);
+ * }
+ *
+ * Cake *
+ * baker_bake_cake_finish (Baker *self,
+ * GAsyncResult *result,
+ * GError **error)
+ * {
+ * g_return_val_if_fail (g_task_is_valid (result, self), NULL);
+ *
+ * return g_task_propagate_pointer (G_TASK (result), error);
+ * }
+ * </programlisting>
+ * </example>
+ * </refsect2>
+ * <refsect2>
+ * <title>Adding cancellability to uncancellable tasks</title>
+ * <para>
+ * Finally, g_task_run_in_thread() and g_task_run_in_thread_sync()
+ * can be used to turn an uncancellable operation into a
+ * cancellable one. If you call g_task_set_return_on_cancel(),
+ * passing %TRUE, then if the task's #GCancellable is cancelled,
+ * it will return control back to the caller immediately, while
+ * allowing the task thread to continue running in the background
+ * (and simply discarding its result when it finally does finish).
+ * Provided that the task thread is careful about how it uses
+ * locks and other externally-visible resources, this allows you
+ * to make "GLib-friendly" asynchronous and cancellable
+ * synchronous variants of blocking APIs.
+ * </para>
+ * <example id="gtask-cancellable"><title>g_task_set_return_on_cancel()</title>
+ * <programlisting>
+ * static void
+ * bake_cake_thread (GTask *task,
+ * gpointer source_object,
+ * gpointer task_data,
+ * GCancellable *cancellable)
+ * {
+ * Baker *self = source_object;
+ * CakeData *cake_data = task_data;
+ * Cake *cake;
+ * GError *error = NULL;
+ *
+ * cake = bake_cake (baker, cake_data->radius, cake_data->flavor,
+ * cake_data->frosting, cake_data->message,
+ * &error);
+ * if (error)
+ * {
+ * g_task_return_error (task, error);
+ * return;
+ * }
+ *
+ * /* If the task has already been cancelled, then we don't
+ * * want to add the cake to the cake cache. Likewise, we don't
+ * * want to have the task get cancelled in the middle of
+ * * updating the cache. g_task_set_return_on_cancel() will
+ * * return %TRUE here if it managed to disable return-on-cancel,
+ * * or %FALSE if the task was cancelled before it could.
+ * */
+ * if (g_task_set_return_on_cancel (task, FALSE))
+ * {
+ * /* If the caller cancels at this point, their
+ * * #GAsyncReadyCallback won't be invoked until
+ * * we return.
+ * */
+ * baker_add_cake_to_cache (baker, cake);
+ * g_task_return_pointer (task, cake, g_object_unref);
+ * }
+ * }
+ *
+ * void
+ * baker_bake_cake_async (Baker *self,
+ * guint radius,
+ * CakeFlavor flavor,
+ * CakeFrostingType frosting,
+ * const char *message,
+ * GCancellable *cancellable,
+ * GAsyncReadyCallback callback,
+ * gpointer user_data)
+ * {
+ * CakeData *cake_data;
+ * GTask *task;
+ *
+ * cake_data = g_slice_new (CakeData);
+ * /* ... */
+ *
+ * task = g_task_new (self, cancellable, callback, user_data);
+ * g_task_set_task_data (task, cake_data, (GDestroyNotify) cake_data_free);
+ * g_task_set_return_on_cancel (task, TRUE);
+ * g_task_run_in_thread (task, bake_cake_thread);
+ * }
+ *
+ * Cake *
+ * baker_bake_cake_sync (Baker *self,
+ * guint radius,
+ * CakeFlavor flavor,
+ * CakeFrostingType frosting,
+ * const char *message,
+ * GCancellable *cancellable,
+ * GError **error)
+ * {
+ * CakeData *cake_data;
+ * GTask *task;
+ * Cake *cake;
+ *
+ * cake_data = g_slice_new (CakeData);
+ * /* ... */
+ *
+ * task = g_task_new (self, cancellable, NULL, NULL);
+ * g_task_set_task_data (task, cake_data, (GDestroyNotify) cake_data_free);
+ * g_task_set_return_on_cancel (task, TRUE);
+ * g_task_run_in_thread_sync (task, bake_cake_thread);
+ *
+ * cake = g_task_propagate_pointer (task, error);
+ * g_object_unref (task);
+ * return cake;
+ * }
+ * </programlisting>
+ * </example>
+ * </refsect2>
+ * <refsect2>
+ * <title>Porting from <literal>GSimpleAsyncResult</literal></title>
+ * <para>
+ * #GTask's API attempts to be simpler than #GSimpleAsyncResult's
+ * in several ways:
+ * </para>
+ * <itemizedlist>
+ * <listitem><para>
+ * You can save task-specific data with g_task_set_task_data(), and
+ * retrieve it later with g_task_get_task_data(). This replaces the
+ * abuse of g_simple_async_result_set_op_res_gpointer() for the same
+ * purpose with #GSimpleAsyncResult.
+ * </para></listitem>
+ * <listitem><para>
+ * In addition to the task data, #GTask also keeps track of the
+ * <link linkend="io-priority">priority</link>, #GCancellable, and
+ * #GMainContext associated with the task, so tasks that consist of
+ * a chain of simpler asynchronous operations will have easy access
+ * to those values when starting each sub-task.
+ * </para></listitem>
+ * <listitem><para>
+ * g_task_return_error_if_cancelled() provides simplified
+ * handling for cancellation. In addition, cancellation
+ * overrides any other #GTask return value by default, like
+ * #GSimpleAsyncResult does when
+ * g_simple_async_result_set_check_cancellable() is called.
+ * (You can use g_task_set_check_cancellable() to turn off that
+ * behavior.) On the other hand, g_task_run_in_thread()
+ * guarantees that it will always run your
+ * <literal>task_func</literal>, even if the task's #GCancellable
+ * is already cancelled before the task gets a chance to run;
+ * you can start your <literal>task_func</literal> with a
+ * g_task_return_error_if_cancelled() check if you need the
+ * old behavior.
+ * </para></listitem>
+ * <listitem><para>
+ * The "return" methods (eg, g_task_return_pointer())
+ * automatically cause the task to be "completed" as well, and
+ * there is no need to worry about the "complete" vs "complete
+ * in idle" distinction. (#GTask automatically figures out
+ * whether the task's callback can be invoked directly, or
+ * if it needs to be sent to another #GMainContext, or delayed
+ * until the next iteration of the current #GMainContext.)
+ * </para></listitem>
+ * <listitem><para>
+ * The "finish" functions for #GTask-based operations are generally
+ * much simpler than #GSimpleAsyncResult ones, normally consisting
+ * of only a single call to g_task_propagate_pointer() or the like.
+ * Since g_task_propagate_pointer() "steals" the return value from
+ * the #GTask, it is not necessary to juggle pointers around to
+ * prevent it from being freed twice.
+ * </para></listitem>
+ * <listitem><para>
+ * With #GSimpleAsyncResult, it was common to call
+ * g_simple_async_result_propagate_error() from the
+ * <literal>_finish()</literal> wrapper function, and have
+ * virtual method implementations only deal with successful
+ * returns. This behavior is deprecated, because it makes it
+ * difficult for a subclass to chain to a parent class's async
+ * methods. Instead, the wrapper function should just be a
+ * simple wrapper, and the virtual method should call an
+ * appropriate <literal>g_task_propagate_</literal> function.
+ * Note that wrapper methods can now use
+ * g_async_result_legacy_propagate_error() to do old-style
+ * #GSimpleAsyncResult error-returning behavior, and
+ * g_async_result_is_tagged() to check if a result is tagged as
+ * having come from the <literal>_async()</literal> wrapper
+ * function (for "short-circuit" results, such as when passing
+ * 0 to g_input_stream_read_async()).
+ * </para></listitem>
+ * </itemizedlist>
+ * </refsect2>
+ */
+
+/**
+ * GTask:
+ *
+ * The opaque object representing a synchronous or asynchronous task
+ * and its result.
+ */
+
+struct _GTask {
+ GObject parent_instance;
+
+ gpointer source_object;
+ gpointer source_tag;
+
+ gpointer task_data;
+ GDestroyNotify task_data_destroy;
+
+ GMainContext *context;
+ guint64 creation_time;
+ gint priority;
+ GCancellable *cancellable;
+ gboolean check_cancellable;
+
+ GAsyncReadyCallback callback;
+ gpointer callback_data;
+
+ GTaskThreadFunc task_func;
+ GMutex lock;
+ GCond cond;
+ gboolean return_on_cancel;
+ gboolean thread_cancelled;
+ gboolean synchronous;
+ gboolean thread_complete;
+
+ GError *error;
+ union {
+ gpointer pointer;
+ gssize size;
+ gboolean boolean;
+ } result;
+ GDestroyNotify result_destroy;
+ gboolean result_set;
+};
+
+#define G_TASK_IS_THREADED(task) ((task)->task_func != NULL)
+
+struct _GTaskClass
+{
+ GObjectClass parent_class;
+};
+
+static void g_task_thread_pool_resort (void);
+
+static void g_task_async_result_iface_init (GAsyncResultIface *iface);
+static void g_task_thread_pool_init (void);
+
+G_DEFINE_TYPE_WITH_CODE (GTask, g_task, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_RESULT,
+ g_task_async_result_iface_init);
+ g_task_thread_pool_init ();)
+
+static GThreadPool *task_pool;
+
+static void
+g_task_init (GTask *task)
+{
+ task->check_cancellable = TRUE;
+}
+
+static void
+g_task_finalize (GObject *object)
+{
+ GTask *task = G_TASK (object);
+
+ g_clear_object (&task->source_object);
+ g_clear_object (&task->cancellable);
+
+ if (task->context)
+ g_main_context_unref (task->context);
+
+ if (task->task_data_destroy)
+ task->task_data_destroy (task->task_data);
+
+ if (task->result_destroy && task->result.pointer)
+ task->result_destroy (task->result.pointer);
+
+ if (G_TASK_IS_THREADED (task))
+ {
+ g_mutex_clear (&task->lock);
+ g_cond_clear (&task->cond);
+ }
+
+ G_OBJECT_CLASS (g_task_parent_class)->finalize (object);
+}
+
+/**
+ * g_task_new:
+ * @source_object: (allow-none): the #GObject that owns this task, or %NULL.
+ * @cancellable: (allow-none): optional #GCancellable object, %NULL to ignore.
+ * @callback: (scope async): a #GAsyncReadyCallback.
+ * @callback_data: (closure): user data passed to @callback.
+ *
+ * Creates a #GTask acting on @source_object, which will eventually be
+ * used to invoke @callback in the current <link
+ * linkend="g-main-context-push-thread-default">thread-default main
+ * context</link>.
+ *
+ * Call this in the "start" method of your asynchronous method, and
+ * pass the #GTask around throughout the asynchronous operation. You
+ * can use g_task_set_task_data() to attach task-specific data to the
+ * object, which you can retrieve later via g_task_get_task_data().
+ *
+ * By default, if @cancellable is cancelled, then the return value of
+ * the task will always be %G_IO_ERROR_CANCELLED, even if the task had
+ * already completed before the cancellation. This allows for
+ * simplified handling in cases where cancellation may imply that
+ * other objects that the task depends on have been destroyed. If you
+ * do not want this behavior, you can use
+ * g_task_set_check_cancellable() to change it.
+ *
+ * Returns: a #GTask.
+ *
+ * Since: 2.34
+ */
+GTask *
+g_task_new (gpointer source_object,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer callback_data)
+{
+ GTask *task;
+ GSource *source;
+
+ task = g_object_new (G_TYPE_TASK, NULL);
+ task->source_object = source_object ? g_object_ref (source_object) : NULL;
+ task->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
+ task->callback = callback;
+ task->callback_data = callback_data;
+ task->context = g_main_context_ref_thread_default ();
+
+ source = g_main_current_source ();
+ if (source)
+ task->creation_time = g_source_get_time (source);
+
+ return task;
+}
+
+/**
+ * g_task_report_error:
+ * @source_object: (allow-none): the #GObject that owns this task, or %NULL.
+ * @callback: (scope async): a #GAsyncReadyCallback.
+ * @callback_data: (closure): user data passed to @callback.
+ * @source_tag: an opaque pointer indicating the source of this task
+ * @error: (transfer full): error to report
+ *
+ * Creates a #GTask and then immediately calls g_task_return_error()
+ * on it. Use this in the wrapper function of an asynchronous method
+ * when you want to avoid even calling the virtual method. You can
+ * then use g_async_result_is_tagged() in the finish method wrapper to
+ * check if the result there is tagged as having been created by the
+ * wrapper method, and deal with it appropriately if so.
+ *
+ * See also g_task_report_new_error().
+ *
+ * Since: 2.34
+ */
+void
+g_task_report_error (gpointer source_object,
+ GAsyncReadyCallback callback,
+ gpointer callback_data,
+ gpointer source_tag,
+ GError *error)
+{
+ GTask *task;
+
+ task = g_task_new (source_object, NULL, callback, callback_data);
+ g_task_set_source_tag (task, source_tag);
+ g_task_return_error (task, error);
+ g_object_unref (task);
+}
+
+/**
+ * g_task_report_new_error:
+ * @source_object: (allow-none): the #GObject that owns this task, or %NULL.
+ * @callback: (scope async): a #GAsyncReadyCallback.
+ * @callback_data: (closure): user data passed to @callback.
+ * @source_tag: an opaque pointer indicating the source of this task
+ * @domain: a #GQuark.
+ * @code: an error code.
+ * @format: a string with format characters.
+ * @...: a list of values to insert into @format.
+ *
+ * Creates a #GTask and then immediately calls
+ * g_task_return_new_error() on it. Use this in the wrapper function
+ * of an asynchronous method when you want to avoid even calling the
+ * virtual method. You can then use g_async_result_is_tagged() in the
+ * finish method wrapper to check if the result there is tagged as
+ * having been created by the wrapper method, and deal with it
+ * appropriately if so.
+ *
+ * See also g_task_report_error().
+ *
+ * Since: 2.34
+ */
+void
+g_task_report_new_error (gpointer source_object,
+ GAsyncReadyCallback callback,
+ gpointer callback_data,
+ gpointer source_tag,
+ GQuark domain,
+ gint code,
+ const char *format,
+ ...)
+{
+ GError *error;
+ va_list ap;
+
+ va_start (ap, format);
+ error = g_error_new_valist (domain, code, format, ap);
+ va_end (ap);
+
+ g_task_report_error (source_object, callback, callback_data,
+ source_tag, error);
+}
+
+/**
+ * g_task_set_task_data:
+ * @task: the #GTask
+ * @task_data: (allow-none): task-specific data
+ * @task_data_destroy: (allow-none): #GDestroyNotify for @task_data
+ *
+ * Sets @task's task data (freeing the existing task data, if any).
+ *
+ * Since: 2.34
+ */
+void
+g_task_set_task_data (GTask *task,
+ gpointer task_data,
+ GDestroyNotify task_data_destroy)
+{
+ if (task->task_data_destroy)
+ task->task_data_destroy (task->task_data);
+
+ task->task_data = task_data;
+ task->task_data_destroy = task_data_destroy;
+}
+
+/**
+ * g_task_set_priority:
+ * @task: the #GTask
+ * @priority: the <link linkend="io-priority">priority</link>
+ * of the request.
+ *
+ * Sets @task's priority. If you do not call this, it will default to
+ * %G_PRIORITY_DEFAULT.
+ *
+ * This will affect the priority of #GSources created with
+ * g_task_attach_source() and the scheduling of tasks run in threads,
+ * and can also be explicitly retrieved later via
+ * g_task_get_priority().
+ *
+ * Since: 2.34
+ */
+void
+g_task_set_priority (GTask *task,
+ gint priority)
+{
+ task->priority = priority;
+}
+
+/**
+ * g_task_set_check_cancellable:
+ * @task: the #GTask
+ * @check_cancellable: whether #GTask will check the state of
+ * its #GCancellable for you.
+ *
+ * Sets or clears @task's check-cancellable flag. If this is %TRUE
+ * (the default), then g_task_propagate_pointer(), etc, and
+ * g_task_had_error() will check the task's #GCancellable first, and
+ * if it has been cancelled, then they will consider the task to have
+ * returned an "Operation was cancelled" error
+ * (%G_IO_ERROR_CANCELLED), regardless of any other error or return
+ * value the task may have had.
+ *
+ * If @check_cancellable is %FALSE, then the #GTask will not check the
+ * cancellable itself, and it is up to @task's owner to do this (eg,
+ * via g_task_return_error_if_cancelled()).
+ *
+ * If you are using g_task_set_return_on_cancel() as well, then
+ * you must leave check-cancellable set %TRUE.
+ *
+ * Since: 2.34
+ */
+void
+g_task_set_check_cancellable (GTask *task,
+ gboolean check_cancellable)
+{
+ g_return_if_fail (check_cancellable || !task->return_on_cancel);
+
+ task->check_cancellable = check_cancellable;
+}
+
+static void g_task_thread_complete (GTask *task);
+
+/**
+ * g_task_set_return_on_cancel:
+ * @task: the #GTask
+ * @return_on_cancel: whether the task returns automatically when
+ * it is cancelled.
+ *
+ * Sets or clears @task's return-on-cancel flag. This is only
+ * meaningful for tasks run via g_task_run_in_thread() or
+ * g_task_run_in_thread_sync().
+ *
+ * If @return_on_cancel is %TRUE, then cancelling @task's
+ * #GCancellable will immediately cause it to return, as though the
+ * task's #GTaskThreadFunc had called
+ * g_task_return_error_if_cancelled() and then returned.
+ *
+ * This allows you to create a cancellable wrapper around an
+ * uninterruptable function. The #GTaskThreadFunc just needs to be
+ * careful that it does not modify any externally-visible state after
+ * it has been cancelled. To do that, the thread should call
+ * g_task_set_return_on_cancel() again to (atomically) set
+ * return-on-cancel %FALSE before making externally-visible changes;
+ * if the task gets cancelled before the return-on-cancel flag could
+ * be changed, g_task_set_return_on_cancel() will indicate this by
+ * returning %FALSE.
+ *
+ * You can disable and re-enable this flag multiple times if you wish.
+ * If the task's #GCancellable is cancelled while return-on-cancel is
+ * %FALSE, then calling g_task_set_return_on_cancel() to set it %TRUE
+ * again will cause the task to be cancelled at that point.
+ *
+ * If the task's #GCancellable is already cancelled before you call
+ * g_task_run_in_thread()/g_task_run_in_thread_sync(), then the
+ * #GTaskThreadFunc will still be run (for consistency), but the task
+ * will also be completed right away.
+ *
+ * Returns: %TRUE if @task's return-on-cancel flag was changed to
+ * match @return_on_cancel. %FALSE if @task has already been
+ * cancelled.
+ *
+ * Since: 2.34
+ */
+gboolean
+g_task_set_return_on_cancel (GTask *task,
+ gboolean return_on_cancel)
+{
+ g_return_val_if_fail (task->check_cancellable || !return_on_cancel, FALSE);
+
+ if (!G_TASK_IS_THREADED (task))
+ {
+ task->return_on_cancel = return_on_cancel;
+ return TRUE;
+ }
+
+ g_mutex_lock (&task->lock);
+ if (task->thread_cancelled)
+ {
+ if (return_on_cancel && !task->return_on_cancel)
+ {
+ g_mutex_unlock (&task->lock);
+ g_task_thread_complete (task);
+ }
+ else
+ g_mutex_unlock (&task->lock);
+ return FALSE;
+ }
+ task->return_on_cancel = return_on_cancel;
+ g_mutex_unlock (&task->lock);
+
+ return TRUE;
+}
+
+/**
+ * g_task_set_source_tag:
+ * @task: the #GTask
+ * @source_tag: an opaque pointer indicating the source of this task
+ *
+ * Sets @task's source tag. You can use this to tag a task return
+ * value with a particular pointer (usually a pointer to the function
+ * doing the tagging) and then later check it using
+ * g_task_get_source_tag() (or g_async_result_is_tagged()) in the
+ * task's "finish" function, to figure out if the response came from a
+ * particular place.
+ *
+ * Since: 2.34
+ */
+void
+g_task_set_source_tag (GTask *task,
+ gpointer source_tag)
+{
+ task->source_tag = source_tag;
+}
+
+/**
+ * g_task_get_source_object:
+ * @task: a #GTask
+ *
+ * Gets the source object from @task. Like
+ * g_async_result_get_source_object(), but does not ref the object.
+ *
+ * Returns: (transfer none): @task's source object, or %NULL
+ *
+ * Since: 2.34
+ */
+gpointer
+g_task_get_source_object (GTask *task)
+{
+ return task->source_object;
+}
+
+static GObject *
+g_task_ref_source_object (GAsyncResult *res)
+{
+ GTask *task = G_TASK (res);
+
+ if (task->source_object)
+ return g_object_ref (task->source_object);
+ else
+ return NULL;
+}
+
+/**
+ * g_task_get_task_data:
+ * @task: a #GTask
+ *
+ * Gets @task's <literal>task_data</literal>.
+ *
+ * Returns: (transfer none): @task's <literal>task_data</literal>.
+ *
+ * Since: 2.34
+ */
+gpointer
+g_task_get_task_data (GTask *task)
+{
+ return task->task_data;
+}
+
+/**
+ * g_task_get_priority:
+ * @task: a #GTask
+ *
+ * Gets @task's priority
+ *
+ * Returns: @task's priority
+ *
+ * Since: 2.34
+ */
+gint
+g_task_get_priority (GTask *task)
+{
+ return task->priority;
+}
+
+/**
+ * g_task_get_context:
+ * @task: a #GTask
+ *
+ * Gets the #GMainContext that @task will return its result in (that
+ * is, the context that was the <link
+ * linkend="g-main-context-push-thread-default">thread-default main
+ * context</link> at the point when @task was created).
+ *
+ * This will always return a non-%NULL value, even if the task's
+ * context is the default #GMainContext.
+ *
+ * Returns: (transfer none): @task's #GMainContext
+ *
+ * Since: 2.34
+ */
+GMainContext *
+g_task_get_context (GTask *task)
+{
+ return task->context;
+}
+
+/**
+ * g_task_get_cancellable:
+ * @task: a #GTask
+ *
+ * Gets @task's #GCancellable
+ *
+ * Returns: (transfer none): @task's #GCancellable
+ *
+ * Since: 2.34
+ */
+GCancellable *
+g_task_get_cancellable (GTask *task)
+{
+ return task->cancellable;
+}
+
+/**
+ * g_task_get_check_cancellable:
+ * @task: the #GTask
+ *
+ * Gets @task's check-cancellable flag. See
+ * g_task_set_check_cancellable() for more details.
+ *
+ * Since: 2.34
+ */
+gboolean
+g_task_get_check_cancellable (GTask *task)
+{
+ return task->check_cancellable;
+}
+
+/**
+ * g_task_get_return_on_cancel:
+ * @task: the #GTask
+ *
+ * Gets @task's return-on-cancel flag. See
+ * g_task_set_return_on_cancel() for more details.
+ *
+ * Since: 2.34
+ */
+gboolean
+g_task_get_return_on_cancel (GTask *task)
+{
+ return task->return_on_cancel;
+}
+
+/**
+ * g_task_get_source_tag:
+ * @task: a #GTask
+ *
+ * Gets @task's source tag. See g_task_set_source_tag().
+ *
+ * Return value: (transfer none): @task's source tag
+ *
+ * Since: 2.34
+ */
+gpointer
+g_task_get_source_tag (GTask *task)
+{
+ return task->source_tag;
+}
+
+
+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);
+ g_main_context_pop_thread_default (task->context);
+}
+
+static gboolean
+complete_in_idle_cb (gpointer task)
+{
+ g_task_return_now (task);
+ g_object_unref (task);
+ return FALSE;
+}
+
+typedef enum {
+ G_TASK_RETURN_SUCCESS,
+ G_TASK_RETURN_ERROR,
+ G_TASK_RETURN_FROM_THREAD
+} GTaskReturnType;
+
+static void
+g_task_return (GTask *task,
+ GTaskReturnType type)
+{
+ GSource *source;
+
+ if (type == G_TASK_RETURN_SUCCESS)
+ task->result_set = TRUE;
+
+ if (task->synchronous || !task->callback)
+ return;
+
+ /* Normally we want to invoke the task's callback when its return
+ * value is set. But if the task is running in a thread, then we
+ * want to wait until after the task_func returns, to simplify
+ * locking/refcounting/etc.
+ */
+ if (G_TASK_IS_THREADED (task) && type != G_TASK_RETURN_FROM_THREAD)
+ return;
+
+ g_object_ref (task);
+
+ /* See if we can complete the task immediately. First, we have to be
+ * running inside the task's thread/GMainContext.
+ */
+ source = g_main_current_source ();
+ if (source && g_source_get_context (source) == task->context)
+ {
+ /* Second, we can only complete immediately if this is not the
+ * same iteration of the main loop that the task was created in.
+ */
+ if (g_source_get_time (source) > task->creation_time)
+ {
+ g_task_return_now (task);
+ g_object_unref (task);
+ return;
+ }
+ }
+
+ /* Otherwise, complete in the next iteration */
+ source = g_idle_source_new ();
+ g_task_attach_source (task, source, complete_in_idle_cb);
+ g_source_unref (source);
+}
+
+
+/**
+ * GTaskThreadFunc:
+ * @task: the #GTask
+ * @source_object: @task's source object
+ * @task_data: @task's task data
+ * @cancellable: @task's #GCancellable, or %NULL
+ *
+ * The prototype for a task function to be run in a thread via
+ * g_task_run_in_thread() or g_task_run_in_thread_sync().
+ *
+ * If the return-on-cancel flag is set on @task, then the #GTask will
+ * be completed immediately (as though
+ * g_task_return_error_if_cancelled() had been called) if @cancellable
+ * is cancelled, without waiting for the task function to complete.
+ * However, the task function will continue running in its thread in
+ * the background. The function therefore needs to be careful about
+ * how it uses externally-visible state in this case. See
+ * g_task_set_return_on_cancel() for more details.
+ *
+ * Other than in that case, @task will be completed when the
+ * #GTaskThreadFunc returns, <emphasis>not</emphasis> when it calls
+ * a <literal>g_task_return_</literal> function.
+ *
+ * Since: 2.34
+ */
+
+static void task_thread_cancelled (GCancellable *cancellable,
+ gpointer user_data);
+
+static void
+g_task_thread_complete (GTask *task)
+{
+ g_mutex_lock (&task->lock);
+ if (task->thread_complete)
+ {
+ /* The task belatedly completed after having been cancelled
+ * (or was cancelled in the midst of being completed).
+ */
+ g_mutex_unlock (&task->lock);
+ return;
+ }
+
+ task->thread_complete = TRUE;
+ g_mutex_unlock (&task->lock);
+
+ if (task->cancellable)
+ g_signal_handlers_disconnect_by_func (task->cancellable, task_thread_cancelled, task);
+
+ if (task->synchronous)
+ g_cond_signal (&task->cond);
+ else
+ g_task_return (task, G_TASK_RETURN_FROM_THREAD);
+}
+
+static void
+g_task_thread_pool_thread (gpointer thread_data,
+ gpointer pool_data)
+{
+ GTask *task = thread_data;
+
+ task->task_func (task, task->source_object, task->task_data,
+ task->cancellable);
+ g_task_thread_complete (task);
+ g_object_unref (task);
+}
+
+static void
+task_thread_cancelled (GCancellable *cancellable,
+ gpointer user_data)
+{
+ GTask *task = user_data;
+
+ g_task_thread_pool_resort ();
+
+ g_mutex_lock (&task->lock);
+ task->thread_cancelled = TRUE;
+
+ if (!task->return_on_cancel)
+ {
+ g_mutex_unlock (&task->lock);
+ return;
+ }
+
+ /* We don't actually set task->error; g_task_return_error() doesn't
+ * use a lock, and g_task_propagate_error() will call
+ * g_cancellable_set_error_if_cancelled() anyway.
+ */
+ g_mutex_unlock (&task->lock);
+ g_task_thread_complete (task);
+}
+
+static void
+task_thread_cancelled_disconnect_notify (gpointer task,
+ GClosure *closure)
+{
+ g_object_unref (task);
+}
+
+static void
+g_task_start_task_thread (GTask *task,
+ GTaskThreadFunc task_func)
+{
+ g_mutex_init (&task->lock);
+ g_cond_init (&task->cond);
+
+ g_mutex_lock (&task->lock);
+
+ task->task_func = task_func;
+
+ if (task->cancellable)
+ {
+ if (task->return_on_cancel &&
+ g_cancellable_set_error_if_cancelled (task->cancellable,
+ &task->error))
+ {
+ task->thread_cancelled = task->thread_complete = TRUE;
+ g_thread_pool_push (task_pool, g_object_ref (task), NULL);
+ return;
+ }
+
+ g_signal_connect_data (task->cancellable, "cancelled",
+ G_CALLBACK (task_thread_cancelled),
+ g_object_ref (task),
+ task_thread_cancelled_disconnect_notify, 0);
+ }
+
+ g_thread_pool_push (task_pool, g_object_ref (task), &task->error);
+ if (task->error)
+ task->thread_complete = TRUE;
+}
+
+/**
+ * g_task_run_in_thread:
+ * @task: a #GTask
+ * @task_func: a #GTaskThreadFunc
+ *
+ * Runs @task_func in another thread. When @task_func returns, @task's
+ * #GAsyncReadyCallback will be invoked in @task's #GMainContext.
+ *
+ * This takes a ref on @task until the task completes.
+ *
+ * See #GTaskThreadFunc for more details about how @task_func is handled.
+ *
+ * Since: 2.34
+ */
+void
+g_task_run_in_thread (GTask *task,
+ GTaskThreadFunc task_func)
+{
+ g_return_if_fail (G_IS_TASK (task));
+
+ g_object_ref (task);
+ g_task_start_task_thread (task, task_func);
+
+ /* The task may already be cancelled, or g_thread_pool_push() may
+ * have failed.
+ */
+ if (task->thread_complete)
+ {
+ g_mutex_unlock (&task->lock);
+ g_task_return (task, G_TASK_RETURN_FROM_THREAD);
+ }
+ else
+ g_mutex_unlock (&task->lock);
+
+ g_object_unref (task);
+}
+
+/**
+ * g_task_run_in_thread_sync:
+ * @task: a #GTask
+ * @task_func: a #GTaskThreadFunc
+ *
+ * Runs @task_func in another thread, and waits for it to return or be
+ * cancelled. You can use g_task_propagate_pointer(), etc, afterward
+ * to get the result of @task_func.
+ *
+ * See #GTaskThreadFunc for more details about how @task_func is handled.
+ *
+ * Normally this is used with tasks created with a %NULL
+ * <literal>callback</literal>, but note that even if the task does
+ * have a callback, it will not be invoked when @task_func returns.
+ *
+ * Since: 2.34
+ */
+void
+g_task_run_in_thread_sync (GTask *task,
+ GTaskThreadFunc task_func)
+{
+ g_return_if_fail (G_IS_TASK (task));
+
+ g_object_ref (task);
+
+ task->synchronous = TRUE;
+ g_task_start_task_thread (task, task_func);
+
+ while (!task->thread_complete)
+ g_cond_wait (&task->cond, &task->lock);
+
+ g_mutex_unlock (&task->lock);
+ g_object_unref (task);
+}
+
+/**
+ * g_task_attach_source:
+ * @task: a #GTask
+ * @source: the source to attach
+ * @callback: the callback to invoke when @source triggers
+ *
+ * A utility function for dealing with async operations where you need
+ * to wait for a #GSource to trigger. Attaches @source to @task's
+ * #GMainContext with @task's <link
+ * linkend="io-priority">priority</link>, and sets @source's callback
+ * to @callback, with @task as the callback's
+ * <literal>user_data</literal>.
+ *
+ * This takes a reference on @task until @source is destroyed.
+ *
+ * Since: 2.34
+ */
+void
+g_task_attach_source (GTask *task,
+ GSource *source,
+ GSourceFunc callback)
+{
+ g_source_set_callback (source, callback,
+ g_object_ref (task), g_object_unref);
+ g_source_set_priority (source, task->priority);
+ g_source_attach (source, task->context);
+}
+
+
+static gboolean
+g_task_propagate_error (GTask *task,
+ GError **error)
+{
+ if (task->check_cancellable &&
+ g_cancellable_set_error_if_cancelled (task->cancellable, error))
+ return TRUE;
+ else if (task->error)
+ {
+ g_propagate_error (error, task->error);
+ task->error = NULL;
+ return TRUE;
+ }
+ else
+ return FALSE;
+}
+
+/**
+ * g_task_return_pointer:
+ * @task: a #GTask
+ * @result: (allow-none) (transfer full): the pointer result of a task
+ * function
+ * @result_destroy: (allow-nonw): a #GDestroyNotify function.
+ *
+ * Sets @task's result to @result and completes the task. If @result
+ * is not %NULL, then @result_destroy will be used to free @result if
+ * the caller does not take ownership of it with
+ * g_task_propagate_pointer().
+ *
+ * "Completes the task" means that for an ordinary asynchronous task
+ * it will either invoke the task's callback, or else queue that
+ * callback to be invoked in the proper #GMainContext, or in the next
+ * iteration of the current #GMainContext. For a task run via
+ * g_task_run_in_thread() or g_task_run_in_thread_sync(), calling this
+ * method will save @result to be returned to the caller later, but
+ * the task will not actually be completed until the #GTaskThreadFunc
+ * exits.
+ *
+ * Note that since the task may be completed before returning from
+ * g_task_return_pointer(), you cannot assume that @result is still
+ * valid after calling this, unless you are still holding another
+ * reference on it.
+ *
+ * Since: 2.34
+ */
+void
+g_task_return_pointer (GTask *task,
+ gpointer result,
+ GDestroyNotify result_destroy)
+{
+ g_return_if_fail (task->result_set == FALSE);
+
+ task->result.pointer = result;
+ task->result_destroy = result_destroy;
+
+ g_task_return (task, G_TASK_RETURN_SUCCESS);
+}
+
+/**
+ * g_task_propagate_pointer:
+ * @task: a #GTask
+ * @error: return location for a #GError
+ *
+ * Gets the result of @task as a pointer, and transfers ownership
+ * of that value to the caller.
+ *
+ * If the task resulted in an error, or was cancelled, then this will
+ * instead return %NULL and set @error.
+ *
+ * Since this method transfers ownership of the return value (or
+ * error) to the caller, you may only call it once.
+ *
+ * Returns: (transfer full): the task result, or %NULL on error
+ *
+ * Since: 2.34
+ */
+gpointer
+g_task_propagate_pointer (GTask *task,
+ GError **error)
+{
+ if (g_task_propagate_error (task, error))
+ return NULL;
+
+ g_return_val_if_fail (task->result_set == TRUE, NULL);
+
+ task->result_destroy = NULL;
+ task->result_set = FALSE;
+ return task->result.pointer;
+}
+
+/**
+ * g_task_return_int:
+ * @task: a #GTask.
+ * @result: the integer (#gssize) result of a task function.
+ *
+ * Sets @task's result to @result and completes the task (see
+ * g_task_return_pointer() for more discussion of exactly what this
+ * means).
+ *
+ * Since: 2.34
+ */
+void
+g_task_return_int (GTask *task,
+ gssize result)
+{
+ g_return_if_fail (task->result_set == FALSE);
+
+ task->result.size = result;
+
+ g_task_return (task, G_TASK_RETURN_SUCCESS);
+}
+
+/**
+ * g_task_propagate_int:
+ * @task: a #GTask.
+ * @error: return location for a #GError
+ *
+ * Gets the result of @task as an integer (#gssize).
+ *
+ * If the task resulted in an error, or was cancelled, then this will
+ * instead return -1 and set @error.
+ *
+ * Since this method transfers ownership of the return value (or
+ * error) to the caller, you may only call it once.
+ *
+ * Returns: the task result, or -1 on error
+ *
+ * Since: 2.34
+ */
+gssize
+g_task_propagate_int (GTask *task,
+ GError **error)
+{
+ if (g_task_propagate_error (task, error))
+ return -1;
+
+ g_return_val_if_fail (task->result_set == TRUE, -1);
+
+ task->result_set = FALSE;
+ return task->result.size;
+}
+
+/**
+ * g_task_return_boolean:
+ * @task: a #GTask.
+ * @result: the #gboolean result of a task function.
+ *
+ * Sets @task's result to @result and completes the task (see
+ * g_task_return_pointer() for more discussion of exactly what this
+ * means).
+ *
+ * Since: 2.34
+ */
+void
+g_task_return_boolean (GTask *task,
+ gboolean result)
+{
+ g_return_if_fail (task->result_set == FALSE);
+
+ task->result.boolean = result;
+
+ g_task_return (task, G_TASK_RETURN_SUCCESS);
+}
+
+/**
+ * g_task_propagate_boolean:
+ * @task: a #GTask.
+ * @error: return location for a #GError
+ *
+ * Gets the result of @task as a #gboolean.
+ *
+ * If the task resulted in an error, or was cancelled, then this will
+ * instead return %FALSE and set @error.
+ *
+ * Since this method transfers ownership of the return value (or
+ * error) to the caller, you may only call it once.
+ *
+ * Returns: the task result, or %FALSE on error
+ *
+ * Since: 2.34
+ */
+gboolean
+g_task_propagate_boolean (GTask *task,
+ GError **error)
+{
+ if (g_task_propagate_error (task, error))
+ return FALSE;
+
+ g_return_val_if_fail (task->result_set == TRUE, FALSE);
+
+ task->result_set = FALSE;
+ return task->result.boolean;
+}
+
+/**
+ * g_task_return_error:
+ * @task: a #GTask.
+ * @error: (transfer full): the #GError result of a task function.
+ *
+ * Sets @task's result to @error (which @task assumes ownership of)
+ * and completes the task (see g_task_return_pointer() for more
+ * discussion of exactly what this means).
+ *
+ * Note that since the task takes ownership of @error, and since the
+ * task may be completed before returning from g_task_return_error(),
+ * you cannot assume that @error is still valid after calling this.
+ * Call g_error_copy() on the error if you need to keep a local copy
+ * as well.
+ *
+ * See also g_task_return_new_error().
+ *
+ * Since: 2.34
+ */
+void
+g_task_return_error (GTask *task,
+ GError *error)
+{
+ g_return_if_fail (task->result_set == FALSE);
+ g_return_if_fail (error != NULL);
+
+ task->error = error;
+
+ g_task_return (task, G_TASK_RETURN_ERROR);
+}
+
+/**
+ * g_task_return_new_error:
+ * @task: a #GTask.
+ * @domain: a #GQuark.
+ * @code: an error code.
+ * @format: a string with format characters.
+ * @...: a list of values to insert into @format.
+ *
+ * Sets @task's result to a new #GError created from @domain, @code,
+ * @format, and the remaining arguments, and completes the task (see
+ * g_task_return_pointer() for more discussion of exactly what this
+ * means).
+ *
+ * See also g_task_return_error().
+ *
+ * Since: 2.34
+ */
+void
+g_task_return_new_error (GTask *task,
+ GQuark domain,
+ gint code,
+ const char *format,
+ ...)
+{
+ GError *error;
+ va_list args;
+
+ va_start (args, format);
+ error = g_error_new_valist (domain, code, format, args);
+ va_end (args);
+
+ g_task_return_error (task, error);
+}
+
+/**
+ * g_task_return_error_if_cancelled:
+ * @task: a #GTask
+ *
+ * Checks if @task's #GCancellable has been cancelled, and if so, sets
+ * @task's error accordingly and completes the task (see
+ * g_task_return_pointer() for more discussion of exactly what this
+ * means).
+ *
+ * Return value: %TRUE if @task has been cancelled, %FALSE if not
+ *
+ * Since: 2.34
+ */
+gboolean
+g_task_return_error_if_cancelled (GTask *task)
+{
+ GError *error = NULL;
+
+ g_return_val_if_fail (task->result_set == FALSE, FALSE);
+
+ if (g_cancellable_set_error_if_cancelled (task->cancellable, &error))
+ {
+ /* We explicitly set task->error so this works even when
+ * check-cancellable is not set.
+ */
+ g_clear_error (&task->error);
+ task->error = error;
+
+ g_task_return (task, G_TASK_RETURN_ERROR);
+ return TRUE;
+ }
+ else
+ return FALSE;
+}
+
+/**
+ * g_task_had_error:
+ * @task: a #GTask.
+ *
+ * Tests if @task resulted in an error.
+ *
+ * Returns: %TRUE if the task resulted in an error, %FALSE otherwise.
+ *
+ * Since: 2.34
+ */
+gboolean
+g_task_had_error (GTask *task)
+{
+ if (task->error != NULL)
+ return TRUE;
+
+ if (task->check_cancellable && g_cancellable_is_cancelled (task->cancellable))
+ return TRUE;
+
+ return FALSE;
+}
+
+/**
+ * g_task_is_valid:
+ * @result: (type Gio.AsyncResult): A #GAsyncResult
+ * @source_object: (allow-none): the source object expected to be
+ * associated with the task
+ *
+ * Checks that @result is a #GTask, and that @source_object is its
+ * source object (or that @source_object is %NULL and @result has no
+ * source object). This can be used in g_return_if_fail() checks.
+ *
+ * Return value: %TRUE if @result and @source_object are valid, %FALSE
+ * if not
+ *
+ * Since: 2.34
+ */
+gboolean
+g_task_is_valid (gpointer result,
+ gpointer source_object)
+{
+ if (!G_IS_TASK (result))
+ return FALSE;
+
+ return G_TASK (result)->source_object == source_object;
+}
+
+static gint
+g_task_compare_priority (gconstpointer a,
+ gconstpointer b,
+ gpointer user_data)
+{
+ const GTask *ta = a;
+ const GTask *tb = b;
+ gboolean a_cancelled, b_cancelled;
+
+ a_cancelled = (ta->check_cancellable &&
+ g_cancellable_is_cancelled (ta->cancellable));
+ b_cancelled = (tb->check_cancellable &&
+ g_cancellable_is_cancelled (tb->cancellable));
+
+ /* Let already-cancelled tasks finish right away */
+ if (a_cancelled && !b_cancelled)
+ return -1;
+ else if (b_cancelled && !a_cancelled)
+ return 1;
+
+ /* Lower priority == run sooner == negative return value */
+ return ta->priority - tb->priority;
+}
+
+static void
+g_task_thread_pool_init (void)
+{
+ task_pool = g_thread_pool_new (g_task_thread_pool_thread, NULL,
+ 10, FALSE, NULL);
+ g_assert (task_pool != NULL);
+
+ g_thread_pool_set_sort_function (task_pool, g_task_compare_priority, NULL);
+}
+
+static void
+g_task_thread_pool_resort (void)
+{
+ g_thread_pool_set_sort_function (task_pool, g_task_compare_priority, NULL);
+}
+
+static void
+g_task_class_init (GTaskClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->finalize = g_task_finalize;
+}
+
+static gpointer
+g_task_get_user_data (GAsyncResult *res)
+{
+ return G_TASK (res)->callback_data;
+}
+
+static gboolean
+g_task_is_tagged (GAsyncResult *res,
+ gpointer source_tag)
+{
+ return G_TASK (res)->source_tag == source_tag;
+}
+
+static void
+g_task_async_result_iface_init (GAsyncResultIface *iface)
+{
+ iface->get_user_data = g_task_get_user_data;
+ iface->get_source_object = g_task_ref_source_object;
+ iface->is_tagged = g_task_is_tagged;
+}
diff --git a/gio/gtask.h b/gio/gtask.h
new file mode 100644
index 0000000..7220fe2
--- /dev/null
+++ b/gio/gtask.h
@@ -0,0 +1,160 @@
+/* GIO - GLib Input, Output and Streaming Library
+ *
+ * Copyright 2011 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#if !defined (__GIO_GIO_H_INSIDE__) && !defined (GIO_COMPILATION)
+#error "Only <gio/gio.h> can be included directly."
+#endif
+
+#ifndef __G_TASK_H__
+#define __G_TASK_H__
+
+#include <gio/giotypes.h>
+
+G_BEGIN_DECLS
+
+#define G_TYPE_TASK (g_task_get_type ())
+#define G_TASK(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), G_TYPE_TASK, GTask))
+#define G_TASK_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), G_TYPE_TASK, GTaskClass))
+#define G_IS_TASK(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_TYPE_TASK))
+#define G_IS_TASK_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), G_TYPE_TASK))
+#define G_TASK_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_TYPE_TASK, GTaskClass))
+
+typedef struct _GTaskClass GTaskClass;
+
+GLIB_AVAILABLE_IN_2_34
+GType g_task_get_type (void) G_GNUC_CONST;
+
+GLIB_AVAILABLE_IN_2_34
+GTask *g_task_new (gpointer source_object,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer callback_data);
+
+GLIB_AVAILABLE_IN_2_34
+void g_task_report_error (gpointer source_object,
+ GAsyncReadyCallback callback,
+ gpointer callback_data,
+ gpointer source_tag,
+ GError *error);
+GLIB_AVAILABLE_IN_2_34
+void g_task_report_new_error (gpointer source_object,
+ GAsyncReadyCallback callback,
+ gpointer callback_data,
+ gpointer source_tag,
+ GQuark domain,
+ gint code,
+ const char *format,
+ ...);
+
+GLIB_AVAILABLE_IN_2_34
+void g_task_set_task_data (GTask *task,
+ gpointer task_data,
+ GDestroyNotify task_data_destroy);
+GLIB_AVAILABLE_IN_2_34
+void g_task_set_priority (GTask *task,
+ gint priority);
+GLIB_AVAILABLE_IN_2_34
+void g_task_set_check_cancellable (GTask *task,
+ gboolean check_cancellable);
+GLIB_AVAILABLE_IN_2_34
+void g_task_set_source_tag (GTask *task,
+ gpointer source_tag);
+
+GLIB_AVAILABLE_IN_2_34
+gpointer g_task_get_source_object (GTask *task);
+GLIB_AVAILABLE_IN_2_34
+gpointer g_task_get_task_data (GTask *task);
+GLIB_AVAILABLE_IN_2_34
+gint g_task_get_priority (GTask *task);
+GLIB_AVAILABLE_IN_2_34
+GMainContext *g_task_get_context (GTask *task);
+GLIB_AVAILABLE_IN_2_34
+GCancellable *g_task_get_cancellable (GTask *task);
+GLIB_AVAILABLE_IN_2_34
+gboolean g_task_get_check_cancellable (GTask *task);
+GLIB_AVAILABLE_IN_2_34
+gpointer g_task_get_source_tag (GTask *task);
+
+GLIB_AVAILABLE_IN_2_34
+gboolean g_task_is_valid (gpointer result,
+ gpointer source_object);
+
+
+typedef void (*GTaskThreadFunc) (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable);
+GLIB_AVAILABLE_IN_2_34
+void g_task_run_in_thread (GTask *task,
+ GTaskThreadFunc task_func);
+GLIB_AVAILABLE_IN_2_34
+void g_task_run_in_thread_sync (GTask *task,
+ GTaskThreadFunc task_func);
+GLIB_AVAILABLE_IN_2_34
+gboolean g_task_set_return_on_cancel (GTask *task,
+ gboolean return_on_cancel);
+GLIB_AVAILABLE_IN_2_34
+gboolean g_task_get_return_on_cancel (GTask *task);
+
+GLIB_AVAILABLE_IN_2_34
+void g_task_attach_source (GTask *task,
+ GSource *source,
+ GSourceFunc callback);
+
+
+GLIB_AVAILABLE_IN_2_34
+void g_task_return_pointer (GTask *task,
+ gpointer result,
+ GDestroyNotify result_destroy);
+GLIB_AVAILABLE_IN_2_34
+void g_task_return_boolean (GTask *task,
+ gboolean result);
+GLIB_AVAILABLE_IN_2_34
+void g_task_return_int (GTask *task,
+ gssize result);
+
+GLIB_AVAILABLE_IN_2_34
+void g_task_return_error (GTask *task,
+ GError *error);
+GLIB_AVAILABLE_IN_2_34
+void g_task_return_new_error (GTask *task,
+ GQuark domain,
+ gint code,
+ const char *format,
+ ...) G_GNUC_PRINTF (4, 5);
+
+GLIB_AVAILABLE_IN_2_34
+gboolean g_task_return_error_if_cancelled (GTask *task);
+
+GLIB_AVAILABLE_IN_2_34
+gpointer g_task_propagate_pointer (GTask *task,
+ GError **error);
+GLIB_AVAILABLE_IN_2_34
+gboolean g_task_propagate_boolean (GTask *task,
+ GError **error);
+GLIB_AVAILABLE_IN_2_34
+gssize g_task_propagate_int (GTask *task,
+ GError **error);
+GLIB_AVAILABLE_IN_2_34
+gboolean g_task_had_error (GTask *task);
+
+G_END_DECLS
+
+#endif /* __G_TASK_H__ */
diff --git a/gio/tests/.gitignore b/gio/tests/.gitignore
index 7492902..c99ed93 100644
--- a/gio/tests/.gitignore
+++ b/gio/tests/.gitignore
@@ -94,6 +94,7 @@ socket
socket-client
socket-server
srvtarget
+task
test.mo
test.gresource
test_resources.c
diff --git a/gio/tests/Makefile.am b/gio/tests/Makefile.am
index 14411e2..26ce714 100644
--- a/gio/tests/Makefile.am
+++ b/gio/tests/Makefile.am
@@ -60,6 +60,7 @@ TEST_PROGS += \
fileattributematcher \
resources \
proxy-test \
+ task \
$(NULL)
if HAVE_DBUS_DAEMON
@@ -461,6 +462,9 @@ gmenumodel_LDADD = $(progs_ldadd)
proxy_test_SOURCES = proxy-test.c
proxy_test_LDADD = $(progs_ldadd)
+task_SOURCES = task.c
+task_LDADD = $(progs_ldadd)
+
schema_tests = \
schema-tests/array-default-not-in-choices.gschema.xml \
schema-tests/bad-choice.gschema.xml \
diff --git a/gio/tests/task.c b/gio/tests/task.c
new file mode 100644
index 0000000..7c7606f
--- /dev/null
+++ b/gio/tests/task.c
@@ -0,0 +1,1668 @@
+/*
+ * Copyright 2012 Red Hat, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2 of the licence or (at
+ * your option) any later version.
+ *
+ * See the included COPYING file for more information.
+ */
+
+#include <gio/gio.h>
+
+static GMainLoop *loop;
+static GThread *main_thread;
+static gssize magic;
+
+/* We need objects for a few tests where we don't care what type
+ * they are, just that they're GObjects.
+ */
+#define g_dummy_object_new g_socket_client_new
+
+/* test_basic */
+
+static void
+basic_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ gssize *result_out = user_data;
+ GError *error = NULL;
+
+ g_assert (object == NULL);
+ 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)));
+
+ *result_out = g_task_propagate_int (G_TASK (result), &error);
+ g_assert_no_error (error);
+
+ g_main_loop_quit (loop);
+}
+
+static gboolean
+basic_return (gpointer user_data)
+{
+ GTask *task = user_data;
+
+ g_task_return_int (task, magic);
+ g_object_unref (task);
+
+ return FALSE;
+}
+
+static void
+basic_destroy_notify (gpointer user_data)
+{
+ gboolean *destroyed = user_data;
+
+ *destroyed = TRUE;
+}
+
+static void
+test_basic (void)
+{
+ GTask *task;
+ gssize result;
+ gboolean task_data_destroyed = 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_idle_add (basic_return, task);
+ g_main_loop_run (loop);
+
+ g_assert_cmpint (result, ==, magic);
+ g_assert (task_data_destroyed == TRUE);
+ g_assert (task == NULL);
+}
+
+/* test_error */
+
+static void
+error_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ gssize *result_out = user_data;
+ GError *error = NULL;
+
+ g_assert (object == NULL);
+ 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)));
+
+ *result_out = g_task_propagate_int (G_TASK (result), &error);
+ g_assert_error (error, G_IO_ERROR, G_IO_ERROR_FAILED);
+ g_error_free (error);
+
+ g_main_loop_quit (loop);
+}
+
+static gboolean
+error_return (gpointer user_data)
+{
+ GTask *task = user_data;
+
+ g_task_return_new_error (task,
+ G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Failed");
+ g_object_unref (task);
+
+ return FALSE;
+}
+
+static void
+error_destroy_notify (gpointer user_data)
+{
+ gboolean *destroyed = user_data;
+
+ *destroyed = TRUE;
+}
+
+static void
+test_error (void)
+{
+ GTask *task;
+ gssize result;
+ gboolean first_task_data_destroyed = FALSE;
+ gboolean second_task_data_destroyed = FALSE;
+
+ task = g_task_new (NULL, NULL, error_callback, &result);
+ g_object_add_weak_pointer (G_OBJECT (task), (gpointer *)&task);
+
+ g_assert (first_task_data_destroyed == FALSE);
+ g_task_set_task_data (task, &first_task_data_destroyed, error_destroy_notify);
+ g_assert (first_task_data_destroyed == FALSE);
+
+ /* Calling g_task_set_task_data() again will destroy the first data */
+ g_task_set_task_data (task, &second_task_data_destroyed, error_destroy_notify);
+ g_assert (first_task_data_destroyed == TRUE);
+ g_assert (second_task_data_destroyed == FALSE);
+
+ g_idle_add (error_return, task);
+ g_main_loop_run (loop);
+
+ g_assert_cmpint (result, ==, -1);
+ g_assert (second_task_data_destroyed == TRUE);
+ g_assert (task == NULL);
+}
+
+/* test_return_from_same_iteration: calling g_task_return_* from the
+ * loop iteration the task was created in defers completion until the
+ * next iteration.
+ */
+gboolean same_result = FALSE;
+
+static void
+same_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ gboolean *result_out = user_data;
+ GError *error = NULL;
+
+ g_assert (object == NULL);
+ 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)));
+
+ *result_out = g_task_propagate_boolean (G_TASK (result), &error);
+ g_assert_no_error (error);
+
+ g_main_loop_quit (loop);
+}
+
+static gboolean
+same_start (gpointer user_data)
+{
+ gpointer *weak_pointer = user_data;
+ GTask *task;
+
+ task = g_task_new (NULL, NULL, same_callback, &same_result);
+ *weak_pointer = task;
+ g_object_add_weak_pointer (G_OBJECT (task), weak_pointer);
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+
+ /* same_callback should not have been invoked yet */
+ g_assert (same_result == FALSE);
+ g_assert (*weak_pointer == task);
+
+ return FALSE;
+}
+
+static void
+test_return_from_same_iteration (void)
+{
+ gpointer weak_pointer;
+
+ g_idle_add (same_start, &weak_pointer);
+ g_main_loop_run (loop);
+
+ g_assert (same_result == TRUE);
+ g_assert (weak_pointer == NULL);
+}
+
+/* test_return_from_toplevel: calling g_task_return_* from outside any
+ * main loop completes the task inside the main loop.
+ */
+
+static void
+toplevel_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ gboolean *result_out = user_data;
+ GError *error = NULL;
+
+ g_assert (object == NULL);
+ 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)));
+
+ *result_out = g_task_propagate_boolean (G_TASK (result), &error);
+ g_assert_no_error (error);
+
+ g_main_loop_quit (loop);
+}
+
+static void
+test_return_from_toplevel (void)
+{
+ GTask *task;
+ gboolean result = FALSE;
+
+ task = g_task_new (NULL, NULL, toplevel_callback, &result);
+ g_object_add_weak_pointer (G_OBJECT (task), (gpointer *)&task);
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+
+ /* toplevel_callback should not have been invoked yet */
+ g_assert (result == FALSE);
+ g_assert (task != NULL);
+
+ g_main_loop_run (loop);
+
+ g_assert (result == TRUE);
+ g_assert (task == NULL);
+}
+
+/* test_return_from_anon_thread: calling g_task_return_* from a
+ * thread with no thread-default main context will complete the
+ * task in the task's context/thread.
+ */
+
+GThread *anon_thread;
+
+static void
+anon_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ gssize *result_out = user_data;
+ GError *error = NULL;
+
+ g_assert (object == NULL);
+ 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 (g_thread_self () == main_thread);
+
+ *result_out = g_task_propagate_int (G_TASK (result), &error);
+ g_assert_no_error (error);
+
+ g_main_loop_quit (loop);
+}
+
+static gpointer
+anon_thread_func (gpointer user_data)
+{
+ GTask *task = user_data;
+
+ g_task_return_int (task, magic);
+ g_object_unref (task);
+
+ return NULL;
+}
+
+static gboolean
+anon_start (gpointer user_data)
+{
+ GTask *task = user_data;
+
+ anon_thread = g_thread_new ("test_return_from_anon_thread",
+ anon_thread_func, task);
+ return FALSE;
+}
+
+static void
+test_return_from_anon_thread (void)
+{
+ GTask *task;
+ gssize result = 0;
+
+ task = g_task_new (NULL, NULL, anon_callback, &result);
+ g_object_add_weak_pointer (G_OBJECT (task), (gpointer *)&task);
+
+ g_idle_add (anon_start, task);
+ g_main_loop_run (loop);
+
+ g_thread_join (anon_thread);
+
+ g_assert_cmpint (result, ==, magic);
+ g_assert (task == NULL);
+}
+
+/* test_return_from_wrong_thread: calling g_task_return_* from a
+ * thread with its own thread-default main context will complete the
+ * task in the task's context/thread.
+ */
+
+GThread *wrong_thread;
+
+static void
+wrong_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ gssize *result_out = user_data;
+ GError *error = NULL;
+
+ g_assert (object == NULL);
+ 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 (g_thread_self () == main_thread);
+
+ *result_out = g_task_propagate_int (G_TASK (result), &error);
+ g_assert_no_error (error);
+
+ g_main_loop_quit (loop);
+}
+
+static gpointer
+wrong_thread_func (gpointer user_data)
+{
+ GTask *task = user_data;
+ GMainContext *context;
+
+ context = g_main_context_new ();
+ g_main_context_push_thread_default (context);
+
+ g_assert (g_task_get_context (task) != context);
+
+ g_task_return_int (task, magic);
+ g_object_unref (task);
+
+ g_main_context_pop_thread_default (context);
+ g_main_context_unref (context);
+
+ return NULL;
+}
+
+static gboolean
+wrong_start (gpointer user_data)
+{
+ GTask *task = user_data;
+
+ wrong_thread = g_thread_new ("test_return_from_anon_thread",
+ wrong_thread_func, task);
+ return FALSE;
+}
+
+static void
+test_return_from_wrong_thread (void)
+{
+ GTask *task;
+ gssize result = 0;
+
+ task = g_task_new (NULL, NULL, wrong_callback, &result);
+ g_object_add_weak_pointer (G_OBJECT (task), (gpointer *)&task);
+
+ g_idle_add (wrong_start, task);
+ g_main_loop_run (loop);
+
+ g_thread_join (wrong_thread);
+
+ g_assert_cmpint (result, ==, magic);
+ g_assert (task == NULL);
+}
+
+/* test_no_callback */
+
+static void
+test_no_callback (void)
+{
+ GTask *task;
+
+ task = g_task_new (NULL, NULL, NULL, NULL);
+ g_object_add_weak_pointer (G_OBJECT (task), (gpointer *)&task);
+
+ 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);
+}
+
+/* test_report_error */
+
+static void test_report_error (void);
+
+static void
+report_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ gpointer *weak_pointer = user_data;
+ GError *error = NULL;
+ gssize ret;
+
+ g_assert (object == NULL);
+ g_assert (g_task_is_valid (result, object));
+ g_assert (g_async_result_get_user_data (result) == user_data);
+ 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)));
+
+ ret = g_task_propagate_int (G_TASK (result), &error);
+ g_assert_error (error, G_IO_ERROR, G_IO_ERROR_FAILED);
+ g_assert_cmpint (ret, ==, -1);
+ g_error_free (error);
+
+ *weak_pointer = result;
+ g_object_add_weak_pointer (G_OBJECT (result), weak_pointer);
+
+ g_main_loop_quit (loop);
+}
+
+static void
+test_report_error (void)
+{
+ gpointer weak_pointer = (gpointer)-1;
+
+ g_task_report_new_error (NULL, report_callback, &weak_pointer,
+ test_report_error,
+ G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Failed");
+ g_main_loop_run (loop);
+
+ g_assert (weak_pointer == NULL);
+}
+
+/* test_priority: tasks complete in priority order */
+
+static int counter = 0;
+
+static void
+priority_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ gssize *ret_out = user_data;
+ GError *error = NULL;
+
+ g_assert (object == NULL);
+ 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_task_propagate_boolean (G_TASK (result), &error);
+ g_assert_no_error (error);
+
+ *ret_out = ++counter;
+
+ if (counter == 3)
+ g_main_loop_quit (loop);
+}
+
+static void
+test_priority (void)
+{
+ GTask *t1, *t2, *t3;
+ gssize ret1, ret2, ret3;
+
+ /* t2 has higher priority than either t1 or t3, so we can't
+ * accidentally pass the test just by completing the tasks in the
+ * order they were created (or in reverse order).
+ */
+
+ t1 = g_task_new (NULL, NULL, priority_callback, &ret1);
+ g_task_set_priority (t1, G_PRIORITY_DEFAULT);
+ g_task_return_boolean (t1, TRUE);
+ g_object_unref (t1);
+
+ t2 = g_task_new (NULL, NULL, priority_callback, &ret2);
+ g_task_set_priority (t2, G_PRIORITY_HIGH);
+ g_task_return_boolean (t2, TRUE);
+ g_object_unref (t2);
+
+ t3 = g_task_new (NULL, NULL, priority_callback, &ret3);
+ g_task_set_priority (t3, G_PRIORITY_LOW);
+ g_task_return_boolean (t3, TRUE);
+ g_object_unref (t3);
+
+ g_main_loop_run (loop);
+
+ g_assert_cmpint (ret2, ==, 1);
+ g_assert_cmpint (ret1, ==, 2);
+ g_assert_cmpint (ret3, ==, 3);
+}
+
+/* test_check_cancellable: cancellation overrides return value */
+
+enum {
+ CANCEL_BEFORE = (1 << 1),
+ CANCEL_AFTER = (1 << 2),
+ CHECK_CANCELLABLE = (1 << 3)
+};
+#define NUM_CANCEL_TESTS (CANCEL_BEFORE | CANCEL_AFTER | CHECK_CANCELLABLE)
+
+static void
+cancel_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ int state = GPOINTER_TO_INT (user_data);
+ GTask *task;
+ GCancellable *cancellable;
+ GError *error = NULL;
+
+ g_assert (object == NULL);
+ g_assert (g_task_is_valid (result, object));
+ g_assert (g_async_result_get_user_data (result) == user_data);
+
+ task = G_TASK (result);
+ cancellable = g_task_get_cancellable (task);
+ g_assert (G_IS_CANCELLABLE (cancellable));
+
+ if (state & (CANCEL_BEFORE | CANCEL_AFTER))
+ g_assert (g_cancellable_is_cancelled (cancellable));
+ else
+ g_assert (!g_cancellable_is_cancelled (cancellable));
+
+ if (state & CHECK_CANCELLABLE)
+ g_assert (g_task_get_check_cancellable (task));
+ else
+ g_assert (!g_task_get_check_cancellable (task));
+
+ if (g_task_propagate_boolean (task, &error))
+ {
+ g_assert (!g_cancellable_is_cancelled (cancellable) ||
+ !g_task_get_check_cancellable (task));
+ }
+ else
+ {
+ g_assert (g_cancellable_is_cancelled (cancellable) &&
+ g_task_get_check_cancellable (task));
+ g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
+ g_error_free (error);
+ }
+
+ g_main_loop_quit (loop);
+}
+
+static void
+test_check_cancellable (void)
+{
+ GTask *task;
+ GCancellable *cancellable;
+ int state;
+
+ cancellable = g_cancellable_new ();
+
+ for (state = 0; state <= NUM_CANCEL_TESTS; state++)
+ {
+ task = g_task_new (NULL, cancellable, cancel_callback,
+ GINT_TO_POINTER (state));
+ g_task_set_check_cancellable (task, (state & CHECK_CANCELLABLE) != 0);
+
+ if (state & CANCEL_BEFORE)
+ g_cancellable_cancel (cancellable);
+ g_task_return_boolean (task, TRUE);
+ if (state & CANCEL_AFTER)
+ g_cancellable_cancel (cancellable);
+
+ g_main_loop_run (loop);
+ g_object_unref (task);
+ g_cancellable_reset (cancellable);
+ }
+
+ g_object_unref (cancellable);
+}
+
+/* test_return_if_cancelled */
+
+static void
+return_if_cancelled_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GError *error = NULL;
+
+ g_assert (object == NULL);
+ 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_task_propagate_boolean (G_TASK (result), &error);
+ g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
+ g_clear_error (&error);
+
+ g_main_loop_quit (loop);
+}
+
+static void
+test_return_if_cancelled (void)
+{
+ GTask *task;
+ GCancellable *cancellable;
+ gboolean cancelled;
+
+ cancellable = g_cancellable_new ();
+
+ task = g_task_new (NULL, cancellable, return_if_cancelled_callback, NULL);
+ g_cancellable_cancel (cancellable);
+ cancelled = g_task_return_error_if_cancelled (task);
+ g_assert (cancelled);
+ g_main_loop_run (loop);
+ g_object_unref (task);
+ g_cancellable_reset (cancellable);
+
+ task = g_task_new (NULL, cancellable, return_if_cancelled_callback, NULL);
+ g_task_set_check_cancellable (task, FALSE);
+ g_cancellable_cancel (cancellable);
+ cancelled = g_task_return_error_if_cancelled (task);
+ g_assert (cancelled);
+ g_main_loop_run (loop);
+ g_object_unref (task);
+ g_object_unref (cancellable);
+}
+
+/* test_run_in_thread */
+
+static void
+run_in_thread_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ gboolean *done = user_data;
+ GError *error = NULL;
+ gssize ret;
+
+ g_assert (g_thread_self () == main_thread);
+
+ g_assert (object == NULL);
+ 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)));
+
+ ret = g_task_propagate_int (G_TASK (result), &error);
+ g_assert_no_error (error);
+ g_assert_cmpint (ret, ==, magic);
+
+ *done = TRUE;
+ g_main_loop_quit (loop);
+}
+
+static void
+run_in_thread_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ gboolean *thread_ran = task_data;
+
+ 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 (g_thread_self () != main_thread);
+
+ *thread_ran = TRUE;
+ g_task_return_int (task, magic);
+}
+
+static void
+test_run_in_thread (void)
+{
+ GTask *task;
+ volatile gboolean thread_ran = FALSE;
+ gboolean done = FALSE;
+
+ task = g_task_new (NULL, NULL, run_in_thread_callback, &done);
+ g_object_add_weak_pointer (G_OBJECT (task), (gpointer *)&task);
+
+ g_task_set_task_data (task, (gpointer)&thread_ran, NULL);
+ g_task_run_in_thread (task, run_in_thread_thread);
+ g_object_unref (task);
+
+ while (!thread_ran)
+ g_usleep (100);
+
+ g_assert (done == FALSE);
+ g_assert (task != NULL);
+
+ g_main_loop_run (loop);
+
+ g_assert (done == TRUE);
+ g_assert (task == NULL);
+}
+
+/* test_run_in_thread_sync */
+
+static void
+run_in_thread_sync_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ /* g_task_run_in_thread_sync() does not invoke the task's callback */
+ g_assert_not_reached ();
+}
+
+static void
+run_in_thread_sync_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ gboolean *thread_ran = task_data;
+
+ 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 (g_thread_self () != main_thread);
+
+ *thread_ran = TRUE;
+ g_task_return_int (task, magic);
+}
+
+static void
+test_run_in_thread_sync (void)
+{
+ GTask *task;
+ gboolean thread_ran = FALSE;
+ gssize ret;
+ GError *error = NULL;
+
+ task = g_task_new (NULL, NULL, run_in_thread_sync_callback, NULL);
+
+ g_task_set_task_data (task, &thread_ran, NULL);
+ g_task_run_in_thread_sync (task, run_in_thread_sync_thread);
+
+ g_assert (thread_ran == TRUE);
+ g_assert (task != NULL);
+ g_assert (!g_task_had_error (task));
+
+ ret = g_task_propagate_int (task, &error);
+ g_assert_no_error (error);
+ g_assert_cmpint (ret, ==, magic);
+
+ g_object_unref (task);
+}
+
+/* test_run_in_thread_priority */
+
+static GMutex fake_task_mutex, last_fake_task_mutex;
+static gint sequence_number = 0;
+
+static void
+quit_main_loop_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GError *error = NULL;
+ gboolean ret;
+
+ g_assert (g_thread_self () == main_thread);
+
+ g_assert (object == NULL);
+ 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)));
+
+ ret = g_task_propagate_boolean (G_TASK (result), &error);
+ g_assert_no_error (error);
+ g_assert_cmpint (ret, ==, TRUE);
+
+ g_main_loop_quit (loop);
+}
+
+static void
+set_sequence_number_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ gint *seq_no_p = task_data;
+
+ *seq_no_p = ++sequence_number;
+ g_task_return_boolean (task, TRUE);
+}
+
+static void
+fake_task_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ GMutex *mutex = task_data;
+
+ g_mutex_lock (mutex);
+ g_mutex_unlock (mutex);
+ g_task_return_boolean (task, TRUE);
+}
+
+#define G_TASK_THREAD_POOL_SIZE 10
+
+static void
+test_run_in_thread_priority (void)
+{
+ GTask *task;
+ GCancellable *cancellable;
+ int seq_a, seq_b, seq_c, seq_d;
+ int i;
+
+ /* Flush the thread pool, then clog it up with junk tasks */
+ g_thread_pool_stop_unused_threads ();
+
+ g_mutex_lock (&fake_task_mutex);
+ for (i = 0; i < G_TASK_THREAD_POOL_SIZE - 1; i++)
+ {
+ task = g_task_new (NULL, NULL, NULL, NULL);
+ g_task_set_task_data (task, &fake_task_mutex, NULL);
+ g_assert_cmpint (g_task_get_priority (task), ==, G_PRIORITY_DEFAULT);
+ g_task_set_priority (task, G_PRIORITY_HIGH * 2);
+ g_assert_cmpint (g_task_get_priority (task), ==, G_PRIORITY_HIGH * 2);
+ g_task_run_in_thread (task, fake_task_thread);
+ g_object_unref (task);
+ }
+
+ g_mutex_lock (&last_fake_task_mutex);
+ task = g_task_new (NULL, NULL, NULL, NULL);
+ g_task_set_task_data (task, &last_fake_task_mutex, NULL);
+ g_task_set_priority (task, G_PRIORITY_HIGH * 2);
+ g_task_run_in_thread (task, fake_task_thread);
+ g_object_unref (task);
+
+ /* Queue three more tasks that we'll arrange to have run serially */
+ task = g_task_new (NULL, NULL, NULL, NULL);
+ g_task_set_task_data (task, &seq_a, NULL);
+ g_task_run_in_thread (task, set_sequence_number_thread);
+ g_object_unref (task);
+
+ task = g_task_new (NULL, NULL, quit_main_loop_callback, NULL);
+ g_task_set_task_data (task, &seq_b, NULL);
+ g_task_set_priority (task, G_PRIORITY_LOW);
+ g_task_run_in_thread (task, set_sequence_number_thread);
+ g_object_unref (task);
+
+ task = g_task_new (NULL, NULL, NULL, NULL);
+ g_task_set_task_data (task, &seq_c, NULL);
+ g_task_set_priority (task, G_PRIORITY_HIGH);
+ g_task_run_in_thread (task, set_sequence_number_thread);
+ g_object_unref (task);
+
+ cancellable = g_cancellable_new ();
+ task = g_task_new (NULL, cancellable, NULL, NULL);
+ g_task_set_task_data (task, &seq_d, NULL);
+ g_task_run_in_thread (task, set_sequence_number_thread);
+ g_cancellable_cancel (cancellable);
+ g_object_unref (cancellable);
+ g_object_unref (task);
+
+ /* Let the last fake task complete; the four other tasks will then
+ * complete serially, in the order D, C, A, B, and B will quit the
+ * main loop.
+ */
+ g_mutex_unlock (&last_fake_task_mutex);
+ g_main_loop_run (loop);
+
+ g_assert_cmpint (seq_d, ==, 1);
+ g_assert_cmpint (seq_c, ==, 2);
+ g_assert_cmpint (seq_a, ==, 3);
+ g_assert_cmpint (seq_b, ==, 4);
+
+ g_mutex_unlock (&fake_task_mutex);
+}
+
+/* test_return_on_cancel */
+
+GMutex roc_init_mutex, roc_finish_mutex;
+GCond roc_init_cond, roc_finish_cond;
+
+typedef enum {
+ THREAD_STARTING,
+ THREAD_RUNNING,
+ THREAD_CANCELLED,
+ THREAD_COMPLETED
+} ThreadState;
+
+static void
+return_on_cancel_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ gboolean *callback_ran = user_data;
+ GError *error = NULL;
+ gssize ret;
+
+ g_assert (g_thread_self () == main_thread);
+
+ g_assert (object == NULL);
+ 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)));
+
+ ret = g_task_propagate_int (G_TASK (result), &error);
+ g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
+ g_clear_error (&error);
+ g_assert_cmpint (ret, ==, -1);
+
+ *callback_ran = TRUE;
+ g_main_loop_quit (loop);
+}
+
+static void
+return_on_cancel_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ ThreadState *state = task_data;
+
+ 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 (g_thread_self () != main_thread);
+
+ g_mutex_lock (&roc_init_mutex);
+ *state = THREAD_RUNNING;
+ g_cond_signal (&roc_init_cond);
+ g_mutex_unlock (&roc_init_mutex);
+
+ g_mutex_lock (&roc_finish_mutex);
+
+ if (!g_task_get_return_on_cancel (task) ||
+ g_task_set_return_on_cancel (task, FALSE))
+ {
+ *state = THREAD_COMPLETED;
+ g_task_return_int (task, magic);
+ }
+ else
+ *state = THREAD_CANCELLED;
+
+ g_cond_signal (&roc_finish_cond);
+ g_mutex_unlock (&roc_finish_mutex);
+}
+
+static void
+test_return_on_cancel (void)
+{
+ GTask *task;
+ GCancellable *cancellable;
+ volatile ThreadState thread_state;
+ gboolean callback_ran;
+
+ cancellable = g_cancellable_new ();
+
+ /* If return-on-cancel is FALSE (default), the task does not return
+ * early.
+ */
+ callback_ran = FALSE;
+ thread_state = THREAD_STARTING;
+ task = g_task_new (NULL, cancellable, return_on_cancel_callback, &callback_ran);
+
+ g_task_set_task_data (task, (gpointer)&thread_state, NULL);
+ g_mutex_lock (&roc_init_mutex);
+ g_mutex_lock (&roc_finish_mutex);
+ g_task_run_in_thread (task, return_on_cancel_thread);
+ g_object_unref (task);
+
+ while (thread_state == THREAD_STARTING)
+ g_cond_wait (&roc_init_cond, &roc_init_mutex);
+ g_mutex_unlock (&roc_init_mutex);
+
+ g_assert (thread_state == THREAD_RUNNING);
+ g_assert (callback_ran == FALSE);
+
+ g_cancellable_cancel (cancellable);
+ g_mutex_unlock (&roc_finish_mutex);
+ g_main_loop_run (loop);
+
+ g_assert (thread_state == THREAD_COMPLETED);
+ g_assert (callback_ran == TRUE);
+
+ g_cancellable_reset (cancellable);
+
+ /* If return-on-cancel is TRUE, it does return early */
+ callback_ran = FALSE;
+ thread_state = THREAD_STARTING;
+ task = g_task_new (NULL, cancellable, return_on_cancel_callback, &callback_ran);
+ g_task_set_return_on_cancel (task, TRUE);
+
+ g_task_set_task_data (task, (gpointer)&thread_state, NULL);
+ g_mutex_lock (&roc_init_mutex);
+ g_mutex_lock (&roc_finish_mutex);
+ g_task_run_in_thread (task, return_on_cancel_thread);
+ g_object_unref (task);
+
+ while (thread_state == THREAD_STARTING)
+ g_cond_wait (&roc_init_cond, &roc_init_mutex);
+ g_mutex_unlock (&roc_init_mutex);
+
+ g_assert (thread_state == THREAD_RUNNING);
+ g_assert (callback_ran == FALSE);
+
+ g_cancellable_cancel (cancellable);
+ g_main_loop_run (loop);
+ g_assert (thread_state == THREAD_RUNNING);
+ g_assert (callback_ran == TRUE);
+
+ while (thread_state == THREAD_RUNNING)
+ g_cond_wait (&roc_finish_cond, &roc_finish_mutex);
+ g_mutex_unlock (&roc_finish_mutex);
+
+ g_assert (thread_state == THREAD_CANCELLED);
+ /* We can't g_assert (task == NULL) here because it won't become NULL
+ * until a little bit after roc_finish_cond is signaled.
+ */
+
+ 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;
+ thread_state = THREAD_STARTING;
+ task = g_task_new (NULL, cancellable, return_on_cancel_callback, &callback_ran);
+ g_task_set_return_on_cancel (task, TRUE);
+
+ g_cancellable_cancel (cancellable);
+
+ g_task_set_task_data (task, (gpointer)&thread_state, NULL);
+ g_mutex_lock (&roc_init_mutex);
+ g_mutex_lock (&roc_finish_mutex);
+ g_task_run_in_thread (task, return_on_cancel_thread);
+ g_object_unref (task);
+
+ g_main_loop_run (loop);
+ g_assert (callback_ran == TRUE);
+
+ while (thread_state == THREAD_STARTING)
+ g_cond_wait (&roc_init_cond, &roc_init_mutex);
+ g_mutex_unlock (&roc_init_mutex);
+
+ g_assert (thread_state == THREAD_RUNNING);
+
+ while (thread_state == THREAD_RUNNING)
+ g_cond_wait (&roc_finish_cond, &roc_finish_mutex);
+ g_mutex_unlock (&roc_finish_mutex);
+
+ g_assert (thread_state == THREAD_CANCELLED);
+
+ g_object_unref (cancellable);
+}
+
+/* test_return_on_cancel_sync */
+
+static gpointer
+cancel_sync_runner_thread (gpointer task)
+{
+ g_task_run_in_thread_sync (task, return_on_cancel_thread);
+ return NULL;
+}
+
+static void
+test_return_on_cancel_sync (void)
+{
+ GTask *task;
+ GCancellable *cancellable;
+ volatile ThreadState thread_state;
+ GThread *runner_thread;
+ gssize ret;
+ GError *error = NULL;
+
+ cancellable = g_cancellable_new ();
+
+ /* If return-on-cancel is FALSE, the task does not return early.
+ */
+ thread_state = THREAD_STARTING;
+ task = g_task_new (NULL, cancellable, run_in_thread_sync_callback, NULL);
+
+ g_task_set_task_data (task, (gpointer)&thread_state, NULL);
+ g_mutex_lock (&roc_init_mutex);
+ g_mutex_lock (&roc_finish_mutex);
+ runner_thread = g_thread_new ("return-on-cancel-sync runner thread",
+ cancel_sync_runner_thread, task);
+
+ while (thread_state == THREAD_STARTING)
+ g_cond_wait (&roc_init_cond, &roc_init_mutex);
+ g_mutex_unlock (&roc_init_mutex);
+
+ g_assert (thread_state == THREAD_RUNNING);
+
+ g_cancellable_cancel (cancellable);
+ g_mutex_unlock (&roc_finish_mutex);
+ g_thread_join (runner_thread);
+ g_assert (thread_state == THREAD_COMPLETED);
+
+ ret = g_task_propagate_int (task, &error);
+ g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
+ g_clear_error (&error);
+ g_assert_cmpint (ret, ==, -1);
+
+ g_object_unref (task);
+
+ g_cancellable_reset (cancellable);
+
+ /* If return-on-cancel is TRUE, it does return early */
+ thread_state = THREAD_STARTING;
+ task = g_task_new (NULL, cancellable, run_in_thread_sync_callback, NULL);
+ g_task_set_return_on_cancel (task, TRUE);
+
+ g_task_set_task_data (task, (gpointer)&thread_state, NULL);
+ g_mutex_lock (&roc_init_mutex);
+ g_mutex_lock (&roc_finish_mutex);
+ runner_thread = g_thread_new ("return-on-cancel-sync runner thread",
+ cancel_sync_runner_thread, task);
+
+ while (thread_state == THREAD_STARTING)
+ g_cond_wait (&roc_init_cond, &roc_init_mutex);
+ g_mutex_unlock (&roc_init_mutex);
+
+ g_assert (thread_state == THREAD_RUNNING);
+
+ g_cancellable_cancel (cancellable);
+ g_thread_join (runner_thread);
+ g_assert (thread_state == THREAD_RUNNING);
+
+ ret = g_task_propagate_int (task, &error);
+ g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
+ g_clear_error (&error);
+ g_assert_cmpint (ret, ==, -1);
+
+ g_object_unref (task);
+
+ while (thread_state == THREAD_RUNNING)
+ g_cond_wait (&roc_finish_cond, &roc_finish_mutex);
+ g_mutex_unlock (&roc_finish_mutex);
+
+ g_assert (thread_state == THREAD_CANCELLED);
+
+ g_cancellable_reset (cancellable);
+
+ /* If the task is already cancelled before it starts, it returns
+ * immediately, but the thread func still runs.
+ */
+ thread_state = THREAD_STARTING;
+ task = g_task_new (NULL, cancellable, run_in_thread_sync_callback, NULL);
+ g_task_set_return_on_cancel (task, TRUE);
+
+ g_cancellable_cancel (cancellable);
+
+ g_task_set_task_data (task, (gpointer)&thread_state, NULL);
+ g_mutex_lock (&roc_init_mutex);
+ g_mutex_lock (&roc_finish_mutex);
+ runner_thread = g_thread_new ("return-on-cancel-sync runner thread",
+ cancel_sync_runner_thread, task);
+
+ g_thread_join (runner_thread);
+ g_assert (thread_state == THREAD_STARTING);
+
+ ret = g_task_propagate_int (task, &error);
+ g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
+ g_clear_error (&error);
+ g_assert_cmpint (ret, ==, -1);
+
+ g_object_unref (task);
+
+ while (thread_state == THREAD_STARTING)
+ g_cond_wait (&roc_init_cond, &roc_init_mutex);
+ g_mutex_unlock (&roc_init_mutex);
+
+ g_assert (thread_state == THREAD_RUNNING);
+
+ while (thread_state == THREAD_RUNNING)
+ g_cond_wait (&roc_finish_cond, &roc_finish_mutex);
+ g_mutex_unlock (&roc_finish_mutex);
+
+ g_assert (thread_state == THREAD_CANCELLED);
+
+ g_object_unref (cancellable);
+}
+
+/* test_return_on_cancel_atomic: turning return-on-cancel on/off is
+ * non-racy
+ */
+
+GMutex roca_mutex_1, roca_mutex_2;
+GCond roca_cond_1, roca_cond_2;
+
+static void
+return_on_cancel_atomic_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ gboolean *callback_ran = user_data;
+ GError *error = NULL;
+ gssize ret;
+
+ g_assert (g_thread_self () == main_thread);
+
+ g_assert (object == NULL);
+ 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)));
+
+ ret = g_task_propagate_int (G_TASK (result), &error);
+ g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
+ g_clear_error (&error);
+ g_assert_cmpint (ret, ==, -1);
+
+ *callback_ran = TRUE;
+ 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,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ gint *state = task_data;
+
+ 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 (g_thread_self () != main_thread);
+ g_assert_cmpint (*state, ==, 0);
+
+ g_mutex_lock (&roca_mutex_1);
+ *state = 1;
+ g_cond_signal (&roca_cond_1);
+ g_mutex_unlock (&roca_mutex_1);
+
+ g_mutex_lock (&roca_mutex_2);
+ if (g_task_set_return_on_cancel (task, FALSE))
+ *state = 2;
+ else
+ *state = 3;
+ g_cond_signal (&roca_cond_2);
+ g_mutex_unlock (&roca_mutex_2);
+
+ g_mutex_lock (&roca_mutex_1);
+ if (g_task_set_return_on_cancel (task, TRUE))
+ *state = 4;
+ else
+ *state = 5;
+ g_cond_signal (&roca_cond_1);
+ g_mutex_unlock (&roca_mutex_1);
+
+ g_mutex_lock (&roca_mutex_2);
+ if (g_task_set_return_on_cancel (task, TRUE))
+ *state = 6;
+ else
+ *state = 7;
+ g_cond_signal (&roca_cond_2);
+ g_mutex_unlock (&roca_mutex_2);
+
+ g_task_return_int (task, magic);
+}
+
+static void
+test_return_on_cancel_atomic (void)
+{
+ GTask *task;
+ GCancellable *cancellable;
+ volatile gint state;
+ gboolean callback_ran;
+
+ cancellable = g_cancellable_new ();
+ g_mutex_lock (&roca_mutex_1);
+ g_mutex_lock (&roca_mutex_2);
+
+ /* If we don't cancel it, each set_return_on_cancel() call will succeed */
+ state = 0;
+ 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_task_set_task_data (task, (gpointer)&state, NULL);
+ g_task_run_in_thread (task, return_on_cancel_atomic_thread);
+ g_object_unref (task);
+
+ g_assert_cmpint (state, ==, 0);
+
+ while (state == 0)
+ g_cond_wait (&roca_cond_1, &roca_mutex_1);
+ g_assert (state == 1);
+
+ while (state == 1)
+ g_cond_wait (&roca_cond_2, &roca_mutex_2);
+ g_assert (state == 2);
+
+ while (state == 2)
+ g_cond_wait (&roca_cond_1, &roca_mutex_1);
+ g_assert (state == 4);
+
+ while (state == 4)
+ g_cond_wait (&roca_cond_2, &roca_mutex_2);
+ g_assert (state == 6);
+
+ /* callback assumes there'll be a cancelled error */
+ g_cancellable_cancel (cancellable);
+
+ g_assert (callback_ran == FALSE);
+ g_main_loop_run (loop);
+ g_assert (callback_ran == TRUE);
+
+ g_cancellable_reset (cancellable);
+
+
+ /* If we cancel while it's temporarily not return-on-cancel, the
+ * task won't complete right away, and further
+ * g_task_set_return_on_cancel() calls will return FALSE.
+ */
+ state = 0;
+ 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_task_set_task_data (task, (gpointer)&state, NULL);
+ g_task_run_in_thread (task, return_on_cancel_atomic_thread);
+ g_object_unref (task);
+
+ g_assert_cmpint (state, ==, 0);
+
+ while (state == 0)
+ g_cond_wait (&roca_cond_1, &roca_mutex_1);
+ g_assert (state == 1);
+ g_assert (g_task_get_return_on_cancel (task));
+
+ while (state == 1)
+ g_cond_wait (&roca_cond_2, &roca_mutex_2);
+ g_assert (state == 2);
+ g_assert (!g_task_get_return_on_cancel (task));
+
+ g_cancellable_cancel (cancellable);
+ g_idle_add (idle_quit_loop, NULL);
+ g_main_loop_run (loop);
+ g_assert (callback_ran == FALSE);
+
+ while (state == 2)
+ g_cond_wait (&roca_cond_1, &roca_mutex_1);
+ g_assert (state == 5);
+ g_assert (!g_task_get_return_on_cancel (task));
+
+ g_main_loop_run (loop);
+ g_assert (callback_ran == TRUE);
+
+ while (state == 5)
+ g_cond_wait (&roca_cond_2, &roca_mutex_2);
+ g_assert (state == 7);
+
+ g_object_unref (cancellable);
+ g_mutex_unlock (&roca_mutex_1);
+ g_mutex_unlock (&roca_mutex_2);
+}
+
+/* test_return_pointer: memory management of pointer returns */
+
+static void
+test_return_pointer (void)
+{
+ GObject *object, *ret;
+ GTask *task;
+ GCancellable *cancellable;
+ GError *error = NULL;
+
+ /* If we don't read back the return value, the task will
+ * run its destroy notify.
+ */
+ object = (GObject *)g_dummy_object_new ();
+ g_assert_cmpint (object->ref_count, ==, 1);
+ g_object_add_weak_pointer (object, (gpointer *)&object);
+
+ task = g_task_new (NULL, NULL, NULL, NULL);
+ g_object_add_weak_pointer (G_OBJECT (task), (gpointer *)&task);
+ g_task_return_pointer (task, object, g_object_unref);
+ g_assert_cmpint (object->ref_count, ==, 1);
+
+ g_object_unref (task);
+ g_assert (task == NULL);
+ g_assert (object == NULL);
+
+ /* Likewise, if the return value is overwritten by an error */
+ object = (GObject *)g_dummy_object_new ();
+ g_assert_cmpint (object->ref_count, ==, 1);
+ g_object_add_weak_pointer (object, (gpointer *)&object);
+
+ cancellable = g_cancellable_new ();
+ task = g_task_new (NULL, cancellable, NULL, NULL);
+ g_object_add_weak_pointer (G_OBJECT (task), (gpointer *)&task);
+ g_task_return_pointer (task, object, g_object_unref);
+ g_assert_cmpint (object->ref_count, ==, 1);
+ g_cancellable_cancel (cancellable);
+ g_assert_cmpint (object->ref_count, ==, 1);
+
+ ret = g_task_propagate_pointer (task, &error);
+ g_assert (ret == NULL);
+ g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
+ g_clear_error (&error);
+ g_assert_cmpint (object->ref_count, ==, 1);
+
+ g_object_unref (task);
+ g_object_unref (cancellable);
+ g_assert (task == NULL);
+ g_assert (object == NULL);
+
+ /* If we read back the return value, we steal its ref */
+ object = (GObject *)g_dummy_object_new ();
+ g_assert_cmpint (object->ref_count, ==, 1);
+ g_object_add_weak_pointer (object, (gpointer *)&object);
+
+ task = g_task_new (NULL, NULL, NULL, NULL);
+ g_object_add_weak_pointer (G_OBJECT (task), (gpointer *)&task);
+ g_task_return_pointer (task, object, g_object_unref);
+ g_assert_cmpint (object->ref_count, ==, 1);
+
+ ret = g_task_propagate_pointer (task, &error);
+ g_assert_no_error (error);
+ g_assert (ret == object);
+ g_assert_cmpint (object->ref_count, ==, 1);
+
+ g_object_unref (task);
+ g_assert (task == NULL);
+ g_assert_cmpint (object->ref_count, ==, 1);
+ g_object_unref (object);
+ g_assert (object == NULL);
+}
+
+/* test_object_keepalive: GTask takes a ref on its source object */
+
+static GObject *keepalive_object;
+
+static void
+keepalive_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ gssize *result_out = user_data;
+ GError *error = NULL;
+
+ g_assert (object == keepalive_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)));
+
+ *result_out = g_task_propagate_int (G_TASK (result), &error);
+ g_assert_no_error (error);
+
+ g_main_loop_quit (loop);
+}
+
+static void
+test_object_keepalive (void)
+{
+ GObject *object;
+ GTask *task;
+ gssize result;
+ int ref_count;
+
+ 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);
+
+ ref_count = object->ref_count;
+ g_assert_cmpint (ref_count, >, 1);
+
+ g_assert (g_task_get_source_object (task) == object);
+ g_assert (g_async_result_get_source_object (G_ASYNC_RESULT (task)) == object);
+ g_assert_cmpint (object->ref_count, ==, ref_count + 1);
+ g_object_unref (object);
+
+ g_object_unref (object);
+ g_assert (object != NULL);
+
+ g_task_return_int (task, magic);
+ g_main_loop_run (loop);
+
+ g_assert (object != NULL);
+ g_assert_cmpint (result, ==, magic);
+
+ g_object_unref (task);
+ g_assert (task == NULL);
+ g_assert (object == NULL);
+}
+
+/* test_legacy_error: legacy GSimpleAsyncResult handling */
+static void test_legacy_error (void);
+
+static void
+legacy_error_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ gssize *result_out = user_data;
+ GError *error = NULL;
+
+ g_assert (object == NULL);
+ g_assert (g_async_result_is_tagged (result, test_legacy_error));
+ g_assert (g_async_result_get_user_data (result) == user_data);
+
+ if (g_async_result_legacy_propagate_error (result, &error))
+ {
+ g_assert (!g_task_is_valid (result, object));
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
+ g_assert (g_simple_async_result_is_valid (result, object, test_legacy_error));
+ G_GNUC_END_IGNORE_DEPRECATIONS;
+
+ g_assert_error (error, G_IO_ERROR, G_IO_ERROR_FAILED);
+ *result_out = -2;
+ }
+ else
+ {
+ g_assert (g_task_is_valid (result, object));
+
+ *result_out = g_task_propagate_int (G_TASK (result), NULL);
+ /* Might be error, might not */
+ }
+
+ g_main_loop_quit (loop);
+}
+
+static gboolean
+legacy_error_return (gpointer user_data)
+{
+ if (G_IS_TASK (user_data))
+ {
+ GTask *task = user_data;
+
+ g_task_return_int (task, magic);
+ g_object_unref (task);
+ }
+ else
+ {
+ GSimpleAsyncResult *simple = user_data;
+
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
+ g_simple_async_result_set_error (simple,
+ G_IO_ERROR,
+ G_IO_ERROR_FAILED,
+ "Failed");
+ g_simple_async_result_complete (simple);
+ G_GNUC_END_IGNORE_DEPRECATIONS;
+ g_object_unref (simple);
+ }
+
+ return FALSE;
+}
+
+static void
+test_legacy_error (void)
+{
+ GTask *task;
+ GSimpleAsyncResult *simple;
+ gssize result;
+
+ /* GTask success */
+ task = g_task_new (NULL, NULL, legacy_error_callback, &result);
+ g_task_set_source_tag (task, test_legacy_error);
+ g_object_add_weak_pointer (G_OBJECT (task), (gpointer *)&task);
+
+ g_idle_add (legacy_error_return, task);
+ g_main_loop_run (loop);
+
+ g_assert_cmpint (result, ==, magic);
+ g_assert (task == NULL);
+
+ /* GTask error */
+ task = g_task_new (NULL, NULL, legacy_error_callback, &result);
+ g_task_set_source_tag (task, test_legacy_error);
+ g_object_add_weak_pointer (G_OBJECT (task), (gpointer *)&task);
+
+ g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Failed");
+ g_object_unref (task);
+ g_main_loop_run (loop);
+
+ g_assert_cmpint (result, ==, -1);
+ g_assert (task == NULL);
+
+ /* GSimpleAsyncResult error */
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
+ simple = g_simple_async_result_new (NULL, legacy_error_callback, &result,
+ test_legacy_error);
+ G_GNUC_END_IGNORE_DEPRECATIONS;
+ g_object_add_weak_pointer (G_OBJECT (simple), (gpointer *)&simple);
+
+ g_idle_add (legacy_error_return, simple);
+ g_main_loop_run (loop);
+
+ g_assert_cmpint (result, ==, -2);
+ g_assert (simple == NULL);
+}
+
+
+int
+main (int argc, char **argv)
+{
+ int ret;
+
+ g_type_init ();
+ g_test_init (&argc, &argv, NULL);
+
+ loop = g_main_loop_new (NULL, FALSE);
+ main_thread = g_thread_self ();
+ magic = g_get_monotonic_time ();
+
+ g_test_add_func ("/gtask/basic", test_basic);
+ g_test_add_func ("/gtask/error", test_error);
+ g_test_add_func ("/gtask/return-from-same-iteration", test_return_from_same_iteration);
+ g_test_add_func ("/gtask/return-from-toplevel", test_return_from_toplevel);
+ g_test_add_func ("/gtask/return-from-anon-thread", test_return_from_anon_thread);
+ g_test_add_func ("/gtask/return-from-wrong-thread", test_return_from_wrong_thread);
+ g_test_add_func ("/gtask/no-callback", test_no_callback);
+ g_test_add_func ("/gtask/report-error", test_report_error);
+ g_test_add_func ("/gtask/priority", test_priority);
+ g_test_add_func ("/gtask/check-cancellable", test_check_cancellable);
+ g_test_add_func ("/gtask/return-if-cancelled", test_return_if_cancelled);
+ g_test_add_func ("/gtask/run-in-thread", test_run_in_thread);
+ g_test_add_func ("/gtask/run-in-thread-sync", test_run_in_thread_sync);
+ g_test_add_func ("/gtask/run-in-thread-priority", test_run_in_thread_priority);
+ g_test_add_func ("/gtask/return-on-cancel", test_return_on_cancel);
+ g_test_add_func ("/gtask/return-on-cancel-sync", test_return_on_cancel_sync);
+ g_test_add_func ("/gtask/return-on-cancel-atomic", test_return_on_cancel_atomic);
+ g_test_add_func ("/gtask/return-pointer", test_return_pointer);
+ g_test_add_func ("/gtask/object-keepalive", test_object_keepalive);
+ g_test_add_func ("/gtask/legacy-error", test_legacy_error);
+
+ ret = g_test_run();
+
+ g_main_loop_unref (loop);
+
+ return ret;
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]