2185 lines
58 KiB
C
2185 lines
58 KiB
C
|
/* ide-task.c
|
||
|
*
|
||
|
* Copyright 2018 Christian Hergert
|
||
|
*
|
||
|
* 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.1 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||
|
*/
|
||
|
|
||
|
#define G_LOG_DOMAIN "ide-task"
|
||
|
|
||
|
#include "config.h"
|
||
|
|
||
|
#include <libide-core.h>
|
||
|
|
||
|
#include "ide-task.h"
|
||
|
#include "ide-thread-pool.h"
|
||
|
#include "ide-thread-private.h"
|
||
|
|
||
|
/* From GDK_PRIORITY_REDRAW */
|
||
|
#define PRIORITY_REDRAW (G_PRIORITY_HIGH_IDLE + 20)
|
||
|
|
||
|
#if 0
|
||
|
# define ENABLE_TIME_CHART
|
||
|
#endif
|
||
|
|
||
|
/**
|
||
|
* SECTION:ide-task
|
||
|
* @title: IdeTask
|
||
|
* @short_description: asynchronous task management
|
||
|
*
|
||
|
* #IdeTask is meant to be an improved form of #GTask. There are a few
|
||
|
* deficiencies in #GTask that have made it unsuitable for certain use cases.
|
||
|
*
|
||
|
* #GTask does not provide a way to guarantee that the source object,
|
||
|
* task data, and unused results are freed with in a given #GMainContext.
|
||
|
* #IdeTask addresses this by having a more flexible result and object
|
||
|
* ownership control.
|
||
|
*
|
||
|
* Furthermore, #IdeTask allows consumers to force disposal from a given
|
||
|
* thread so that the data is released there.
|
||
|
*
|
||
|
* #IdeTask also supports chaining tasks together which makes it simpler
|
||
|
* to avoid doing duplicate work by instead simply chaining the tasks.
|
||
|
*
|
||
|
* There are some costs to this design. It uses the main context a bit
|
||
|
* more than #GTask may use it.
|
||
|
*
|
||
|
* #IdeTask allows setting a task kind which determines which thread pool
|
||
|
* the task will be executed (and throttled) on.
|
||
|
*
|
||
|
* Because #IdeTask needs more control over result life-cycles (for chaining
|
||
|
* results), additional return methods have been provided. Consumers should
|
||
|
* use ide_task_return_boxed() when working with boxed types as it allows us
|
||
|
* to copy the result to another task. Additionally, ide_task_return_object()
|
||
|
* provides a simplified API over ide_task_return_pointer() which also allows
|
||
|
* copying the result to chained tasks.
|
||
|
*
|
||
|
* Since: 3.32
|
||
|
*/
|
||
|
|
||
|
typedef struct
|
||
|
{
|
||
|
/*
|
||
|
* The pointer we were provided.
|
||
|
*/
|
||
|
gpointer data;
|
||
|
|
||
|
/*
|
||
|
* The destroy notify for @data. We should only call this from the
|
||
|
* main context associated with the task.
|
||
|
*/
|
||
|
GDestroyNotify data_destroy;
|
||
|
} IdeTaskData;
|
||
|
|
||
|
typedef enum
|
||
|
{
|
||
|
IDE_TASK_RESULT_NONE,
|
||
|
IDE_TASK_RESULT_CANCELLED,
|
||
|
IDE_TASK_RESULT_BOOLEAN,
|
||
|
IDE_TASK_RESULT_INT,
|
||
|
IDE_TASK_RESULT_ERROR,
|
||
|
IDE_TASK_RESULT_OBJECT,
|
||
|
IDE_TASK_RESULT_BOXED,
|
||
|
IDE_TASK_RESULT_POINTER,
|
||
|
} IdeTaskResultType;
|
||
|
|
||
|
typedef struct
|
||
|
{
|
||
|
/*
|
||
|
* The type of result stored in our union @u.
|
||
|
*/
|
||
|
IdeTaskResultType type;
|
||
|
|
||
|
/*
|
||
|
* To ensure that we can pass ownership back to the main context
|
||
|
* from our worker thread, we need to be able to stash the reference
|
||
|
* here in our result. It is also convenient as we need access to it
|
||
|
* from the main context callback anyway.
|
||
|
*/
|
||
|
IdeTask *task;
|
||
|
|
||
|
/*
|
||
|
* Additionally, we need to allow passing our main context reference
|
||
|
* back so that it cannot be finalized in our thread.
|
||
|
*/
|
||
|
GMainContext *main_context;
|
||
|
|
||
|
/*
|
||
|
* Priority for our GSource attached to @main_context.
|
||
|
*/
|
||
|
gint complete_priority;
|
||
|
|
||
|
/*
|
||
|
* The actual result information, broken down by result @type.
|
||
|
*/
|
||
|
union {
|
||
|
gboolean v_bool;
|
||
|
gssize v_int;
|
||
|
GError *v_error;
|
||
|
GObject *v_object;
|
||
|
struct {
|
||
|
GType type;
|
||
|
gpointer pointer;
|
||
|
} v_boxed;
|
||
|
struct {
|
||
|
gpointer pointer;
|
||
|
GDestroyNotify destroy;
|
||
|
} v_pointer;
|
||
|
} u;
|
||
|
} IdeTaskResult;
|
||
|
|
||
|
typedef struct
|
||
|
{
|
||
|
IdeTask *task;
|
||
|
GMainContext *main_context;
|
||
|
gint priority;
|
||
|
} IdeTaskCancel;
|
||
|
|
||
|
typedef struct
|
||
|
{
|
||
|
/*
|
||
|
* @global_link is used to store a pointer to the task in the global
|
||
|
* queue during the lifetime of the task. This is a debugging feature
|
||
|
* so that we can dump the list of active tasks from the debugger.
|
||
|
*/
|
||
|
GList global_link;
|
||
|
|
||
|
/*
|
||
|
* Controls access to our private data. We only access structure
|
||
|
* data while holding this mutex to ensure that we have consistency
|
||
|
* between threads which could be accessing internals.
|
||
|
*/
|
||
|
GMutex mutex;
|
||
|
|
||
|
/*
|
||
|
* The source object for the GAsyncResult interface. If we have set
|
||
|
* release_on_propagate, this will be released when the task propagate
|
||
|
* function is called.
|
||
|
*/
|
||
|
gpointer source_object;
|
||
|
|
||
|
/*
|
||
|
* The cancellable that we're monitoring for task cancellation.
|
||
|
*/
|
||
|
GCancellable *cancellable;
|
||
|
|
||
|
/*
|
||
|
* If ide_task_set_return_on_cancel() has been set, then we might be
|
||
|
* listening for changes. Handling this will queue a completion
|
||
|
*/
|
||
|
gulong cancel_handler;
|
||
|
|
||
|
/*
|
||
|
* The callback to execute upon completion of the operation. It will
|
||
|
* be called from @main_contect after the operation completes.
|
||
|
*/
|
||
|
GAsyncReadyCallback callback;
|
||
|
gpointer user_data;
|
||
|
|
||
|
/*
|
||
|
* The name for the task. This string is interned so you should not
|
||
|
* use dynamic names. They are meant to simplify the process of
|
||
|
* debugging what task failed.
|
||
|
*/
|
||
|
const gchar *name;
|
||
|
|
||
|
/*
|
||
|
* The GMainContext that was the thread default when the task was
|
||
|
* created. Most operations are proxied back to this context so that
|
||
|
* the consumer does not need to worry about thread safety.
|
||
|
*/
|
||
|
GMainContext *main_context;
|
||
|
|
||
|
/*
|
||
|
* The task data that has been set for the task. Task data is released
|
||
|
* from a callback in the #GMainContext if changed outside the main
|
||
|
* context.
|
||
|
*/
|
||
|
IdeTaskData *task_data;
|
||
|
|
||
|
/*
|
||
|
* The result for the task. If release_on_propagate as set to %FALSE,
|
||
|
* then this may be kept around so that ide_task_chain() can be used to
|
||
|
* duplicate the result to another task. This is convenient when multiple
|
||
|
* async funcs race to do some work, allowing just a single winner with all
|
||
|
* the callers getting the same result.
|
||
|
*/
|
||
|
IdeTaskResult *result;
|
||
|
|
||
|
/*
|
||
|
* ide_task_chain() allows us to propagate the result of this task to
|
||
|
* another task (for a limited number of result types). This is the
|
||
|
* list of those tasks.
|
||
|
*/
|
||
|
GPtrArray *chained;
|
||
|
|
||
|
/*
|
||
|
* If ide_task_run_in_thread() is called, this will be set to the func
|
||
|
* that should be called from within the thread.
|
||
|
*/
|
||
|
IdeTaskThreadFunc thread_func;
|
||
|
|
||
|
/*
|
||
|
* If we're running in a thread, we'll stash the value here until we
|
||
|
* can complete things cleanly and pass ownership back as one operation.
|
||
|
*/
|
||
|
IdeTaskResult *thread_result;
|
||
|
|
||
|
/*
|
||
|
* The source tag for the task, which can be used to determine what
|
||
|
* the task is from a debugger as well as to verify correctness
|
||
|
* in async finish functions.
|
||
|
*/
|
||
|
gpointer source_tag;
|
||
|
|
||
|
#ifdef ENABLE_TIME_CHART
|
||
|
/* The time the task was created */
|
||
|
gint64 begin_time;
|
||
|
#endif
|
||
|
|
||
|
/*
|
||
|
* Our priority for scheduling tasks in the particular workqueue.
|
||
|
*/
|
||
|
gint priority;
|
||
|
|
||
|
/*
|
||
|
* The priority for completing the result back on the main context. This
|
||
|
* defaults to a value lower than gtk redraw priority to ensure that gtk
|
||
|
* has higher priority than task completion.
|
||
|
*/
|
||
|
gint complete_priority;
|
||
|
|
||
|
/*
|
||
|
* While we're waiting for our return callback, this is set to our
|
||
|
* source id. We use that to know we need to block on the main loop
|
||
|
* in case the user calls ide_task_propagate_*() synchronously without
|
||
|
* round-triping to the main loop.
|
||
|
*/
|
||
|
guint return_source;
|
||
|
|
||
|
/*
|
||
|
* Our kind of task, which is used to determine what thread pool we
|
||
|
* can use when running threaded work. This can be used to help choke
|
||
|
* lots of work down to a relatively small number of threads.
|
||
|
*/
|
||
|
IdeTaskKind kind : 8;
|
||
|
|
||
|
/*
|
||
|
* If the task has been completed, which is to say that the callback
|
||
|
* dispatch has occurred in @main_context.
|
||
|
*/
|
||
|
guint completed : 1;
|
||
|
|
||
|
/*
|
||
|
* If we should check @cancellable before returning the result. If set
|
||
|
* to true, and the cancellable was cancelled, an error will be returned
|
||
|
* even if the task completed successfully.
|
||
|
*/
|
||
|
guint check_cancellable : 1;
|
||
|
|
||
|
/*
|
||
|
* If we should synthesize completion from a GCancellable::cancelled
|
||
|
* event instead of waiting for the task to complete normally.
|
||
|
*/
|
||
|
guint return_on_cancel : 1;
|
||
|
|
||
|
/*
|
||
|
* If we should release the source object and task data after we've
|
||
|
* dispatched the callback (or the callback was NULL). This allows us
|
||
|
* to ensure that various dependent data are released in the main
|
||
|
* context. This is the default and helps ensure thread-safety.
|
||
|
*/
|
||
|
guint release_on_propagate : 1;
|
||
|
|
||
|
/*
|
||
|
* Protect against multiple return calls, and given the developer a good
|
||
|
* warning so they catch this early.
|
||
|
*/
|
||
|
guint return_called : 1;
|
||
|
|
||
|
/*
|
||
|
* If we got a result that was a cancellation, then we mark it here so
|
||
|
* that we can deal with it cleanly later.
|
||
|
*/
|
||
|
guint got_cancel : 1;
|
||
|
|
||
|
/*
|
||
|
* If we have dispatched to a thread already.
|
||
|
*/
|
||
|
guint thread_called : 1;
|
||
|
|
||
|
} IdeTaskPrivate;
|
||
|
|
||
|
static void async_result_init_iface (GAsyncResultIface *iface);
|
||
|
static void ide_task_data_free (IdeTaskData *task_data);
|
||
|
static void ide_task_result_free (IdeTaskResult *result);
|
||
|
static gboolean ide_task_return_cb (gpointer user_data);
|
||
|
static void ide_task_release (IdeTask *self,
|
||
|
gboolean force);
|
||
|
|
||
|
G_DEFINE_AUTOPTR_CLEANUP_FUNC (IdeTaskData, ide_task_data_free);
|
||
|
G_DEFINE_AUTOPTR_CLEANUP_FUNC (IdeTaskResult, ide_task_result_free);
|
||
|
|
||
|
G_DEFINE_TYPE_WITH_CODE (IdeTask, ide_task, G_TYPE_OBJECT,
|
||
|
G_ADD_PRIVATE (IdeTask)
|
||
|
G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_RESULT, async_result_init_iface))
|
||
|
|
||
|
enum {
|
||
|
PROP_0,
|
||
|
PROP_COMPLETED,
|
||
|
N_PROPS
|
||
|
};
|
||
|
|
||
|
static GParamSpec *properties [N_PROPS];
|
||
|
static GQueue global_task_list = G_QUEUE_INIT;
|
||
|
|
||
|
G_LOCK_DEFINE (global_task_list);
|
||
|
|
||
|
static void
|
||
|
ide_task_cancel_free (IdeTaskCancel *cancel)
|
||
|
{
|
||
|
g_clear_pointer (&cancel->main_context, g_main_context_unref);
|
||
|
g_clear_object (&cancel->task);
|
||
|
g_slice_free (IdeTaskCancel, cancel);
|
||
|
}
|
||
|
|
||
|
static const gchar *
|
||
|
result_type_name (IdeTaskResultType type)
|
||
|
{
|
||
|
switch (type)
|
||
|
{
|
||
|
case IDE_TASK_RESULT_NONE:
|
||
|
return "none";
|
||
|
|
||
|
case IDE_TASK_RESULT_CANCELLED:
|
||
|
return "cancelled";
|
||
|
|
||
|
case IDE_TASK_RESULT_INT:
|
||
|
return "int";
|
||
|
|
||
|
case IDE_TASK_RESULT_POINTER:
|
||
|
return "pointer";
|
||
|
|
||
|
case IDE_TASK_RESULT_OBJECT:
|
||
|
return "object";
|
||
|
|
||
|
case IDE_TASK_RESULT_BOXED:
|
||
|
return "boxed";
|
||
|
|
||
|
case IDE_TASK_RESULT_BOOLEAN:
|
||
|
return "boolean";
|
||
|
|
||
|
case IDE_TASK_RESULT_ERROR:
|
||
|
return "error";
|
||
|
|
||
|
default:
|
||
|
return NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_task_data_free (IdeTaskData *task_data)
|
||
|
{
|
||
|
if (task_data->data_destroy != NULL)
|
||
|
task_data->data_destroy (task_data->data);
|
||
|
g_slice_free (IdeTaskData, task_data);
|
||
|
}
|
||
|
|
||
|
static IdeTaskResult *
|
||
|
ide_task_result_copy (const IdeTaskResult *src)
|
||
|
{
|
||
|
IdeTaskResult *dst;
|
||
|
|
||
|
dst = g_slice_new0 (IdeTaskResult);
|
||
|
dst->type = src->type;
|
||
|
|
||
|
switch (src->type)
|
||
|
{
|
||
|
case IDE_TASK_RESULT_INT:
|
||
|
dst->u.v_int = src->u.v_int;
|
||
|
break;
|
||
|
|
||
|
case IDE_TASK_RESULT_BOOLEAN:
|
||
|
dst->u.v_bool = src->u.v_bool;
|
||
|
break;
|
||
|
|
||
|
case IDE_TASK_RESULT_ERROR:
|
||
|
dst->u.v_error = g_error_copy (src->u.v_error);
|
||
|
break;
|
||
|
|
||
|
case IDE_TASK_RESULT_OBJECT:
|
||
|
dst->u.v_object = src->u.v_object ? g_object_ref (src->u.v_object) : NULL;
|
||
|
break;
|
||
|
|
||
|
case IDE_TASK_RESULT_BOXED:
|
||
|
dst->u.v_boxed.type = src->u.v_boxed.type;
|
||
|
dst->u.v_boxed.pointer = g_boxed_copy (src->u.v_boxed.type, src->u.v_boxed.pointer);
|
||
|
break;
|
||
|
|
||
|
case IDE_TASK_RESULT_POINTER:
|
||
|
g_critical ("Cannot proxy raw pointers for task results");
|
||
|
break;
|
||
|
|
||
|
case IDE_TASK_RESULT_CANCELLED:
|
||
|
case IDE_TASK_RESULT_NONE:
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return g_steal_pointer (&dst);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_task_result_free (IdeTaskResult *result)
|
||
|
{
|
||
|
if (result == NULL)
|
||
|
return;
|
||
|
|
||
|
switch (result->type)
|
||
|
{
|
||
|
case IDE_TASK_RESULT_POINTER:
|
||
|
if (result->u.v_pointer.destroy)
|
||
|
result->u.v_pointer.destroy (result->u.v_pointer.pointer);
|
||
|
break;
|
||
|
|
||
|
case IDE_TASK_RESULT_ERROR:
|
||
|
g_error_free (result->u.v_error);
|
||
|
break;
|
||
|
|
||
|
case IDE_TASK_RESULT_BOXED:
|
||
|
if (result->u.v_boxed.pointer)
|
||
|
g_boxed_free (result->u.v_boxed.type, result->u.v_boxed.pointer);
|
||
|
break;
|
||
|
|
||
|
case IDE_TASK_RESULT_OBJECT:
|
||
|
g_clear_object (&result->u.v_object);
|
||
|
break;
|
||
|
|
||
|
case IDE_TASK_RESULT_BOOLEAN:
|
||
|
case IDE_TASK_RESULT_INT:
|
||
|
case IDE_TASK_RESULT_NONE:
|
||
|
case IDE_TASK_RESULT_CANCELLED:
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
g_clear_object (&result->task);
|
||
|
g_clear_pointer (&result->main_context, g_main_context_unref);
|
||
|
g_slice_free (IdeTaskResult, result);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* ide_task_complete:
|
||
|
* @result: (transfer full): the result to complete
|
||
|
*
|
||
|
* queues the completion for the task. make sure that you've
|
||
|
* set the result->task, main_context, and priority first.
|
||
|
*
|
||
|
* This is designed to allow stealing the last reference from
|
||
|
* a worker thread and pass it back to the main context.
|
||
|
*
|
||
|
* Returns: a gsource identifier
|
||
|
*/
|
||
|
static guint
|
||
|
ide_task_complete (IdeTaskResult *result)
|
||
|
{
|
||
|
GSource *source;
|
||
|
guint ret;
|
||
|
|
||
|
g_assert (result != NULL);
|
||
|
g_assert (IDE_IS_TASK (result->task));
|
||
|
g_assert (result->main_context);
|
||
|
|
||
|
source = g_idle_source_new ();
|
||
|
g_source_set_name (source, "[ide-task] complete result");
|
||
|
g_source_set_ready_time (source, -1);
|
||
|
g_source_set_callback (source, ide_task_return_cb, result, NULL);
|
||
|
g_source_set_priority (source, result->complete_priority);
|
||
|
ret = g_source_attach (source, result->main_context);
|
||
|
g_source_unref (source);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_task_thread_func (gpointer data)
|
||
|
{
|
||
|
g_autoptr(GObject) source_object = NULL;
|
||
|
g_autoptr(GCancellable) cancellable = NULL;
|
||
|
g_autoptr(IdeTask) task = data;
|
||
|
IdeTaskPrivate *priv = ide_task_get_instance_private (task);
|
||
|
gpointer task_data = NULL;
|
||
|
IdeTaskThreadFunc thread_func;
|
||
|
|
||
|
g_assert (IDE_IS_TASK (task));
|
||
|
|
||
|
g_mutex_lock (&priv->mutex);
|
||
|
source_object = priv->source_object ? g_object_ref (priv->source_object) : NULL;
|
||
|
cancellable = priv->cancellable ? g_object_ref (priv->cancellable) : NULL;
|
||
|
if (priv->task_data)
|
||
|
task_data = priv->task_data->data;
|
||
|
thread_func = priv->thread_func;
|
||
|
priv->thread_func = NULL;
|
||
|
g_mutex_unlock (&priv->mutex);
|
||
|
|
||
|
g_assert (thread_func != NULL);
|
||
|
|
||
|
thread_func (task, source_object, task_data, cancellable);
|
||
|
|
||
|
g_clear_object (&source_object);
|
||
|
g_clear_object (&cancellable);
|
||
|
|
||
|
g_mutex_lock (&priv->mutex);
|
||
|
|
||
|
/*
|
||
|
* We've delayed our ide_task_return() until we reach here, so now
|
||
|
* we can steal our object instance and complete the task along with
|
||
|
* ensuring the object wont be finalized from this thread.
|
||
|
*/
|
||
|
if (priv->thread_result)
|
||
|
{
|
||
|
IdeTaskResult *result = g_steal_pointer (&priv->thread_result);
|
||
|
|
||
|
g_assert (result->task == task);
|
||
|
g_clear_object (&result->task);
|
||
|
result->task = g_steal_pointer (&task);
|
||
|
|
||
|
priv->return_source = ide_task_complete (g_steal_pointer (&result));
|
||
|
|
||
|
g_assert (source_object == NULL);
|
||
|
g_assert (cancellable == NULL);
|
||
|
g_assert (task == NULL);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
/* The task did not return a value while in the thread func! GTask
|
||
|
* doesn't support this, but its useful to us in a number of ways, so
|
||
|
* we'll begrudgingly support it but the best we can do is drop our
|
||
|
* reference from the thread.
|
||
|
*/
|
||
|
}
|
||
|
|
||
|
g_mutex_unlock (&priv->mutex);
|
||
|
|
||
|
g_assert (source_object == NULL);
|
||
|
g_assert (cancellable == NULL);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_task_dispose (GObject *object)
|
||
|
{
|
||
|
IdeTask *self = (IdeTask *)object;
|
||
|
IdeTaskPrivate *priv = ide_task_get_instance_private (self);
|
||
|
|
||
|
g_assert (IDE_IS_TASK (self));
|
||
|
|
||
|
ide_task_release (self, TRUE);
|
||
|
|
||
|
g_mutex_lock (&priv->mutex);
|
||
|
g_clear_pointer (&priv->result, ide_task_result_free);
|
||
|
g_mutex_unlock (&priv->mutex);
|
||
|
|
||
|
G_OBJECT_CLASS (ide_task_parent_class)->dispose (object);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_task_finalize (GObject *object)
|
||
|
{
|
||
|
IdeTask *self = (IdeTask *)object;
|
||
|
IdeTaskPrivate *priv = ide_task_get_instance_private (self);
|
||
|
|
||
|
G_LOCK (global_task_list);
|
||
|
g_queue_unlink (&global_task_list, &priv->global_link);
|
||
|
G_UNLOCK (global_task_list);
|
||
|
|
||
|
if (!priv->return_called)
|
||
|
g_critical ("%s [%s] finalized before completing",
|
||
|
G_OBJECT_TYPE_NAME (self),
|
||
|
priv->name ?: "unnamed");
|
||
|
else if (priv->chained && priv->chained->len)
|
||
|
g_critical ("%s [%s] finalized before dependents were notified",
|
||
|
G_OBJECT_TYPE_NAME (self),
|
||
|
priv->name ?: "unnamed");
|
||
|
else if (priv->thread_func)
|
||
|
g_critical ("%s [%s] finalized while thread_func is active",
|
||
|
G_OBJECT_TYPE_NAME (self),
|
||
|
priv->name ?: "unnamed");
|
||
|
else if (!priv->completed)
|
||
|
g_critical ("%s [%s] finalized before completion",
|
||
|
G_OBJECT_TYPE_NAME (self),
|
||
|
priv->name ?: "unnamed");
|
||
|
|
||
|
g_assert (priv->return_source == 0);
|
||
|
g_assert (priv->result == NULL);
|
||
|
g_assert (priv->task_data == NULL);
|
||
|
g_assert (priv->source_object == NULL);
|
||
|
g_assert (priv->chained == NULL);
|
||
|
g_assert (priv->thread_result == NULL);
|
||
|
|
||
|
g_clear_pointer (&priv->main_context, g_main_context_unref);
|
||
|
g_clear_object (&priv->cancellable);
|
||
|
g_mutex_clear (&priv->mutex);
|
||
|
|
||
|
G_OBJECT_CLASS (ide_task_parent_class)->finalize (object);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_task_get_property (GObject *object,
|
||
|
guint prop_id,
|
||
|
GValue *value,
|
||
|
GParamSpec *pspec)
|
||
|
{
|
||
|
IdeTask *self = IDE_TASK (object);
|
||
|
|
||
|
switch (prop_id)
|
||
|
{
|
||
|
case PROP_COMPLETED:
|
||
|
g_value_set_boolean (value, ide_task_get_completed (self));
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_task_class_init (IdeTaskClass *klass)
|
||
|
{
|
||
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||
|
|
||
|
object_class->dispose = ide_task_dispose;
|
||
|
object_class->finalize = ide_task_finalize;
|
||
|
object_class->get_property = ide_task_get_property;
|
||
|
|
||
|
properties [PROP_COMPLETED] =
|
||
|
g_param_spec_boolean ("completed",
|
||
|
"Completed",
|
||
|
"If the task has completed",
|
||
|
FALSE,
|
||
|
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
|
||
|
|
||
|
g_object_class_install_properties (object_class, N_PROPS, properties);
|
||
|
|
||
|
/* This can be called multiple times, so we use this to allow
|
||
|
* unit tests to work without having to expose the function as
|
||
|
* public API.
|
||
|
*/
|
||
|
_ide_thread_pool_init (FALSE);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_task_init (IdeTask *self)
|
||
|
{
|
||
|
IdeTaskPrivate *priv = ide_task_get_instance_private (self);
|
||
|
|
||
|
g_mutex_init (&priv->mutex);
|
||
|
|
||
|
priv->check_cancellable = TRUE;
|
||
|
priv->release_on_propagate = TRUE;
|
||
|
priv->priority = G_PRIORITY_DEFAULT;
|
||
|
priv->complete_priority = PRIORITY_REDRAW + 1;
|
||
|
priv->main_context = g_main_context_ref_thread_default ();
|
||
|
priv->global_link.data = self;
|
||
|
|
||
|
G_LOCK (global_task_list);
|
||
|
g_queue_push_tail_link (&global_task_list, &priv->global_link);
|
||
|
G_UNLOCK (global_task_list);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* ide_task_get_source_object: (skip)
|
||
|
* @self: a #IdeTask
|
||
|
*
|
||
|
* Gets the #GObject used when creating the source object.
|
||
|
*
|
||
|
* As this does not provide ownership transfer of the #GObject, it is a
|
||
|
* programmer error to call this function outside of a thread worker called
|
||
|
* from ide_task_run_in_thread() or outside the #GMainContext that is
|
||
|
* associated with the task.
|
||
|
*
|
||
|
* If you need to access the object in other scenarios, you must use the
|
||
|
* g_async_result_get_source_object() which provides a full reference to the
|
||
|
* source object, safely. You are responsible for ensuring that you do not
|
||
|
* release the object in a manner that is unsafe for the source object.
|
||
|
*
|
||
|
* Returns: (transfer none) (nullable) (type GObject.Object): a #GObject or %NULL
|
||
|
*
|
||
|
* Since: 3.32
|
||
|
*/
|
||
|
gpointer
|
||
|
ide_task_get_source_object (IdeTask *self)
|
||
|
{
|
||
|
IdeTaskPrivate *priv = ide_task_get_instance_private (self);
|
||
|
gpointer ret;
|
||
|
|
||
|
g_return_val_if_fail (IDE_IS_TASK (self), NULL);
|
||
|
|
||
|
g_mutex_lock (&priv->mutex);
|
||
|
ret = priv->source_object;
|
||
|
g_mutex_unlock (&priv->mutex);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* ide_task_new:
|
||
|
* @source_object: (type GObject.Object) (nullable): a #GObject or %NULL
|
||
|
* @cancellable: (nullable): a #GCancellable or %NULL
|
||
|
* @callback: (scope async) (nullable): a #GAsyncReadyCallback or %NULL
|
||
|
* @user_data: closure data for @callback
|
||
|
*
|
||
|
* Creates a new #IdeTask.
|
||
|
*
|
||
|
* #IdeTask is similar to #GTask but provides some additional guarantees
|
||
|
* such that by default, the source object, task data, and unused results
|
||
|
* are guaranteed to be finalized in the #GMainContext associated with
|
||
|
* the task itself.
|
||
|
*
|
||
|
* Returns: (transfer full): an #IdeTask
|
||
|
*
|
||
|
* Since: 3.32
|
||
|
*/
|
||
|
IdeTask *
|
||
|
(ide_task_new) (gpointer source_object,
|
||
|
GCancellable *cancellable,
|
||
|
GAsyncReadyCallback callback,
|
||
|
gpointer user_data)
|
||
|
{
|
||
|
g_autoptr(IdeTask) self = NULL;
|
||
|
IdeTaskPrivate *priv;
|
||
|
|
||
|
g_return_val_if_fail (!source_object || G_IS_OBJECT (source_object), NULL);
|
||
|
g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), NULL);
|
||
|
|
||
|
self = g_object_new (IDE_TYPE_TASK, NULL);
|
||
|
priv = ide_task_get_instance_private (self);
|
||
|
|
||
|
priv->source_object = source_object ? g_object_ref (source_object) : NULL;
|
||
|
priv->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
|
||
|
priv->callback = callback;
|
||
|
priv->user_data = user_data;
|
||
|
#ifdef ENABLE_TIME_CHART
|
||
|
priv->begin_time = g_get_monotonic_time ();
|
||
|
#endif
|
||
|
|
||
|
return g_steal_pointer (&self);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* ide_task_is_valid:
|
||
|
* @self: (nullable) (type IdeTask): a #IdeTask
|
||
|
* @source_object: (nullable): a #GObject or %NULL
|
||
|
*
|
||
|
* Checks if @source_object matches the object the task was created with.
|
||
|
*
|
||
|
* Returns: %TRUE is source_object matches
|
||
|
*
|
||
|
* Since: 3.32
|
||
|
*/
|
||
|
gboolean
|
||
|
ide_task_is_valid (gpointer self,
|
||
|
gpointer source_object)
|
||
|
{
|
||
|
IdeTaskPrivate *priv = ide_task_get_instance_private (self);
|
||
|
|
||
|
return IDE_IS_TASK (self) && priv->source_object == source_object;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* ide_task_get_completed:
|
||
|
* @self: a #IdeTask
|
||
|
*
|
||
|
* Gets the "completed" property. This is %TRUE after the callback used when
|
||
|
* creating the task has been executed.
|
||
|
*
|
||
|
* The property will be notified using g_object_notify() exactly once in the
|
||
|
* same #GMainContext as the callback.
|
||
|
*
|
||
|
* Returns: %TRUE if the task has completed
|
||
|
*
|
||
|
* Since: 3.32
|
||
|
*/
|
||
|
gboolean
|
||
|
ide_task_get_completed (IdeTask *self)
|
||
|
{
|
||
|
IdeTaskPrivate *priv = ide_task_get_instance_private (self);
|
||
|
gboolean ret;
|
||
|
|
||
|
g_return_val_if_fail (IDE_IS_TASK (self), FALSE);
|
||
|
|
||
|
g_mutex_lock (&priv->mutex);
|
||
|
ret = priv->completed;
|
||
|
g_mutex_unlock (&priv->mutex);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
gint
|
||
|
ide_task_get_priority (IdeTask *self)
|
||
|
{
|
||
|
IdeTaskPrivate *priv = ide_task_get_instance_private (self);
|
||
|
gint ret;
|
||
|
|
||
|
g_return_val_if_fail (IDE_IS_TASK (self), 0);
|
||
|
|
||
|
g_mutex_lock (&priv->mutex);
|
||
|
ret = priv->priority;
|
||
|
g_mutex_unlock (&priv->mutex);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
ide_task_set_priority (IdeTask *self,
|
||
|
gint priority)
|
||
|
{
|
||
|
IdeTaskPrivate *priv = ide_task_get_instance_private (self);
|
||
|
|
||
|
g_return_if_fail (IDE_IS_TASK (self));
|
||
|
|
||
|
g_mutex_lock (&priv->mutex);
|
||
|
priv->priority = priority;
|
||
|
g_mutex_unlock (&priv->mutex);
|
||
|
}
|
||
|
|
||
|
gint
|
||
|
ide_task_get_complete_priority (IdeTask *self)
|
||
|
{
|
||
|
IdeTaskPrivate *priv = ide_task_get_instance_private (self);
|
||
|
gint ret;
|
||
|
|
||
|
g_return_val_if_fail (IDE_IS_TASK (self), 0);
|
||
|
|
||
|
g_mutex_lock (&priv->mutex);
|
||
|
ret = priv->complete_priority;
|
||
|
g_mutex_unlock (&priv->mutex);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
ide_task_set_complete_priority (IdeTask *self,
|
||
|
gint complete_priority)
|
||
|
{
|
||
|
IdeTaskPrivate *priv = ide_task_get_instance_private (self);
|
||
|
|
||
|
g_return_if_fail (IDE_IS_TASK (self));
|
||
|
|
||
|
g_mutex_lock (&priv->mutex);
|
||
|
priv->complete_priority = complete_priority;
|
||
|
g_mutex_unlock (&priv->mutex);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* ide_task_get_cancellable:
|
||
|
* @self: a #IdeTask
|
||
|
*
|
||
|
* Gets the #GCancellable for the task.
|
||
|
*
|
||
|
* Returns: (transfer none) (nullable): a #GCancellable or %NULL
|
||
|
*
|
||
|
* Since: 3.32
|
||
|
*/
|
||
|
GCancellable *
|
||
|
ide_task_get_cancellable (IdeTask *self)
|
||
|
{
|
||
|
IdeTaskPrivate *priv = ide_task_get_instance_private (self);
|
||
|
|
||
|
g_return_val_if_fail (IDE_IS_TASK (self), NULL);
|
||
|
|
||
|
return priv->cancellable;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_task_deliver_result (IdeTask *self,
|
||
|
IdeTaskResult *result)
|
||
|
{
|
||
|
IdeTaskPrivate *priv = ide_task_get_instance_private (self);
|
||
|
|
||
|
g_assert (IDE_IS_TASK (self));
|
||
|
g_assert (result != NULL);
|
||
|
g_assert (result->task == NULL);
|
||
|
g_assert (result->main_context == NULL);
|
||
|
|
||
|
/* This task was chained from another task. This completes the result
|
||
|
* and we should dispatch the callback. To simplify the dispatching and
|
||
|
* help prevent any re-entrancy issues, we defer back to the main context
|
||
|
* to complete the operation.
|
||
|
*/
|
||
|
|
||
|
result->task = g_object_ref (self);
|
||
|
result->main_context = g_main_context_ref (priv->main_context);
|
||
|
result->complete_priority = priv->complete_priority;
|
||
|
|
||
|
g_mutex_lock (&priv->mutex);
|
||
|
|
||
|
priv->return_called = TRUE;
|
||
|
priv->return_source = ide_task_complete (g_steal_pointer (&result));
|
||
|
|
||
|
g_mutex_unlock (&priv->mutex);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_task_release (IdeTask *self,
|
||
|
gboolean force)
|
||
|
{
|
||
|
IdeTaskPrivate *priv = ide_task_get_instance_private (self);
|
||
|
g_autoptr(IdeTaskData) task_data = NULL;
|
||
|
g_autoptr(GObject) source_object = NULL;
|
||
|
g_autoptr(GPtrArray) chained = NULL;
|
||
|
|
||
|
g_assert (IDE_IS_TASK (self));
|
||
|
|
||
|
g_mutex_lock (&priv->mutex);
|
||
|
if (force || priv->release_on_propagate)
|
||
|
{
|
||
|
source_object = g_steal_pointer (&priv->source_object);
|
||
|
task_data = g_steal_pointer (&priv->task_data);
|
||
|
chained = g_steal_pointer (&priv->chained);
|
||
|
}
|
||
|
g_mutex_unlock (&priv->mutex);
|
||
|
|
||
|
if (chained)
|
||
|
{
|
||
|
for (guint i = 0; i < chained->len; i++)
|
||
|
{
|
||
|
IdeTask *task = g_ptr_array_index (chained, i);
|
||
|
|
||
|
ide_task_return_new_error (task,
|
||
|
G_IO_ERROR,
|
||
|
G_IO_ERROR_FAILED,
|
||
|
"Error synthesized for task, parent task disposed");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static gboolean
|
||
|
ide_task_return_cb (gpointer user_data)
|
||
|
{
|
||
|
g_autoptr(IdeTask) self = NULL;
|
||
|
g_autoptr(IdeTaskResult) result = user_data;
|
||
|
g_autoptr(IdeTaskResult) result_copy = NULL;
|
||
|
g_autoptr(GCancellable) cancellable = NULL;
|
||
|
g_autoptr(GObject) source_object = NULL;
|
||
|
g_autoptr(GPtrArray) chained = NULL;
|
||
|
GAsyncReadyCallback callback = NULL;
|
||
|
gpointer callback_data = NULL;
|
||
|
IdeTaskPrivate *priv;
|
||
|
|
||
|
g_assert (result != NULL);
|
||
|
g_assert (IDE_IS_TASK (result->task));
|
||
|
|
||
|
/* We steal the task object, because we only stash it in the result
|
||
|
* structure to get it here. And if we held onto it, we would have
|
||
|
* a reference cycle.
|
||
|
*/
|
||
|
self = g_steal_pointer (&result->task);
|
||
|
priv = ide_task_get_instance_private (self);
|
||
|
|
||
|
#ifdef ENABLE_TIME_CHART
|
||
|
g_message ("TASK-END: %s: duration=%lf",
|
||
|
priv->name,
|
||
|
(g_get_monotonic_time () - priv->begin_time) / (gdouble)G_USEC_PER_SEC);
|
||
|
#endif
|
||
|
|
||
|
g_mutex_lock (&priv->mutex);
|
||
|
|
||
|
g_assert (priv->return_source != 0);
|
||
|
|
||
|
priv->return_source = 0;
|
||
|
|
||
|
if (priv->got_cancel && priv->result != NULL)
|
||
|
{
|
||
|
/* We can discard this since we already handled a result for the
|
||
|
* task. We delivered this here just so that we could finalize
|
||
|
* any objects back inside them main context.
|
||
|
*/
|
||
|
g_mutex_unlock (&priv->mutex);
|
||
|
return G_SOURCE_REMOVE;
|
||
|
}
|
||
|
|
||
|
g_assert (priv->result == NULL);
|
||
|
g_assert (priv->return_called == TRUE);
|
||
|
|
||
|
priv->result = g_steal_pointer (&result);
|
||
|
|
||
|
callback = priv->callback;
|
||
|
callback_data = priv->user_data;
|
||
|
|
||
|
priv->callback = NULL;
|
||
|
priv->user_data = NULL;
|
||
|
|
||
|
source_object = priv->source_object ? g_object_ref (priv->source_object) : NULL;
|
||
|
cancellable = priv->cancellable ? g_object_ref (priv->cancellable) : NULL;
|
||
|
|
||
|
chained = g_steal_pointer (&priv->chained);
|
||
|
|
||
|
/* Make a private copy of the result data if we're going to need to notify
|
||
|
* other tasks of our result. We can't guarantee the result in @task will
|
||
|
* stay alive during our dispatch callbacks, so we need to have a copy.
|
||
|
*/
|
||
|
if (chained != NULL && chained->len > 0)
|
||
|
result_copy = ide_task_result_copy (priv->result);
|
||
|
|
||
|
g_mutex_unlock (&priv->mutex);
|
||
|
|
||
|
if (callback)
|
||
|
callback (source_object, G_ASYNC_RESULT (self), callback_data);
|
||
|
|
||
|
if (chained)
|
||
|
{
|
||
|
for (guint i = 0; i < chained->len; i++)
|
||
|
{
|
||
|
IdeTask *other = g_ptr_array_index (chained, i);
|
||
|
g_autoptr(IdeTaskResult) other_result = ide_task_result_copy (result_copy);
|
||
|
|
||
|
ide_task_deliver_result (other, g_steal_pointer (&other_result));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
g_mutex_lock (&priv->mutex);
|
||
|
priv->completed = TRUE;
|
||
|
g_mutex_unlock (&priv->mutex);
|
||
|
|
||
|
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_COMPLETED]);
|
||
|
|
||
|
ide_task_release (self, FALSE);
|
||
|
|
||
|
return G_SOURCE_REMOVE;
|
||
|
}
|
||
|
|
||
|
static gboolean
|
||
|
ide_task_return_dummy_cb (gpointer data)
|
||
|
{
|
||
|
return G_SOURCE_REMOVE;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_task_return (IdeTask *self,
|
||
|
IdeTaskResult *result)
|
||
|
{
|
||
|
IdeTaskPrivate *priv = ide_task_get_instance_private (self);
|
||
|
g_autoptr(GMutexLocker) locker = NULL;
|
||
|
|
||
|
g_assert (IDE_IS_TASK (self));
|
||
|
g_assert (result != NULL);
|
||
|
g_assert (result->task == NULL);
|
||
|
|
||
|
locker = g_mutex_locker_new (&priv->mutex);
|
||
|
|
||
|
if (priv->cancel_handler && priv->cancellable)
|
||
|
{
|
||
|
g_cancellable_disconnect (priv->cancellable, priv->cancel_handler);
|
||
|
priv->cancel_handler = 0;
|
||
|
}
|
||
|
|
||
|
if (priv->return_called)
|
||
|
{
|
||
|
GSource *source;
|
||
|
|
||
|
if (result->type == IDE_TASK_RESULT_CANCELLED)
|
||
|
{
|
||
|
/* We already had a result, and now raced to be notified of
|
||
|
* cancellation. We can safely free this result even if we're
|
||
|
* currently in a worker thread.
|
||
|
*/
|
||
|
ide_task_result_free (result);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* If we haven't been cancelled, then we reached this path multiple
|
||
|
* times by programmer error.
|
||
|
*/
|
||
|
if (!priv->got_cancel)
|
||
|
g_critical ("Attempted to set result on task [%s] multiple times", priv->name);
|
||
|
|
||
|
/*
|
||
|
* This task has already returned, but we need to ensure that we pass
|
||
|
* the data back to the main context so that it is freed appropriately.
|
||
|
*/
|
||
|
|
||
|
source = g_idle_source_new ();
|
||
|
g_source_set_name (source, "[ide-task] finalize task result");
|
||
|
g_source_set_ready_time (source, -1);
|
||
|
g_source_set_callback (source,
|
||
|
ide_task_return_dummy_cb,
|
||
|
result,
|
||
|
(GDestroyNotify)ide_task_result_free);
|
||
|
g_source_attach (source, priv->main_context);
|
||
|
g_source_unref (source);
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
priv->return_called = TRUE;
|
||
|
|
||
|
if (result->type == IDE_TASK_RESULT_CANCELLED)
|
||
|
priv->got_cancel = TRUE;
|
||
|
|
||
|
result->task = g_object_ref (self);
|
||
|
result->main_context = g_main_context_ref (priv->main_context);
|
||
|
result->complete_priority = priv->complete_priority;
|
||
|
|
||
|
/* We can queue the result immediately if we're not being called
|
||
|
* while we're inside of a ide_task_run_in_thread() callback. Otherwise,
|
||
|
* that thread cleanup must complete this to ensure objects cannot
|
||
|
* be finalized in that thread.
|
||
|
*/
|
||
|
if (!priv->thread_called || IDE_IS_MAIN_THREAD ())
|
||
|
priv->return_source = ide_task_complete (result);
|
||
|
else if (priv->return_on_cancel && result->type == IDE_TASK_RESULT_CANCELLED)
|
||
|
priv->return_source = ide_task_complete (result);
|
||
|
else
|
||
|
priv->thread_result = result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* ide_task_return_int:
|
||
|
* @self: a #IdeTask
|
||
|
* @result: the result for the task
|
||
|
*
|
||
|
* Sets the result of the task to @result.
|
||
|
*
|
||
|
* Other tasks depending on the result will be notified after returning
|
||
|
* to the #GMainContext of the task.
|
||
|
*
|
||
|
* Since: 3.32
|
||
|
*/
|
||
|
void
|
||
|
ide_task_return_int (IdeTask *self,
|
||
|
gssize result)
|
||
|
{
|
||
|
IdeTaskResult *ret;
|
||
|
|
||
|
g_return_if_fail (IDE_IS_TASK (self));
|
||
|
|
||
|
ret = g_slice_new0 (IdeTaskResult);
|
||
|
ret->type = IDE_TASK_RESULT_INT;
|
||
|
ret->u.v_int = result;
|
||
|
|
||
|
ide_task_return (self, g_steal_pointer (&ret));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* ide_task_return_boolean:
|
||
|
* @self: a #IdeTask
|
||
|
* @result: the result for the task
|
||
|
*
|
||
|
* Sets the result of the task to @result.
|
||
|
*
|
||
|
* Other tasks depending on the result will be notified after returning
|
||
|
* to the #GMainContext of the task.
|
||
|
*
|
||
|
* Since: 3.32
|
||
|
*/
|
||
|
void
|
||
|
ide_task_return_boolean (IdeTask *self,
|
||
|
gboolean result)
|
||
|
{
|
||
|
IdeTaskResult *ret;
|
||
|
|
||
|
g_return_if_fail (IDE_IS_TASK (self));
|
||
|
|
||
|
ret = g_slice_new0 (IdeTaskResult);
|
||
|
ret->type = IDE_TASK_RESULT_BOOLEAN;
|
||
|
ret->u.v_bool = !!result;
|
||
|
|
||
|
ide_task_return (self, g_steal_pointer (&ret));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* ide_task_return_boxed: (skip)
|
||
|
* @self: a #IdeTask
|
||
|
* @result_type: the #GType of the boxed type
|
||
|
* @result: (transfer full): the result to be returned
|
||
|
*
|
||
|
* This is similar to ide_task_return_pointer(), but allows the task to
|
||
|
* know the boxed #GType so that the result may be propagated to chained
|
||
|
* tasks.
|
||
|
*
|
||
|
* Since: 3.32
|
||
|
*/
|
||
|
void
|
||
|
ide_task_return_boxed (IdeTask *self,
|
||
|
GType result_type,
|
||
|
gpointer result)
|
||
|
{
|
||
|
IdeTaskResult *ret;
|
||
|
|
||
|
g_return_if_fail (IDE_IS_TASK (self));
|
||
|
g_return_if_fail (result_type != G_TYPE_INVALID);
|
||
|
g_return_if_fail (G_TYPE_IS_BOXED (result_type));
|
||
|
|
||
|
ret = g_slice_new0 (IdeTaskResult);
|
||
|
ret->type = IDE_TASK_RESULT_BOXED;
|
||
|
ret->u.v_boxed.type = result_type;
|
||
|
ret->u.v_boxed.pointer = result;
|
||
|
|
||
|
ide_task_return (self, g_steal_pointer (&ret));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* ide_task_return_object:
|
||
|
* @self: a #IdeTask
|
||
|
* @instance: (transfer full) (type GObject.Object): a #GObject instance
|
||
|
*
|
||
|
* Returns a new object instance.
|
||
|
*
|
||
|
* Takes ownership of @instance to allow saving a reference increment and
|
||
|
* decrement by the caller.
|
||
|
*
|
||
|
* Since: 3.32
|
||
|
*/
|
||
|
void
|
||
|
ide_task_return_object (IdeTask *self,
|
||
|
gpointer instance)
|
||
|
{
|
||
|
IdeTaskResult *ret;
|
||
|
|
||
|
g_return_if_fail (IDE_IS_TASK (self));
|
||
|
g_return_if_fail (!instance || G_IS_OBJECT (instance));
|
||
|
|
||
|
ret = g_slice_new0 (IdeTaskResult);
|
||
|
ret->type = IDE_TASK_RESULT_OBJECT;
|
||
|
ret->u.v_object = instance;
|
||
|
|
||
|
ide_task_return (self, g_steal_pointer (&ret));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* ide_task_return_pointer: (skip)
|
||
|
* @self: a #IdeTask
|
||
|
* @data: the data to return
|
||
|
* @destroy: an optional #GDestroyNotify to cleanup data if no handler
|
||
|
* propagates the result
|
||
|
*
|
||
|
* Returns a new raw pointer.
|
||
|
*
|
||
|
* Note that pointers cannot be chained to other tasks, so you may not
|
||
|
* use ide_task_chain() in conjunction with a task returning a pointer
|
||
|
* using ide_task_return_pointer().
|
||
|
*
|
||
|
* If you need task chaining with pointers, see ide_task_return_boxed()
|
||
|
* or ide_task_return_object().
|
||
|
*
|
||
|
* Since: 3.32
|
||
|
*/
|
||
|
void
|
||
|
(ide_task_return_pointer) (IdeTask *self,
|
||
|
gpointer data,
|
||
|
GDestroyNotify destroy)
|
||
|
{
|
||
|
IdeTaskResult *ret;
|
||
|
|
||
|
g_return_if_fail (IDE_IS_TASK (self));
|
||
|
|
||
|
ret = g_slice_new0 (IdeTaskResult);
|
||
|
ret->type = IDE_TASK_RESULT_POINTER;
|
||
|
ret->u.v_pointer.pointer = data;
|
||
|
ret->u.v_pointer.destroy = destroy;
|
||
|
|
||
|
ide_task_return (self, g_steal_pointer (&ret));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* ide_task_return_error:
|
||
|
* @self: a #IdeTask
|
||
|
* @error: (transfer full): a #GError
|
||
|
*
|
||
|
* Sets @error as the result of the #IdeTask
|
||
|
*
|
||
|
* Since: 3.32
|
||
|
*/
|
||
|
void
|
||
|
ide_task_return_error (IdeTask *self,
|
||
|
GError *error)
|
||
|
{
|
||
|
IdeTaskResult *ret;
|
||
|
|
||
|
g_return_if_fail (IDE_IS_TASK (self));
|
||
|
|
||
|
ret = g_slice_new0 (IdeTaskResult);
|
||
|
ret->type = IDE_TASK_RESULT_ERROR;
|
||
|
ret->u.v_error = error;
|
||
|
|
||
|
ide_task_return (self, g_steal_pointer (&ret));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* ide_task_return_new_error:
|
||
|
* @self: a #IdeTask
|
||
|
* @error_domain: the error domain of the #GError
|
||
|
* @error_code: the error code for the #GError
|
||
|
* @format: the printf-style format string
|
||
|
*
|
||
|
* Creates a new #GError and sets it as the result for the task.
|
||
|
*
|
||
|
* Since: 3.32
|
||
|
*/
|
||
|
void
|
||
|
ide_task_return_new_error (IdeTask *self,
|
||
|
GQuark error_domain,
|
||
|
gint error_code,
|
||
|
const gchar *format,
|
||
|
...)
|
||
|
{
|
||
|
GError *error;
|
||
|
va_list args;
|
||
|
|
||
|
g_return_if_fail (IDE_IS_TASK (self));
|
||
|
|
||
|
va_start (args, format);
|
||
|
error = g_error_new_valist (error_domain, error_code, format, args);
|
||
|
va_end (args);
|
||
|
|
||
|
ide_task_return_error (self, g_steal_pointer (&error));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* ide_task_return_error_if_cancelled:
|
||
|
* @self: a #IdeTask
|
||
|
*
|
||
|
* Returns a new #GError if the cancellable associated with the task
|
||
|
* has been cancelled. If so, %TRUE is returned, otherwise %FALSE.
|
||
|
*
|
||
|
* If the source object related to the task is an #IdeObject and that
|
||
|
* object has had been requested to destroy, it too will be considered
|
||
|
* a cancellation state.
|
||
|
*
|
||
|
* Returns: %TRUE if the task was cancelled and error returned.
|
||
|
*
|
||
|
* Since: 3.32
|
||
|
*/
|
||
|
gboolean
|
||
|
ide_task_return_error_if_cancelled (IdeTask *self)
|
||
|
{
|
||
|
IdeTaskPrivate *priv = ide_task_get_instance_private (self);
|
||
|
gboolean failed;
|
||
|
|
||
|
g_return_val_if_fail (IDE_IS_TASK (self), FALSE);
|
||
|
|
||
|
g_mutex_lock (&priv->mutex);
|
||
|
failed = g_cancellable_is_cancelled (priv->cancellable) ||
|
||
|
(IDE_IS_OBJECT (priv->source_object) &&
|
||
|
ide_object_in_destruction (IDE_OBJECT (priv->source_object)));
|
||
|
g_mutex_unlock (&priv->mutex);
|
||
|
|
||
|
if (failed)
|
||
|
ide_task_return_new_error (self,
|
||
|
G_IO_ERROR,
|
||
|
G_IO_ERROR_CANCELLED,
|
||
|
"The task was cancelled");
|
||
|
|
||
|
return failed;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* ide_task_set_release_on_propagate:
|
||
|
* @self: a #IdeTask
|
||
|
* @release_on_propagate: if data should be released on propagate
|
||
|
*
|
||
|
* Setting this to %TRUE (the default) ensures that the task will release all
|
||
|
* task data and source_object references after executing the configured
|
||
|
* callback. This is useful to ensure that dependent objects are finalized
|
||
|
* in the thread-default #GMainContext the task was created in.
|
||
|
*
|
||
|
* Generally, you want to leave this as %TRUE to ensure thread-safety on the
|
||
|
* dependent objects and task data.
|
||
|
*
|
||
|
* Since: 3.32
|
||
|
*/
|
||
|
void
|
||
|
ide_task_set_release_on_propagate (IdeTask *self,
|
||
|
gboolean release_on_propagate)
|
||
|
{
|
||
|
IdeTaskPrivate *priv = ide_task_get_instance_private (self);
|
||
|
|
||
|
g_return_if_fail (IDE_IS_TASK (self));
|
||
|
|
||
|
release_on_propagate = !!release_on_propagate;
|
||
|
|
||
|
g_mutex_lock (&priv->mutex);
|
||
|
priv->release_on_propagate = release_on_propagate;
|
||
|
g_mutex_unlock (&priv->mutex);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* ide_task_set_source_tag:
|
||
|
* @self: a #IdeTask
|
||
|
* @source_tag: a tag to identify the task, usual a function pointer
|
||
|
*
|
||
|
* Sets the source tag for the task. Generally this is a function pointer
|
||
|
* of the function that created the task.
|
||
|
*
|
||
|
* Since: 3.32
|
||
|
*/
|
||
|
void
|
||
|
ide_task_set_source_tag (IdeTask *self,
|
||
|
gpointer source_tag)
|
||
|
{
|
||
|
IdeTaskPrivate *priv = ide_task_get_instance_private (self);
|
||
|
|
||
|
g_return_if_fail (IDE_IS_TASK (self));
|
||
|
|
||
|
g_mutex_lock (&priv->mutex);
|
||
|
priv->source_tag = source_tag;
|
||
|
g_mutex_unlock (&priv->mutex);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* ide_task_set_check_cancellable:
|
||
|
* @self: a #IdeTask
|
||
|
* @check_cancellable: %TRUE if the cancellable should be checked
|
||
|
*
|
||
|
* Setting @check_cancellable to %TRUE (the default) ensures that the
|
||
|
* #GCancellable used when creating the #IdeTask is checked for cancellation
|
||
|
* before propagating a result. If cancelled, an error will be returned
|
||
|
* instead of the result.
|
||
|
*
|
||
|
* Since: 3.32
|
||
|
*/
|
||
|
void
|
||
|
ide_task_set_check_cancellable (IdeTask *self,
|
||
|
gboolean check_cancellable)
|
||
|
{
|
||
|
IdeTaskPrivate *priv = ide_task_get_instance_private (self);
|
||
|
|
||
|
g_return_if_fail (IDE_IS_TASK (self));
|
||
|
|
||
|
check_cancellable = !!check_cancellable;
|
||
|
|
||
|
g_mutex_lock (&priv->mutex);
|
||
|
priv->check_cancellable = check_cancellable;
|
||
|
g_mutex_unlock (&priv->mutex);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* ide_task_run_in_thread: (skip)
|
||
|
* @self: a #IdeTask
|
||
|
* @thread_func: a function to execute on a worker thread
|
||
|
*
|
||
|
* Scheules @thread_func to be executed on a worker thread.
|
||
|
*
|
||
|
* @thread_func must complete the task from the worker thread using one of
|
||
|
* ide_task_return_boolean(), ide_task_return_int(), or
|
||
|
* ide_task_return_pointer().
|
||
|
*
|
||
|
* Since: 3.32
|
||
|
*/
|
||
|
void
|
||
|
ide_task_run_in_thread (IdeTask *self,
|
||
|
IdeTaskThreadFunc thread_func)
|
||
|
{
|
||
|
IdeTaskPrivate *priv = ide_task_get_instance_private (self);
|
||
|
g_autoptr(GError) error = NULL;
|
||
|
|
||
|
g_return_if_fail (IDE_IS_TASK (self));
|
||
|
g_return_if_fail (thread_func != NULL);
|
||
|
|
||
|
g_mutex_lock (&priv->mutex);
|
||
|
|
||
|
if (priv->completed == TRUE)
|
||
|
{
|
||
|
g_critical ("Task already completed, cannot run in thread");
|
||
|
goto unlock;
|
||
|
}
|
||
|
|
||
|
if (priv->thread_called)
|
||
|
{
|
||
|
g_critical ("Run in thread already called, cannot run again");
|
||
|
goto unlock;
|
||
|
}
|
||
|
|
||
|
priv->thread_called = TRUE;
|
||
|
priv->thread_func = thread_func;
|
||
|
|
||
|
ide_thread_pool_push_with_priority ((IdeThreadPoolKind)priv->kind,
|
||
|
priv->priority,
|
||
|
ide_task_thread_func,
|
||
|
g_object_ref (self));
|
||
|
|
||
|
unlock:
|
||
|
g_mutex_unlock (&priv->mutex);
|
||
|
|
||
|
if (error != NULL)
|
||
|
ide_task_return_error (self, g_steal_pointer (&error));
|
||
|
}
|
||
|
|
||
|
static IdeTaskResult *
|
||
|
ide_task_propagate_locked (IdeTask *self,
|
||
|
IdeTaskResultType expected_type,
|
||
|
GError **error)
|
||
|
{
|
||
|
IdeTaskPrivate *priv = ide_task_get_instance_private (self);
|
||
|
IdeTaskResult *ret = NULL;
|
||
|
|
||
|
g_assert (IDE_IS_TASK (self));
|
||
|
g_assert (expected_type > IDE_TASK_RESULT_NONE);
|
||
|
|
||
|
if (priv->result == NULL)
|
||
|
{
|
||
|
g_autoptr(GMainContext) context = g_main_context_ref (priv->main_context);
|
||
|
|
||
|
while (priv->return_source)
|
||
|
{
|
||
|
g_mutex_unlock (&priv->mutex);
|
||
|
g_main_context_iteration (context, FALSE);
|
||
|
g_mutex_lock (&priv->mutex);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (priv->result == NULL)
|
||
|
{
|
||
|
g_set_error_literal (error,
|
||
|
G_IO_ERROR,
|
||
|
G_IO_ERROR_FAILED,
|
||
|
"No result available for task");
|
||
|
}
|
||
|
else if (priv->result->type == IDE_TASK_RESULT_ERROR)
|
||
|
{
|
||
|
if (error != NULL)
|
||
|
*error = g_error_copy (priv->result->u.v_error);
|
||
|
}
|
||
|
else if ((priv->check_cancellable && g_cancellable_is_cancelled (priv->cancellable)) ||
|
||
|
priv->result->type == IDE_TASK_RESULT_CANCELLED)
|
||
|
{
|
||
|
g_set_error (error,
|
||
|
G_IO_ERROR,
|
||
|
G_IO_ERROR_CANCELLED,
|
||
|
"The operation was cancelled");
|
||
|
}
|
||
|
else if (IDE_IS_OBJECT (priv->source_object) &&
|
||
|
ide_object_in_destruction (IDE_OBJECT (priv->source_object)))
|
||
|
{
|
||
|
g_set_error (error,
|
||
|
G_IO_ERROR,
|
||
|
G_IO_ERROR_CANCELLED,
|
||
|
"The object was destroyed while the task executed");
|
||
|
}
|
||
|
else if (priv->result->type != expected_type)
|
||
|
{
|
||
|
g_set_error (error,
|
||
|
G_IO_ERROR,
|
||
|
G_IO_ERROR_FAILED,
|
||
|
"Task expected result of %s got %s",
|
||
|
result_type_name (expected_type),
|
||
|
result_type_name (priv->result->type));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
g_assert (priv->result != NULL);
|
||
|
g_assert (priv->result->type == expected_type);
|
||
|
|
||
|
if (priv->release_on_propagate)
|
||
|
ret = g_steal_pointer (&priv->result);
|
||
|
else if (priv->result->type == IDE_TASK_RESULT_POINTER)
|
||
|
ret = g_steal_pointer (&priv->result);
|
||
|
else
|
||
|
ret = ide_task_result_copy (priv->result);
|
||
|
|
||
|
g_assert (ret != NULL);
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
gboolean
|
||
|
ide_task_propagate_boolean (IdeTask *self,
|
||
|
GError **error)
|
||
|
{
|
||
|
IdeTaskPrivate *priv = ide_task_get_instance_private (self);
|
||
|
g_autoptr(GMutexLocker) locker = NULL;
|
||
|
g_autoptr(IdeTaskResult) res = NULL;
|
||
|
|
||
|
g_return_val_if_fail (IDE_IS_TASK (self), FALSE);
|
||
|
|
||
|
locker = g_mutex_locker_new (&priv->mutex);
|
||
|
|
||
|
if (!(res = ide_task_propagate_locked (self, IDE_TASK_RESULT_BOOLEAN, error)))
|
||
|
return FALSE;
|
||
|
|
||
|
return res->u.v_bool;
|
||
|
}
|
||
|
|
||
|
gpointer
|
||
|
ide_task_propagate_boxed (IdeTask *self,
|
||
|
GError **error)
|
||
|
{
|
||
|
IdeTaskPrivate *priv = ide_task_get_instance_private (self);
|
||
|
g_autoptr(GMutexLocker) locker = NULL;
|
||
|
g_autoptr(IdeTaskResult) res = NULL;
|
||
|
|
||
|
g_return_val_if_fail (IDE_IS_TASK (self), NULL);
|
||
|
|
||
|
locker = g_mutex_locker_new (&priv->mutex);
|
||
|
|
||
|
if (!(res = ide_task_propagate_locked (self, IDE_TASK_RESULT_BOXED, error)))
|
||
|
return NULL;
|
||
|
|
||
|
return g_boxed_copy (res->u.v_boxed.type, res->u.v_boxed.pointer);
|
||
|
}
|
||
|
|
||
|
gssize
|
||
|
ide_task_propagate_int (IdeTask *self,
|
||
|
GError **error)
|
||
|
{
|
||
|
IdeTaskPrivate *priv = ide_task_get_instance_private (self);
|
||
|
g_autoptr(GMutexLocker) locker = NULL;
|
||
|
g_autoptr(IdeTaskResult) res = NULL;
|
||
|
|
||
|
g_return_val_if_fail (IDE_IS_TASK (self), 0);
|
||
|
|
||
|
locker = g_mutex_locker_new (&priv->mutex);
|
||
|
|
||
|
if (!(res = ide_task_propagate_locked (self, IDE_TASK_RESULT_INT, error)))
|
||
|
return 0;
|
||
|
|
||
|
return res->u.v_int;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* ide_task_propagate_object:
|
||
|
* @self: a #IdeTask
|
||
|
* @error: a location for a #GError, or %NULL
|
||
|
*
|
||
|
* Returns an object if the task completed with an object. Otherwise, %NULL
|
||
|
* is returned.
|
||
|
*
|
||
|
* @error is set if the task completed with an error.
|
||
|
*
|
||
|
* Returns: (transfer full) (type GObject.Object): a #GObject or %NULL
|
||
|
* and @error may be set.
|
||
|
*
|
||
|
* Since: 3.32
|
||
|
*/
|
||
|
gpointer
|
||
|
ide_task_propagate_object (IdeTask *self,
|
||
|
GError **error)
|
||
|
{
|
||
|
IdeTaskPrivate *priv = ide_task_get_instance_private (self);
|
||
|
g_autoptr(GMutexLocker) locker = NULL;
|
||
|
g_autoptr(IdeTaskResult) res = NULL;
|
||
|
|
||
|
g_return_val_if_fail (IDE_IS_TASK (self), NULL);
|
||
|
|
||
|
locker = g_mutex_locker_new (&priv->mutex);
|
||
|
|
||
|
if (!(res = ide_task_propagate_locked (self, IDE_TASK_RESULT_OBJECT, error)))
|
||
|
return NULL;
|
||
|
|
||
|
return g_steal_pointer (&res->u.v_object);
|
||
|
}
|
||
|
|
||
|
gpointer
|
||
|
ide_task_propagate_pointer (IdeTask *self,
|
||
|
GError **error)
|
||
|
{
|
||
|
IdeTaskPrivate *priv = ide_task_get_instance_private (self);
|
||
|
g_autoptr(GMutexLocker) locker = NULL;
|
||
|
g_autoptr(IdeTaskResult) res = NULL;
|
||
|
gpointer ret;
|
||
|
|
||
|
g_return_val_if_fail (IDE_IS_TASK (self), NULL);
|
||
|
|
||
|
locker = g_mutex_locker_new (&priv->mutex);
|
||
|
|
||
|
if (!(res = ide_task_propagate_locked (self, IDE_TASK_RESULT_POINTER, error)))
|
||
|
return NULL;
|
||
|
|
||
|
ret = g_steal_pointer (&res->u.v_pointer.pointer);
|
||
|
res->u.v_pointer.destroy = NULL;
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* ide_task_chain:
|
||
|
* @self: a #IdeTask
|
||
|
* @other_task: a #IdeTask
|
||
|
*
|
||
|
* Causes the result of @self to also be delivered to @other_task.
|
||
|
*
|
||
|
* This API is useful in situations when you want to avoid doing the same
|
||
|
* work multiple times, and can share the result between mutliple async
|
||
|
* operations requesting the same work.
|
||
|
*
|
||
|
* Users of this API must make sure one of two things is true. Either they
|
||
|
* have called ide_task_set_release_on_propagate() with @self and set
|
||
|
* release_on_propagate to %FALSE, or @self has not yet completed.
|
||
|
*
|
||
|
* Since: 3.32
|
||
|
*/
|
||
|
void
|
||
|
ide_task_chain (IdeTask *self,
|
||
|
IdeTask *other_task)
|
||
|
{
|
||
|
IdeTaskPrivate *self_priv = ide_task_get_instance_private (self);
|
||
|
|
||
|
g_return_if_fail (IDE_IS_TASK (self));
|
||
|
g_return_if_fail (IDE_IS_TASK (other_task));
|
||
|
g_return_if_fail (self != other_task);
|
||
|
|
||
|
g_mutex_lock (&self_priv->mutex);
|
||
|
|
||
|
if (self_priv->result)
|
||
|
{
|
||
|
IdeTaskResult *copy = ide_task_result_copy (self_priv->result);
|
||
|
|
||
|
if (copy != NULL)
|
||
|
ide_task_deliver_result (other_task, g_steal_pointer (©));
|
||
|
else
|
||
|
ide_task_return_new_error (other_task,
|
||
|
G_IO_ERROR,
|
||
|
G_IO_ERROR_FAILED,
|
||
|
"Result could not be copied to task");
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (self_priv->chained == NULL)
|
||
|
self_priv->chained = g_ptr_array_new_with_free_func (g_object_unref);
|
||
|
g_ptr_array_add (self_priv->chained, g_object_ref (other_task));
|
||
|
}
|
||
|
|
||
|
g_mutex_unlock (&self_priv->mutex);
|
||
|
}
|
||
|
|
||
|
gpointer
|
||
|
ide_task_get_source_tag (IdeTask *self)
|
||
|
{
|
||
|
IdeTaskPrivate *priv = ide_task_get_instance_private (self);
|
||
|
gpointer ret;
|
||
|
|
||
|
g_return_val_if_fail (IDE_IS_TASK (self), NULL);
|
||
|
|
||
|
g_mutex_lock (&priv->mutex);
|
||
|
ret = priv->source_tag;
|
||
|
g_mutex_unlock (&priv->mutex);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
IdeTaskKind
|
||
|
ide_task_get_kind (IdeTask *self)
|
||
|
{
|
||
|
IdeTaskPrivate *priv = ide_task_get_instance_private (self);
|
||
|
IdeTaskKind kind;
|
||
|
|
||
|
g_return_val_if_fail (IDE_IS_TASK (self), 0);
|
||
|
|
||
|
g_mutex_lock (&priv->mutex);
|
||
|
kind = priv->kind;
|
||
|
g_mutex_unlock (&priv->mutex);
|
||
|
|
||
|
return kind;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
ide_task_set_kind (IdeTask *self,
|
||
|
IdeTaskKind kind)
|
||
|
{
|
||
|
IdeTaskPrivate *priv = ide_task_get_instance_private (self);
|
||
|
|
||
|
g_return_if_fail (IDE_IS_TASK (self));
|
||
|
g_return_if_fail (kind >= IDE_TASK_KIND_DEFAULT);
|
||
|
g_return_if_fail (kind < IDE_TASK_KIND_LAST);
|
||
|
|
||
|
g_mutex_lock (&priv->mutex);
|
||
|
priv->kind = kind;
|
||
|
g_mutex_unlock (&priv->mutex);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* ide_task_get_task_data: (skip)
|
||
|
* @self: a #IdeTask
|
||
|
*
|
||
|
* Gets the task data previously set with ide_task_set_task_data().
|
||
|
*
|
||
|
* Returns: (transfer none): previously registered task data or %NULL
|
||
|
*
|
||
|
* Since: 3.32
|
||
|
*/
|
||
|
gpointer
|
||
|
ide_task_get_task_data (IdeTask *self)
|
||
|
{
|
||
|
IdeTaskPrivate *priv = ide_task_get_instance_private (self);
|
||
|
gpointer task_data = NULL;
|
||
|
|
||
|
g_assert (IDE_IS_TASK (self));
|
||
|
|
||
|
g_mutex_lock (&priv->mutex);
|
||
|
if (priv->task_data)
|
||
|
task_data = priv->task_data->data;
|
||
|
g_mutex_unlock (&priv->mutex);
|
||
|
|
||
|
return task_data;
|
||
|
}
|
||
|
|
||
|
static gboolean
|
||
|
ide_task_set_task_data_cb (gpointer data)
|
||
|
{
|
||
|
IdeTaskData *task_data = data;
|
||
|
ide_task_data_free (task_data);
|
||
|
return G_SOURCE_REMOVE;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
(ide_task_set_task_data) (IdeTask *self,
|
||
|
gpointer task_data,
|
||
|
GDestroyNotify task_data_destroy)
|
||
|
{
|
||
|
IdeTaskPrivate *priv = ide_task_get_instance_private (self);
|
||
|
g_autoptr(IdeTaskData) old_task_data = NULL;
|
||
|
g_autoptr(IdeTaskData) new_task_data = NULL;
|
||
|
|
||
|
g_return_if_fail (IDE_IS_TASK (self));
|
||
|
|
||
|
new_task_data = g_slice_new0 (IdeTaskData);
|
||
|
new_task_data->data = task_data;
|
||
|
new_task_data->data_destroy = task_data_destroy;
|
||
|
|
||
|
g_mutex_lock (&priv->mutex);
|
||
|
|
||
|
if (priv->return_called)
|
||
|
{
|
||
|
g_critical ("Cannot set task data after returning value");
|
||
|
goto unlock;
|
||
|
}
|
||
|
|
||
|
old_task_data = g_steal_pointer (&priv->task_data);
|
||
|
priv->task_data = g_steal_pointer (&new_task_data);
|
||
|
|
||
|
if (priv->thread_called && old_task_data)
|
||
|
{
|
||
|
GSource *source;
|
||
|
|
||
|
source = g_idle_source_new ();
|
||
|
g_source_set_name (source, "[ide-task] finalize task data");
|
||
|
g_source_set_ready_time (source, -1);
|
||
|
g_source_set_callback (source,
|
||
|
ide_task_set_task_data_cb,
|
||
|
NULL, NULL);
|
||
|
g_source_set_priority (source, priv->priority);
|
||
|
g_source_attach (source, priv->main_context);
|
||
|
g_source_unref (source);
|
||
|
}
|
||
|
|
||
|
unlock:
|
||
|
g_mutex_unlock (&priv->mutex);
|
||
|
}
|
||
|
|
||
|
static gboolean
|
||
|
ide_task_cancel_cb (gpointer user_data)
|
||
|
{
|
||
|
IdeTask *self = user_data;
|
||
|
IdeTaskResult *ret;
|
||
|
|
||
|
g_assert (IDE_IS_TASK (self));
|
||
|
|
||
|
ret = g_slice_new0 (IdeTaskResult);
|
||
|
ret->type = IDE_TASK_RESULT_CANCELLED;
|
||
|
|
||
|
ide_task_return (self, g_steal_pointer (&ret));
|
||
|
|
||
|
return G_SOURCE_REMOVE;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_task_cancellable_cancelled_cb (GCancellable *cancellable,
|
||
|
IdeTaskCancel *cancel)
|
||
|
{
|
||
|
GSource *source;
|
||
|
|
||
|
g_assert (G_IS_CANCELLABLE (cancellable));
|
||
|
g_assert (cancel != NULL);
|
||
|
g_assert (IDE_IS_TASK (cancel->task));
|
||
|
g_assert (cancel->main_context != NULL);
|
||
|
|
||
|
/*
|
||
|
* This can be called synchronously from g_cancellable_connect(), which
|
||
|
* could still be holding priv->mutex. So we need to queue the cancellation
|
||
|
* request back through the main context.
|
||
|
*/
|
||
|
|
||
|
source = g_idle_source_new ();
|
||
|
g_source_set_name (source, "[ide-task] cancel task");
|
||
|
g_source_set_ready_time (source, -1);
|
||
|
g_source_set_callback (source, ide_task_cancel_cb, g_object_ref (cancel->task), g_object_unref);
|
||
|
g_source_set_priority (source, cancel->priority);
|
||
|
g_source_attach (source, cancel->main_context);
|
||
|
g_source_unref (source);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* ide_task_get_return_on_cancel:
|
||
|
* @self: a #IdeTask
|
||
|
*
|
||
|
* Gets the return_on_cancel value, which means the task will return
|
||
|
* immediately when the #GCancellable is cancelled.
|
||
|
*
|
||
|
* Since: 3.32
|
||
|
*/
|
||
|
gboolean
|
||
|
ide_task_get_return_on_cancel (IdeTask *self)
|
||
|
{
|
||
|
IdeTaskPrivate *priv = ide_task_get_instance_private (self);
|
||
|
gboolean ret;
|
||
|
|
||
|
g_return_val_if_fail (IDE_IS_TASK (self), FALSE);
|
||
|
|
||
|
g_mutex_lock (&priv->mutex);
|
||
|
ret = priv->return_on_cancel;
|
||
|
g_mutex_unlock (&priv->mutex);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* ide_task_set_return_on_cancel:
|
||
|
* @self: a #IdeTask
|
||
|
* @return_on_cancel: if the task should return immediately when the
|
||
|
* #GCancellable has been cancelled.
|
||
|
*
|
||
|
* Setting @return_on_cancel to %TRUE ensures that the task will cancel
|
||
|
* immediately when #GCancellable::cancelled is emitted by the configured
|
||
|
* cancellable.
|
||
|
*
|
||
|
* Setting this requires that the caller can ensure the configured #GMainContext
|
||
|
* will outlive the threaded worker so that task state can be freed in a delayed
|
||
|
* fashion.
|
||
|
*
|
||
|
* Since: 3.32
|
||
|
*/
|
||
|
void
|
||
|
ide_task_set_return_on_cancel (IdeTask *self,
|
||
|
gboolean return_on_cancel)
|
||
|
{
|
||
|
IdeTaskPrivate *priv = ide_task_get_instance_private (self);
|
||
|
g_autoptr(GMutexLocker) locker = NULL;
|
||
|
|
||
|
g_return_if_fail (IDE_IS_TASK (self));
|
||
|
|
||
|
locker = g_mutex_locker_new (&priv->mutex);
|
||
|
|
||
|
if (priv->cancellable == NULL)
|
||
|
return;
|
||
|
|
||
|
return_on_cancel = !!return_on_cancel;
|
||
|
|
||
|
if (priv->return_on_cancel != return_on_cancel)
|
||
|
{
|
||
|
priv->return_on_cancel = return_on_cancel;
|
||
|
|
||
|
if (return_on_cancel)
|
||
|
{
|
||
|
IdeTaskCancel *cancel;
|
||
|
|
||
|
/* This creates a reference cycle, but it gets destroyed when the
|
||
|
* appropriate ide_task_return() API is called.
|
||
|
*/
|
||
|
cancel = g_slice_new0 (IdeTaskCancel);
|
||
|
cancel->main_context = g_main_context_ref (priv->main_context);
|
||
|
cancel->task = g_object_ref (self);
|
||
|
cancel->priority = priv->priority;
|
||
|
|
||
|
priv->cancel_handler =
|
||
|
g_cancellable_connect (priv->cancellable,
|
||
|
G_CALLBACK (ide_task_cancellable_cancelled_cb),
|
||
|
g_steal_pointer (&cancel),
|
||
|
(GDestroyNotify)ide_task_cancel_free);
|
||
|
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (priv->cancel_handler)
|
||
|
{
|
||
|
g_cancellable_disconnect (priv->cancellable, priv->cancel_handler);
|
||
|
priv->cancel_handler = 0;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void
|
||
|
ide_task_report_new_error (gpointer source_object,
|
||
|
GAsyncReadyCallback callback,
|
||
|
gpointer callback_data,
|
||
|
gpointer source_tag,
|
||
|
GQuark domain,
|
||
|
gint code,
|
||
|
const gchar *format,
|
||
|
...)
|
||
|
{
|
||
|
g_autoptr(IdeTask) task = NULL;
|
||
|
GError *error;
|
||
|
va_list args;
|
||
|
|
||
|
va_start (args, format);
|
||
|
error = g_error_new_valist (domain, code, format, args);
|
||
|
va_end (args);
|
||
|
|
||
|
task = ide_task_new (source_object, NULL, callback, callback_data);
|
||
|
ide_task_set_source_tag (task, source_tag);
|
||
|
ide_task_return_error (task, g_steal_pointer (&error));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* ide_task_get_name:
|
||
|
* @self: a #IdeTask
|
||
|
*
|
||
|
* Gets the name assigned for the task.
|
||
|
*
|
||
|
* Returns: (nullable): a string or %NULL
|
||
|
*
|
||
|
* Since: 3.32
|
||
|
*/
|
||
|
const gchar *
|
||
|
ide_task_get_name (IdeTask *self)
|
||
|
{
|
||
|
IdeTaskPrivate *priv = ide_task_get_instance_private (self);
|
||
|
const gchar *ret;
|
||
|
|
||
|
g_return_val_if_fail (IDE_IS_TASK (self), NULL);
|
||
|
|
||
|
g_mutex_lock (&priv->mutex);
|
||
|
ret = priv->name;
|
||
|
g_mutex_unlock (&priv->mutex);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* ide_task_set_name:
|
||
|
* @self: a #IdeTask
|
||
|
*
|
||
|
* Sets a useful name for the task.
|
||
|
*
|
||
|
* This string is interned, so it is best to avoid dynamic names as
|
||
|
* that can result in lots of unnecessary strings being interned for
|
||
|
* the lifetime of the process.
|
||
|
*
|
||
|
* This name may be used in various g_critical() messages which can
|
||
|
* be useful in troubleshooting.
|
||
|
*
|
||
|
* If using #IdeTask from C, a default name is set using the source
|
||
|
* file name and line number.
|
||
|
*
|
||
|
* Since: 3.32
|
||
|
*/
|
||
|
void
|
||
|
ide_task_set_name (IdeTask *self,
|
||
|
const gchar *name)
|
||
|
{
|
||
|
IdeTaskPrivate *priv = ide_task_get_instance_private (self);
|
||
|
|
||
|
g_return_if_fail (IDE_IS_TASK (self));
|
||
|
|
||
|
name = g_intern_string (name);
|
||
|
|
||
|
g_mutex_lock (&priv->mutex);
|
||
|
priv->name = name;
|
||
|
g_mutex_unlock (&priv->mutex);
|
||
|
|
||
|
#ifdef ENABLE_TIME_CHART
|
||
|
g_message ("TASK-BEGIN: %s", name);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* ide_task_had_error:
|
||
|
* @self: a #IdeTask
|
||
|
*
|
||
|
* Checks to see if the task had an error.
|
||
|
*
|
||
|
* Returns: %TRUE if an error has occurred
|
||
|
*
|
||
|
* Since: 3.32
|
||
|
*/
|
||
|
gboolean
|
||
|
ide_task_had_error (IdeTask *self)
|
||
|
{
|
||
|
IdeTaskPrivate *priv = ide_task_get_instance_private (self);
|
||
|
gboolean ret;
|
||
|
|
||
|
g_return_val_if_fail (IDE_IS_TASK (self), FALSE);
|
||
|
|
||
|
g_mutex_lock (&priv->mutex);
|
||
|
ret = (priv->result != NULL && priv->result->type == IDE_TASK_RESULT_ERROR) ||
|
||
|
(priv->thread_result != NULL && priv->thread_result->type == IDE_TASK_RESULT_ERROR);
|
||
|
g_mutex_unlock (&priv->mutex);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static gpointer
|
||
|
ide_task_get_user_data (GAsyncResult *result)
|
||
|
{
|
||
|
IdeTask *self = (IdeTask *)result;
|
||
|
IdeTaskPrivate *priv = ide_task_get_instance_private (self);
|
||
|
|
||
|
g_assert (IDE_IS_TASK (self));
|
||
|
|
||
|
return priv->user_data;
|
||
|
}
|
||
|
|
||
|
static GObject *
|
||
|
ide_task_get_source_object_full (GAsyncResult *result)
|
||
|
{
|
||
|
IdeTask *self = (IdeTask *)result;
|
||
|
IdeTaskPrivate *priv = ide_task_get_instance_private (self);
|
||
|
|
||
|
g_assert (IDE_IS_TASK (self));
|
||
|
|
||
|
return priv->source_object ? g_object_ref (priv->source_object) : NULL;
|
||
|
}
|
||
|
|
||
|
static gboolean
|
||
|
ide_task_is_tagged (GAsyncResult *result,
|
||
|
gpointer source_tag)
|
||
|
{
|
||
|
IdeTask *self = (IdeTask *)result;
|
||
|
IdeTaskPrivate *priv = ide_task_get_instance_private (self);
|
||
|
|
||
|
g_assert (IDE_IS_TASK (self));
|
||
|
|
||
|
return source_tag == priv->source_tag;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
async_result_init_iface (GAsyncResultIface *iface)
|
||
|
{
|
||
|
iface->get_user_data = ide_task_get_user_data;
|
||
|
iface->get_source_object = ide_task_get_source_object_full;
|
||
|
iface->is_tagged = ide_task_is_tagged;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
_ide_dump_tasks (void)
|
||
|
{
|
||
|
guint i = 0;
|
||
|
|
||
|
G_LOCK (global_task_list);
|
||
|
|
||
|
for (const GList *iter = global_task_list.head; iter; iter = iter->next)
|
||
|
{
|
||
|
IdeTask *self = iter->data;
|
||
|
IdeTaskPrivate *priv = ide_task_get_instance_private (self);
|
||
|
|
||
|
g_printerr ("[%02d]: %s %s\n", i++, priv->name,
|
||
|
priv->completed ? "completed" : "");
|
||
|
}
|
||
|
|
||
|
G_UNLOCK (global_task_list);
|
||
|
}
|