From: Dan Winship Date: Wed, 5 Oct 2011 14:05:50 +0000 (-0400) Subject: GTask: new GAsyncResult implementation / threaded task manager X-Git-Tag: 2.35.1~28 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=4aeefa70a10b89b53a3bd1ed305d451f3cc6d2ad;p=platform%2Fupstream%2Fglib.git 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 --- 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 @@ + Data conversion diff --git a/docs/reference/gio/gio-sections.txt b/docs/reference/gio/gio-sections.txt index c9dd506..a3a0fb0 100644 --- a/docs/reference/gio/gio-sections.txt +++ b/docs/reference/gio/gio-sections.txt @@ -3921,3 +3921,56 @@ g_test_dbus_unset g_test_dbus_get_type g_test_dbus_flags_get_type + +
+gtask +GTask +GTask +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 + +g_task_report_error +g_task_report_new_error + +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 + +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 + +g_task_propagate_boolean +g_task_propagate_int +g_task_propagate_pointer +g_task_had_error + +g_task_run_in_thread +g_task_run_in_thread_sync +GTaskThreadFunc +g_task_attach_source + +g_task_is_valid + +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 +
diff --git a/docs/reference/gio/gio.types b/docs/reference/gio/gio.types index 9eec8a9..5cd996c 100644 --- a/docs/reference/gio/gio.types +++ b/docs/reference/gio/gio.types @@ -135,3 +135,4 @@ g_menu_get_type g_menu_item_get_type g_test_dbus_get_type g_test_dbus_flags_get_type +g_task_get_type 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 #include #include +#include #include #include #include diff --git a/gio/gio.symbols b/gio/gio.symbols index ab207fa..b2a95b2 100644 --- a/gio/gio.symbols +++ b/gio/gio.symbols @@ -1767,3 +1767,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..48ddf6a --- /dev/null +++ b/gio/gtask.c @@ -0,0 +1,1805 @@ +/* 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 + * + * + * A #GTask represents and manages a cancellable "task". + * + * + * Asynchronous operations + * + * 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. + * + * GTask as a GAsyncResult + * + * 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 (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_object_unref (cake); + * /* g_task_return_error() takes ownership of error */ + * g_task_return_error (task, error); + * g_object_unref (task); + * return; + * } + * + * g_task_return_pointer (result, 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); + * } + * + * + * + * + * Chained asynchronous operations + * + * #GTask also tries to simplify asynchronous operations that + * internally chain together several smaller asynchronous + * operations. g_task_get_cancellable(), g_task_get_context(), and + * g_task_get_priority() allow you to get back the task's + * #GCancellable, #GMainContext, and I/O priority when starting a new + * subtask, so you don't have to keep track of them yourself. + * g_task_attach_source() simplifies the case of waiting for a + * source to fire (automatically using the correct #GMainContext + * and priority). + * + * Chained asynchronous operations + * + * typedef struct { + * Cake *cake; + * CakeFrostingType frosting; + * char *message; + * } BakingData; + * + * static void + * decoration_data_free (BakingData *bd) + * { + * if (bd->cake) + * g_object_unref (bd->cake); + * g_free (bd->message); + * g_slice_free (BakingData, bd); + * } + * + * static void + * decorated_cb (Cake *cake, + * GAsyncResult *result, + * gpointer user_data) + * { + * GTask *task = user_data; + * GError *error = NULL; + * + * if (!cake_decorate_finish (cake, result, &error)) + * { + * g_object_unref (cake); + * g_task_return_error (task, error); + * g_object_unref (task); + * return; + * } + * + * /* baking_data_free() will drop its ref on the cake, so + * * we have to take another here to give to the caller. + * */ + * g_task_return_pointer (result, g_object_ref (cake), g_object_unref); + * g_object_unref (task); + * } + * + * static void + * decorator_ready (gpointer user_data) + * { + * GTask *task = user_data; + * BakingData *bd = g_task_get_task_data (task); + * + * cake_decorate_async (bd->cake, bd->frosting, bd->message, + * g_task_get_cancellable (task), + * decorated_cb, task); + * } + * + * static void + * baked_cb (Cake *cake, + * gpointer user_data) + * { + * GTask *task = user_data; + * BakingData *bd = g_task_get_task_data (task); + * GError *error = NULL; + * + * if (cake == NULL) + * { + * g_task_return_new_error (task, BAKER_ERROR, BAKER_ERROR_NO_FLOUR, + * "Go to the supermarket"); + * g_object_unref (task); + * return; + * } + * + * bd->cake = cake; + * + * /* Bail out now if the user has already cancelled */ + * if (g_task_return_error_if_cancelled (g_task_get_cancellable (task))) + * { + * g_object_unref (task); + * return; + * } + * + * if (cake_decorator_available (cake)) + * decorator_ready (task); + * else + * { + * GSource *source; + * + * source = cake_decorator_wait_source_new (cake); + * /* Attach @source to @task's GMainContext and have it call + * * decorator_ready() when it is ready. + * */ + * g_task_attach_source (task, source, + * G_CALLBACK (decorator_ready)); + * g_source_unref (source); + * } + * } + * + * void + * baker_bake_cake_async (Baker *self, + * guint radius, + * CakeFlavor flavor, + * CakeFrostingType frosting, + * const char *message, + * gint priority, + * GCancellable *cancellable, + * GAsyncReadyCallback callback, + * gpointer user_data) + * { + * GTask *task; + * BakingData *bd; + * + * task = g_task_new (self, cancellable, callback, user_data); + * g_task_set_priority (task, priority); + * + * bd = g_slice_new0 (BakingData); + * bd->frosting = frosting; + * bd->message = g_strdup (message); + * g_task_set_task_data (task, bd, (GDestroyNotify) baking_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); + * } + * + * + * + * + * Asynchronous operations from synchronous ones + * + * You can 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. + * + * g_task_run_in_thread() + * + * 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); + * } + * + * + * + * + * Adding cancellability to uncancellable tasks + * + * 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. + * + * g_task_set_return_on_cancel() + * + * 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, + * * so we don't have to worry that this code will run at + * * the same time as that code does. But if there were + * * other functions that might look at the cake cache, + * * then we'd probably need a GMutex here as well. + * */ + * 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; + * } + * + * + * + * + * Porting from <literal>GSimpleAsyncResult</literal> + * + * #GTask's API attempts to be simpler than #GSimpleAsyncResult's + * in several ways: + * + * + * + * 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. + * + * + * In addition to the task data, #GTask also keeps track of the + * priority, #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. + * + * + * 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 + * task_func, even if the task's #GCancellable + * is already cancelled before the task gets a chance to run; + * you can start your task_func with a + * g_task_return_error_if_cancelled() check if you need the + * old behavior. + * + * + * 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.) + * + * + * 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. + * + * + * With #GSimpleAsyncResult, it was common to call + * g_simple_async_result_propagate_error() from the + * _finish() 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 g_task_propagate_ 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 _async() wrapper + * function (for "short-circuit" results, such as when passing + * 0 to g_input_stream_read_async()). + * + * + * + */ + +/** + * 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 thread-default main + * context. + * + * 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.36 + */ +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.36 + */ +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.36 + */ +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.36 + */ +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 priority + * 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.36 + */ +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.36 + */ +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.36 + */ +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.36 + */ +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.36 + */ +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 task_data. + * + * Returns: (transfer none): @task's task_data. + * + * Since: 2.36 + */ +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.36 + */ +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 thread-default main + * context 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.36 + */ +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.36 + */ +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.36 + */ +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.36 + */ +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.36 + */ +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, and @cancellable gets + * cancelled, then the #GTask will be completed immediately (as though + * g_task_return_error_if_cancelled() had been called), 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, not when it calls + * a g_task_return_ function. + * + * Since: 2.36 + */ + +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.36 + */ +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 + * callback, but note that even if the task does + * have a callback, it will not be invoked when @task_func returns. + * + * Since: 2.36 + */ +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 priority, and sets @source's callback + * to @callback, with @task as the callback's + * user_data. + * + * This takes a reference on @task until @source is destroyed. + * + * Since: 2.36 + */ +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-none): 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.36 + */ +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.36 + */ +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.36 + */ +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.36 + */ +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.36 + */ +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.36 + */ +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.36 + */ +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.36 + */ +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.36 + */ +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.36 + */ +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.36 + */ +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..2cc5f65 --- /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 can be included directly." +#endif + +#ifndef __G_TASK_H__ +#define __G_TASK_H__ + +#include + +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_36 +GType g_task_get_type (void) G_GNUC_CONST; + +GLIB_AVAILABLE_IN_2_36 +GTask *g_task_new (gpointer source_object, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer callback_data); + +GLIB_AVAILABLE_IN_2_36 +void g_task_report_error (gpointer source_object, + GAsyncReadyCallback callback, + gpointer callback_data, + gpointer source_tag, + GError *error); +GLIB_AVAILABLE_IN_2_36 +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_36 +void g_task_set_task_data (GTask *task, + gpointer task_data, + GDestroyNotify task_data_destroy); +GLIB_AVAILABLE_IN_2_36 +void g_task_set_priority (GTask *task, + gint priority); +GLIB_AVAILABLE_IN_2_36 +void g_task_set_check_cancellable (GTask *task, + gboolean check_cancellable); +GLIB_AVAILABLE_IN_2_36 +void g_task_set_source_tag (GTask *task, + gpointer source_tag); + +GLIB_AVAILABLE_IN_2_36 +gpointer g_task_get_source_object (GTask *task); +GLIB_AVAILABLE_IN_2_36 +gpointer g_task_get_task_data (GTask *task); +GLIB_AVAILABLE_IN_2_36 +gint g_task_get_priority (GTask *task); +GLIB_AVAILABLE_IN_2_36 +GMainContext *g_task_get_context (GTask *task); +GLIB_AVAILABLE_IN_2_36 +GCancellable *g_task_get_cancellable (GTask *task); +GLIB_AVAILABLE_IN_2_36 +gboolean g_task_get_check_cancellable (GTask *task); +GLIB_AVAILABLE_IN_2_36 +gpointer g_task_get_source_tag (GTask *task); + +GLIB_AVAILABLE_IN_2_36 +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_36 +void g_task_run_in_thread (GTask *task, + GTaskThreadFunc task_func); +GLIB_AVAILABLE_IN_2_36 +void g_task_run_in_thread_sync (GTask *task, + GTaskThreadFunc task_func); +GLIB_AVAILABLE_IN_2_36 +gboolean g_task_set_return_on_cancel (GTask *task, + gboolean return_on_cancel); +GLIB_AVAILABLE_IN_2_36 +gboolean g_task_get_return_on_cancel (GTask *task); + +GLIB_AVAILABLE_IN_2_36 +void g_task_attach_source (GTask *task, + GSource *source, + GSourceFunc callback); + + +GLIB_AVAILABLE_IN_2_36 +void g_task_return_pointer (GTask *task, + gpointer result, + GDestroyNotify result_destroy); +GLIB_AVAILABLE_IN_2_36 +void g_task_return_boolean (GTask *task, + gboolean result); +GLIB_AVAILABLE_IN_2_36 +void g_task_return_int (GTask *task, + gssize result); + +GLIB_AVAILABLE_IN_2_36 +void g_task_return_error (GTask *task, + GError *error); +GLIB_AVAILABLE_IN_2_36 +void g_task_return_new_error (GTask *task, + GQuark domain, + gint code, + const char *format, + ...) G_GNUC_PRINTF (4, 5); + +GLIB_AVAILABLE_IN_2_36 +gboolean g_task_return_error_if_cancelled (GTask *task); + +GLIB_AVAILABLE_IN_2_36 +gpointer g_task_propagate_pointer (GTask *task, + GError **error); +GLIB_AVAILABLE_IN_2_36 +gboolean g_task_propagate_boolean (GTask *task, + GError **error); +GLIB_AVAILABLE_IN_2_36 +gssize g_task_propagate_int (GTask *task, + GError **error); +GLIB_AVAILABLE_IN_2_36 +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 ccb224a..1016851 100644 --- a/gio/tests/.gitignore +++ b/gio/tests/.gitignore @@ -96,6 +96,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 3866b13..5a1fdc5 100644 --- a/gio/tests/Makefile.am +++ b/gio/tests/Makefile.am @@ -62,6 +62,7 @@ TEST_PROGS += \ proxy-test \ inet-address \ permission \ + task \ $(NULL) if HAVE_DBUS_DAEMON @@ -469,6 +470,9 @@ inet_address_LDADD = $(progs_ldadd) permission_SOURCES = permission.c permission_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 + +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; +}