1 /* ide-task.c
2  *
3  * Copyright 2018 Christian Hergert
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18  */
19 
20 #define G_LOG_DOMAIN "ide-task"
21 
22 #include "config.h"
23 
24 #include <libide-core.h>
25 
26 #include "ide-task.h"
27 #include "ide-thread-pool.h"
28 #include "ide-thread-private.h"
29 
30 /* From GDK_PRIORITY_REDRAW */
31 #define PRIORITY_REDRAW (G_PRIORITY_HIGH_IDLE + 20)
32 
33 #if 0
34 # define ENABLE_TIME_CHART
35 #endif
36 
37 /**
38  * SECTION:ide-task
39  * @title: IdeTask
40  * @short_description: asynchronous task management
41  *
42  * #IdeTask is meant to be an improved form of #GTask. There are a few
43  * deficiencies in #GTask that have made it unsuitable for certain use cases.
44  *
45  * #GTask does not provide a way to guarantee that the source object,
46  * task data, and unused results are freed with in a given #GMainContext.
47  * #IdeTask addresses this by having a more flexible result and object
48  * ownership control.
49  *
50  * Furthermore, #IdeTask allows consumers to force disposal from a given
51  * thread so that the data is released there.
52  *
53  * #IdeTask also supports chaining tasks together which makes it simpler
54  * to avoid doing duplicate work by instead simply chaining the tasks.
55  *
56  * There are some costs to this design. It uses the main context a bit
57  * more than #GTask may use it.
58  *
59  * #IdeTask allows setting a task kind which determines which thread pool
60  * the task will be executed (and throttled) on.
61  *
62  * Because #IdeTask needs more control over result life-cycles (for chaining
63  * results), additional return methods have been provided. Consumers should
64  * use ide_task_return_boxed() when working with boxed types as it allows us
65  * to copy the result to another task. Additionally, ide_task_return_object()
66  * provides a simplified API over ide_task_return_pointer() which also allows
67  * copying the result to chained tasks.
68  *
69  * Since: 3.32
70  */
71 
72 typedef struct
73 {
74   /*
75    * The pointer we were provided.
76    */
77   gpointer data;
78 
79   /*
80    * The destroy notify for @data. We should only call this from the
81    * main context associated with the task.
82    */
83   GDestroyNotify data_destroy;
84 } IdeTaskData;
85 
86 typedef enum
87 {
88   IDE_TASK_RESULT_NONE,
89   IDE_TASK_RESULT_CANCELLED,
90   IDE_TASK_RESULT_BOOLEAN,
91   IDE_TASK_RESULT_INT,
92   IDE_TASK_RESULT_ERROR,
93   IDE_TASK_RESULT_OBJECT,
94   IDE_TASK_RESULT_BOXED,
95   IDE_TASK_RESULT_POINTER,
96 } IdeTaskResultType;
97 
98 typedef struct
99 {
100   /*
101    * The type of result stored in our union @u.
102    */
103   IdeTaskResultType type;
104 
105   /*
106    * To ensure that we can pass ownership back to the main context
107    * from our worker thread, we need to be able to stash the reference
108    * here in our result. It is also convenient as we need access to it
109    * from the main context callback anyway.
110    */
111   IdeTask *task;
112 
113   /*
114    * Additionally, we need to allow passing our main context reference
115    * back so that it cannot be finalized in our thread.
116    */
117   GMainContext *main_context;
118 
119   /*
120    * Priority for our GSource attached to @main_context.
121    */
122   gint complete_priority;
123 
124   /*
125    * The actual result information, broken down by result @type.
126    */
127   union {
128     gboolean  v_bool;
129     gssize    v_int;
130     GError   *v_error;
131     GObject  *v_object;
132     struct {
133       GType    type;
134       gpointer pointer;
135     } v_boxed;
136     struct {
137       gpointer       pointer;
138       GDestroyNotify destroy;
139     } v_pointer;
140   } u;
141 } IdeTaskResult;
142 
143 typedef struct
144 {
145   IdeTask      *task;
146   GMainContext *main_context;
147   gint          priority;
148 } IdeTaskCancel;
149 
150 typedef struct
151 {
152   /*
153    * @global_link is used to store a pointer to the task in the global
154    * queue during the lifetime of the task. This is a debugging feature
155    * so that we can dump the list of active tasks from the debugger.
156    */
157   GList global_link;
158 
159   /*
160    * Controls access to our private data. We only access structure
161    * data while holding this mutex to ensure that we have consistency
162    * between threads which could be accessing internals.
163    */
164   GMutex mutex;
165 
166   /*
167    * The source object for the GAsyncResult interface. If we have set
168    * release_on_propagate, this will be released when the task propagate
169    * function is called.
170    */
171   gpointer source_object;
172 
173   /*
174    * The cancellable that we're monitoring for task cancellation.
175    */
176   GCancellable *cancellable;
177 
178   /*
179    * If ide_task_set_return_on_cancel() has been set, then we might be
180    * listening for changes. Handling this will queue a completion
181    */
182   gulong cancel_handler;
183 
184   /*
185    * The callback to execute upon completion of the operation. It will
186    * be called from @main_contect after the operation completes.
187    */
188   GAsyncReadyCallback callback;
189   gpointer user_data;
190 
191   /*
192    * The name for the task. This string is interned so you should not
193    * use dynamic names. They are meant to simplify the process of
194    * debugging what task failed.
195    */
196   const gchar *name;
197 
198   /*
199    * The GMainContext that was the thread default when the task was
200    * created. Most operations are proxied back to this context so that
201    * the consumer does not need to worry about thread safety.
202    */
203   GMainContext *main_context;
204 
205   /*
206    * The task data that has been set for the task. Task data is released
207    * from a callback in the #GMainContext if changed outside the main
208    * context.
209    */
210   IdeTaskData *task_data;
211 
212   /*
213    * The result for the task. If release_on_propagate as set to %FALSE,
214    * then this may be kept around so that ide_task_chain() can be used to
215    * duplicate the result to another task. This is convenient when multiple
216    * async funcs race to do some work, allowing just a single winner with all
217    * the callers getting the same result.
218    */
219   IdeTaskResult *result;
220 
221   /*
222    * ide_task_chain() allows us to propagate the result of this task to
223    * another task (for a limited number of result types). This is the
224    * list of those tasks.
225    */
226   GPtrArray *chained;
227 
228   /*
229    * If ide_task_run_in_thread() is called, this will be set to the func
230    * that should be called from within the thread.
231    */
232   IdeTaskThreadFunc thread_func;
233 
234   /*
235    * If we're running in a thread, we'll stash the value here until we
236    * can complete things cleanly and pass ownership back as one operation.
237    */
238   IdeTaskResult *thread_result;
239 
240   /*
241    * The source tag for the task, which can be used to determine what
242    * the task is from a debugger as well as to verify correctness
243    * in async finish functions.
244    */
245   gpointer source_tag;
246 
247 #ifdef ENABLE_TIME_CHART
248   /* The time the task was created */
249   gint64 begin_time;
250 #endif
251 
252   /*
253    * Our priority for scheduling tasks in the particular workqueue.
254    */
255   gint priority;
256 
257   /*
258    * The priority for completing the result back on the main context. This
259    * defaults to a value lower than gtk redraw priority to ensure that gtk
260    * has higher priority than task completion.
261    */
262   gint complete_priority;
263 
264   /*
265    * While we're waiting for our return callback, this is set to our
266    * source id. We use that to know we need to block on the main loop
267    * in case the user calls ide_task_propagate_*() synchronously without
268    * round-triping to the main loop.
269    */
270   guint return_source;
271 
272   /*
273    * Our kind of task, which is used to determine what thread pool we
274    * can use when running threaded work. This can be used to help choke
275    * lots of work down to a relatively small number of threads.
276    */
277   IdeTaskKind kind : 8;
278 
279   /*
280    * If the task has been completed, which is to say that the callback
281    * dispatch has occurred in @main_context.
282    */
283   guint completed : 1;
284 
285   /*
286    * If we should check @cancellable before returning the result. If set
287    * to true, and the cancellable was cancelled, an error will be returned
288    * even if the task completed successfully.
289    */
290   guint check_cancellable : 1;
291 
292   /*
293    * If we should synthesize completion from a GCancellable::cancelled
294    * event instead of waiting for the task to complete normally.
295    */
296   guint return_on_cancel : 1;
297 
298   /*
299    * If we should release the source object and task data after we've
300    * dispatched the callback (or the callback was NULL). This allows us
301    * to ensure that various dependent data are released in the main
302    * context. This is the default and helps ensure thread-safety.
303    */
304   guint release_on_propagate : 1;
305 
306   /*
307    * Protect against multiple return calls, and given the developer a good
308    * warning so they catch this early.
309    */
310   guint return_called : 1;
311 
312   /*
313    * If we got a result that was a cancellation, then we mark it here so
314    * that we can deal with it cleanly later.
315    */
316   guint got_cancel : 1;
317 
318   /*
319    * If we have dispatched to a thread already.
320    */
321   guint thread_called : 1;
322 
323 } IdeTaskPrivate;
324 
325 static void     async_result_init_iface (GAsyncResultIface *iface);
326 static void     ide_task_data_free      (IdeTaskData       *task_data);
327 static void     ide_task_result_free    (IdeTaskResult     *result);
328 static gboolean ide_task_return_cb      (gpointer           user_data);
329 static void     ide_task_release        (IdeTask           *self,
330                                          gboolean           force);
331 
332 G_DEFINE_AUTOPTR_CLEANUP_FUNC (IdeTaskData, ide_task_data_free);
333 G_DEFINE_AUTOPTR_CLEANUP_FUNC (IdeTaskResult, ide_task_result_free);
334 
335 G_DEFINE_TYPE_WITH_CODE (IdeTask, ide_task, G_TYPE_OBJECT,
336                          G_ADD_PRIVATE (IdeTask)
337                          G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_RESULT, async_result_init_iface))
338 
339 enum {
340   PROP_0,
341   PROP_COMPLETED,
342   N_PROPS
343 };
344 
345 static GParamSpec *properties [N_PROPS];
346 static GQueue global_task_list = G_QUEUE_INIT;
347 
348 G_LOCK_DEFINE (global_task_list);
349 
350 static void
ide_task_cancel_free(IdeTaskCancel * cancel)351 ide_task_cancel_free (IdeTaskCancel *cancel)
352 {
353   g_clear_pointer (&cancel->main_context, g_main_context_unref);
354   g_clear_object (&cancel->task);
355   g_slice_free (IdeTaskCancel, cancel);
356 }
357 
358 static const gchar *
result_type_name(IdeTaskResultType type)359 result_type_name (IdeTaskResultType type)
360 {
361   switch (type)
362     {
363     case IDE_TASK_RESULT_NONE:
364       return "none";
365 
366     case IDE_TASK_RESULT_CANCELLED:
367       return "cancelled";
368 
369     case IDE_TASK_RESULT_INT:
370       return "int";
371 
372     case IDE_TASK_RESULT_POINTER:
373       return "pointer";
374 
375     case IDE_TASK_RESULT_OBJECT:
376       return "object";
377 
378     case IDE_TASK_RESULT_BOXED:
379       return "boxed";
380 
381     case IDE_TASK_RESULT_BOOLEAN:
382       return "boolean";
383 
384     case IDE_TASK_RESULT_ERROR:
385       return "error";
386 
387     default:
388       return NULL;
389     }
390 }
391 
392 static void
ide_task_data_free(IdeTaskData * task_data)393 ide_task_data_free (IdeTaskData *task_data)
394 {
395   if (task_data->data_destroy != NULL)
396     task_data->data_destroy (task_data->data);
397   g_slice_free (IdeTaskData, task_data);
398 }
399 
400 static IdeTaskResult *
ide_task_result_copy(const IdeTaskResult * src)401 ide_task_result_copy (const IdeTaskResult *src)
402 {
403   IdeTaskResult *dst;
404 
405   dst = g_slice_new0 (IdeTaskResult);
406   dst->type = src->type;
407 
408   switch (src->type)
409     {
410     case IDE_TASK_RESULT_INT:
411       dst->u.v_int = src->u.v_int;
412       break;
413 
414     case IDE_TASK_RESULT_BOOLEAN:
415       dst->u.v_bool = src->u.v_bool;
416       break;
417 
418     case IDE_TASK_RESULT_ERROR:
419       dst->u.v_error = g_error_copy (src->u.v_error);
420       break;
421 
422     case IDE_TASK_RESULT_OBJECT:
423       dst->u.v_object = src->u.v_object ? g_object_ref (src->u.v_object) : NULL;
424       break;
425 
426     case IDE_TASK_RESULT_BOXED:
427       dst->u.v_boxed.type = src->u.v_boxed.type;
428       dst->u.v_boxed.pointer = g_boxed_copy (src->u.v_boxed.type, src->u.v_boxed.pointer);
429       break;
430 
431     case IDE_TASK_RESULT_POINTER:
432       g_critical ("Cannot proxy raw pointers for task results");
433       break;
434 
435     case IDE_TASK_RESULT_CANCELLED:
436     case IDE_TASK_RESULT_NONE:
437     default:
438       break;
439     }
440 
441   return g_steal_pointer (&dst);
442 }
443 
444 static void
ide_task_result_free(IdeTaskResult * result)445 ide_task_result_free (IdeTaskResult *result)
446 {
447   if (result == NULL)
448     return;
449 
450   switch (result->type)
451     {
452     case IDE_TASK_RESULT_POINTER:
453       if (result->u.v_pointer.destroy)
454         result->u.v_pointer.destroy (result->u.v_pointer.pointer);
455       break;
456 
457     case IDE_TASK_RESULT_ERROR:
458       g_error_free (result->u.v_error);
459       break;
460 
461     case IDE_TASK_RESULT_BOXED:
462       if (result->u.v_boxed.pointer)
463         g_boxed_free (result->u.v_boxed.type, result->u.v_boxed.pointer);
464       break;
465 
466     case IDE_TASK_RESULT_OBJECT:
467       g_clear_object (&result->u.v_object);
468       break;
469 
470     case IDE_TASK_RESULT_BOOLEAN:
471     case IDE_TASK_RESULT_INT:
472     case IDE_TASK_RESULT_NONE:
473     case IDE_TASK_RESULT_CANCELLED:
474     default:
475       break;
476     }
477 
478   g_clear_object (&result->task);
479   g_clear_pointer (&result->main_context, g_main_context_unref);
480   g_slice_free (IdeTaskResult, result);
481 }
482 
483 /*
484  * ide_task_complete:
485  * @result: (transfer full): the result to complete
486  *
487  * queues the completion for the task. make sure that you've
488  * set the result->task, main_context, and priority first.
489  *
490  * This is designed to allow stealing the last reference from
491  * a worker thread and pass it back to the main context.
492  *
493  * Returns: a gsource identifier
494  */
495 static guint
ide_task_complete(IdeTaskResult * result)496 ide_task_complete (IdeTaskResult *result)
497 {
498   GSource *source;
499   guint ret;
500 
501   g_assert (result != NULL);
502   g_assert (IDE_IS_TASK (result->task));
503   g_assert (result->main_context);
504 
505   source = g_idle_source_new ();
506   g_source_set_name (source, "[ide-task] complete result");
507   g_source_set_ready_time (source, -1);
508   g_source_set_callback (source, ide_task_return_cb, result, NULL);
509   g_source_set_priority (source, result->complete_priority);
510   ret = g_source_attach (source, result->main_context);
511   g_source_unref (source);
512 
513   return ret;
514 }
515 
516 static void
ide_task_thread_func(gpointer data)517 ide_task_thread_func (gpointer data)
518 {
519   g_autoptr(GObject) source_object = NULL;
520   g_autoptr(GCancellable) cancellable = NULL;
521   g_autoptr(IdeTask) task = data;
522   IdeTaskPrivate *priv = ide_task_get_instance_private (task);
523   gpointer task_data = NULL;
524   IdeTaskThreadFunc thread_func;
525 
526   g_assert (IDE_IS_TASK (task));
527 
528   g_mutex_lock (&priv->mutex);
529   source_object = priv->source_object ? g_object_ref (priv->source_object) : NULL;
530   cancellable = priv->cancellable ? g_object_ref (priv->cancellable) : NULL;
531   if (priv->task_data)
532     task_data = priv->task_data->data;
533   thread_func = priv->thread_func;
534   priv->thread_func = NULL;
535   g_mutex_unlock (&priv->mutex);
536 
537   g_assert (thread_func != NULL);
538 
539   thread_func (task, source_object, task_data, cancellable);
540 
541   g_clear_object (&source_object);
542   g_clear_object (&cancellable);
543 
544   g_mutex_lock (&priv->mutex);
545 
546   /*
547    * We've delayed our ide_task_return() until we reach here, so now
548    * we can steal our object instance and complete the task along with
549    * ensuring the object wont be finalized from this thread.
550    */
551   if (priv->thread_result)
552     {
553       IdeTaskResult *result = g_steal_pointer (&priv->thread_result);
554 
555       g_assert (result->task == task);
556       g_clear_object (&result->task);
557       result->task = g_steal_pointer (&task);
558 
559       priv->return_source = ide_task_complete (g_steal_pointer (&result));
560 
561       g_assert (source_object == NULL);
562       g_assert (cancellable == NULL);
563       g_assert (task == NULL);
564     }
565   else
566     {
567       /* The task did not return a value while in the thread func!  GTask
568        * doesn't support this, but its useful to us in a number of ways, so
569        * we'll begrudgingly support it but the best we can do is drop our
570        * reference from the thread.
571        */
572     }
573 
574   g_mutex_unlock (&priv->mutex);
575 
576   g_assert (source_object == NULL);
577   g_assert (cancellable == NULL);
578 }
579 
580 static void
ide_task_dispose(GObject * object)581 ide_task_dispose (GObject *object)
582 {
583   IdeTask *self = (IdeTask *)object;
584   IdeTaskPrivate *priv = ide_task_get_instance_private (self);
585 
586   g_assert (IDE_IS_TASK (self));
587 
588   ide_task_release (self, TRUE);
589 
590   g_mutex_lock (&priv->mutex);
591   g_clear_pointer (&priv->result, ide_task_result_free);
592   g_mutex_unlock (&priv->mutex);
593 
594   G_OBJECT_CLASS (ide_task_parent_class)->dispose (object);
595 }
596 
597 static void
ide_task_finalize(GObject * object)598 ide_task_finalize (GObject *object)
599 {
600   IdeTask *self = (IdeTask *)object;
601   IdeTaskPrivate *priv = ide_task_get_instance_private (self);
602 
603   G_LOCK (global_task_list);
604   g_queue_unlink (&global_task_list, &priv->global_link);
605   G_UNLOCK (global_task_list);
606 
607   if (!priv->return_called)
608     g_critical ("%s [%s] finalized before completing",
609                 G_OBJECT_TYPE_NAME (self),
610                 priv->name ?: "unnamed");
611   else if (priv->chained && priv->chained->len)
612     g_critical ("%s [%s] finalized before dependents were notified",
613                 G_OBJECT_TYPE_NAME (self),
614                 priv->name ?: "unnamed");
615   else if (priv->thread_func)
616     g_critical ("%s [%s] finalized while thread_func is active",
617                 G_OBJECT_TYPE_NAME (self),
618                 priv->name ?: "unnamed");
619   else if (!priv->completed)
620     g_critical ("%s [%s] finalized before completion",
621                 G_OBJECT_TYPE_NAME (self),
622                 priv->name ?: "unnamed");
623 
624   g_assert (priv->return_source == 0);
625   g_assert (priv->result == NULL);
626   g_assert (priv->task_data == NULL);
627   g_assert (priv->source_object == NULL);
628   g_assert (priv->chained == NULL);
629   g_assert (priv->thread_result == NULL);
630 
631   g_clear_pointer (&priv->main_context, g_main_context_unref);
632   g_clear_object (&priv->cancellable);
633   g_mutex_clear (&priv->mutex);
634 
635   G_OBJECT_CLASS (ide_task_parent_class)->finalize (object);
636 }
637 
638 static void
ide_task_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)639 ide_task_get_property (GObject    *object,
640                        guint       prop_id,
641                        GValue     *value,
642                        GParamSpec *pspec)
643 {
644   IdeTask *self = IDE_TASK (object);
645 
646   switch (prop_id)
647     {
648     case PROP_COMPLETED:
649       g_value_set_boolean (value, ide_task_get_completed (self));
650       break;
651 
652     default:
653       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
654     }
655 }
656 
657 static void
ide_task_class_init(IdeTaskClass * klass)658 ide_task_class_init (IdeTaskClass *klass)
659 {
660   GObjectClass *object_class = G_OBJECT_CLASS (klass);
661 
662   object_class->dispose = ide_task_dispose;
663   object_class->finalize = ide_task_finalize;
664   object_class->get_property = ide_task_get_property;
665 
666   properties [PROP_COMPLETED] =
667     g_param_spec_boolean ("completed",
668                           "Completed",
669                           "If the task has completed",
670                           FALSE,
671                           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
672 
673   g_object_class_install_properties (object_class, N_PROPS, properties);
674 
675   /* This can be called multiple times, so we use this to allow
676    * unit tests to work without having to expose the function as
677    * public API.
678    */
679   _ide_thread_pool_init (FALSE);
680 }
681 
682 static void
ide_task_init(IdeTask * self)683 ide_task_init (IdeTask *self)
684 {
685   IdeTaskPrivate *priv = ide_task_get_instance_private (self);
686 
687   g_mutex_init (&priv->mutex);
688 
689   priv->check_cancellable = TRUE;
690   priv->release_on_propagate = TRUE;
691   priv->priority = G_PRIORITY_DEFAULT;
692   priv->complete_priority = PRIORITY_REDRAW + 1;
693   priv->main_context = g_main_context_ref_thread_default ();
694   priv->global_link.data = self;
695 
696   G_LOCK (global_task_list);
697   g_queue_push_tail_link (&global_task_list, &priv->global_link);
698   G_UNLOCK (global_task_list);
699 }
700 
701 /**
702  * ide_task_get_source_object: (skip)
703  * @self: a #IdeTask
704  *
705  * Gets the #GObject used when creating the source object.
706  *
707  * As this does not provide ownership transfer of the #GObject, it is a
708  * programmer error to call this function outside of a thread worker called
709  * from ide_task_run_in_thread() or outside the #GMainContext that is
710  * associated with the task.
711  *
712  * If you need to access the object in other scenarios, you must use the
713  * g_async_result_get_source_object() which provides a full reference to the
714  * source object, safely. You are responsible for ensuring that you do not
715  * release the object in a manner that is unsafe for the source object.
716  *
717  * Returns: (transfer none) (nullable) (type GObject.Object): a #GObject or %NULL
718  *
719  * Since: 3.32
720  */
721 gpointer
ide_task_get_source_object(IdeTask * self)722 ide_task_get_source_object (IdeTask *self)
723 {
724   IdeTaskPrivate *priv = ide_task_get_instance_private (self);
725   gpointer ret;
726 
727   g_return_val_if_fail (IDE_IS_TASK (self), NULL);
728 
729   g_mutex_lock (&priv->mutex);
730   ret = priv->source_object;
731   g_mutex_unlock (&priv->mutex);
732 
733   return ret;
734 }
735 
736 /**
737  * ide_task_new:
738  * @source_object: (type GObject.Object) (nullable): a #GObject or %NULL
739  * @cancellable: (nullable): a #GCancellable or %NULL
740  * @callback: (scope async) (nullable): a #GAsyncReadyCallback or %NULL
741  * @user_data: closure data for @callback
742  *
743  * Creates a new #IdeTask.
744  *
745  * #IdeTask is similar to #GTask but provides some additional guarantees
746  * such that by default, the source object, task data, and unused results
747  * are guaranteed to be finalized in the #GMainContext associated with
748  * the task itself.
749  *
750  * Returns: (transfer full): an #IdeTask
751  *
752  * Since: 3.32
753  */
754 IdeTask *
755 (ide_task_new) (gpointer             source_object,
756                 GCancellable        *cancellable,
757                 GAsyncReadyCallback  callback,
758                 gpointer             user_data)
759 {
760   g_autoptr(IdeTask) self = NULL;
761   IdeTaskPrivate *priv;
762 
763   g_return_val_if_fail (!source_object || G_IS_OBJECT (source_object), NULL);
764   g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), NULL);
765 
766   self = g_object_new (IDE_TYPE_TASK, NULL);
767   priv = ide_task_get_instance_private (self);
768 
769   priv->source_object = source_object ? g_object_ref (source_object) : NULL;
770   priv->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
771   priv->callback = callback;
772   priv->user_data = user_data;
773 #ifdef ENABLE_TIME_CHART
774   priv->begin_time = g_get_monotonic_time ();
775 #endif
776 
777   return g_steal_pointer (&self);
778 }
779 
780 /**
781  * ide_task_is_valid:
782  * @self: (nullable) (type IdeTask): a #IdeTask
783  * @source_object: (nullable): a #GObject or %NULL
784  *
785  * Checks if @source_object matches the object the task was created with.
786  *
787  * Returns: %TRUE is source_object matches
788  *
789  * Since: 3.32
790  */
791 gboolean
ide_task_is_valid(gpointer self,gpointer source_object)792 ide_task_is_valid (gpointer self,
793                    gpointer source_object)
794 {
795   IdeTaskPrivate *priv = ide_task_get_instance_private (self);
796 
797   return IDE_IS_TASK (self) && priv->source_object == source_object;
798 }
799 
800 /**
801  * ide_task_get_completed:
802  * @self: a #IdeTask
803  *
804  * Gets the "completed" property. This is %TRUE after the callback used when
805  * creating the task has been executed.
806  *
807  * The property will be notified using g_object_notify() exactly once in the
808  * same #GMainContext as the callback.
809  *
810  * Returns: %TRUE if the task has completed
811  *
812  * Since: 3.32
813  */
814 gboolean
ide_task_get_completed(IdeTask * self)815 ide_task_get_completed (IdeTask *self)
816 {
817   IdeTaskPrivate *priv = ide_task_get_instance_private (self);
818   gboolean ret;
819 
820   g_return_val_if_fail (IDE_IS_TASK (self), FALSE);
821 
822   g_mutex_lock (&priv->mutex);
823   ret = priv->completed;
824   g_mutex_unlock (&priv->mutex);
825 
826   return ret;
827 }
828 
829 gint
ide_task_get_priority(IdeTask * self)830 ide_task_get_priority (IdeTask *self)
831 {
832   IdeTaskPrivate *priv = ide_task_get_instance_private (self);
833   gint ret;
834 
835   g_return_val_if_fail (IDE_IS_TASK (self), 0);
836 
837   g_mutex_lock (&priv->mutex);
838   ret = priv->priority;
839   g_mutex_unlock (&priv->mutex);
840 
841   return ret;
842 }
843 
844 void
ide_task_set_priority(IdeTask * self,gint priority)845 ide_task_set_priority (IdeTask *self,
846                        gint     priority)
847 {
848   IdeTaskPrivate *priv = ide_task_get_instance_private (self);
849 
850   g_return_if_fail (IDE_IS_TASK (self));
851 
852   g_mutex_lock (&priv->mutex);
853   priv->priority = priority;
854   g_mutex_unlock (&priv->mutex);
855 }
856 
857 gint
ide_task_get_complete_priority(IdeTask * self)858 ide_task_get_complete_priority (IdeTask *self)
859 {
860   IdeTaskPrivate *priv = ide_task_get_instance_private (self);
861   gint ret;
862 
863   g_return_val_if_fail (IDE_IS_TASK (self), 0);
864 
865   g_mutex_lock (&priv->mutex);
866   ret = priv->complete_priority;
867   g_mutex_unlock (&priv->mutex);
868 
869   return ret;
870 }
871 
872 void
ide_task_set_complete_priority(IdeTask * self,gint complete_priority)873 ide_task_set_complete_priority (IdeTask *self,
874                                 gint     complete_priority)
875 {
876   IdeTaskPrivate *priv = ide_task_get_instance_private (self);
877 
878   g_return_if_fail (IDE_IS_TASK (self));
879 
880   g_mutex_lock (&priv->mutex);
881   priv->complete_priority = complete_priority;
882   g_mutex_unlock (&priv->mutex);
883 }
884 
885 /**
886  * ide_task_get_cancellable:
887  * @self: a #IdeTask
888  *
889  * Gets the #GCancellable for the task.
890  *
891  * Returns: (transfer none) (nullable): a #GCancellable or %NULL
892  *
893  * Since: 3.32
894  */
895 GCancellable *
ide_task_get_cancellable(IdeTask * self)896 ide_task_get_cancellable (IdeTask *self)
897 {
898   IdeTaskPrivate *priv = ide_task_get_instance_private (self);
899 
900   g_return_val_if_fail (IDE_IS_TASK (self), NULL);
901 
902   return priv->cancellable;
903 }
904 
905 static void
ide_task_deliver_result(IdeTask * self,IdeTaskResult * result)906 ide_task_deliver_result (IdeTask       *self,
907                          IdeTaskResult *result)
908 {
909   IdeTaskPrivate *priv = ide_task_get_instance_private (self);
910 
911   g_assert (IDE_IS_TASK (self));
912   g_assert (result != NULL);
913   g_assert (result->task == NULL);
914   g_assert (result->main_context == NULL);
915 
916   /* This task was chained from another task. This completes the result
917    * and we should dispatch the callback. To simplify the dispatching and
918    * help prevent any re-entrancy issues, we defer back to the main context
919    * to complete the operation.
920    */
921 
922   result->task = g_object_ref (self);
923   result->main_context = g_main_context_ref (priv->main_context);
924   result->complete_priority = priv->complete_priority;
925 
926   g_mutex_lock (&priv->mutex);
927 
928   priv->return_called = TRUE;
929   priv->return_source = ide_task_complete (g_steal_pointer (&result));
930 
931   g_mutex_unlock (&priv->mutex);
932 }
933 
934 static void
ide_task_release(IdeTask * self,gboolean force)935 ide_task_release (IdeTask  *self,
936                   gboolean  force)
937 {
938   IdeTaskPrivate *priv = ide_task_get_instance_private (self);
939   g_autoptr(IdeTaskData) task_data = NULL;
940   g_autoptr(GObject) source_object = NULL;
941   g_autoptr(GPtrArray) chained = NULL;
942 
943   g_assert (IDE_IS_TASK (self));
944 
945   g_mutex_lock (&priv->mutex);
946   if (force || priv->release_on_propagate)
947     {
948       source_object = g_steal_pointer (&priv->source_object);
949       task_data = g_steal_pointer (&priv->task_data);
950       chained = g_steal_pointer (&priv->chained);
951     }
952   g_mutex_unlock (&priv->mutex);
953 
954   if (chained)
955     {
956       for (guint i = 0; i < chained->len; i++)
957         {
958           IdeTask *task = g_ptr_array_index (chained, i);
959 
960           ide_task_return_new_error (task,
961                                      G_IO_ERROR,
962                                      G_IO_ERROR_FAILED,
963                                      "Error synthesized for task, parent task disposed");
964         }
965     }
966 }
967 
968 static gboolean
ide_task_return_cb(gpointer user_data)969 ide_task_return_cb (gpointer user_data)
970 {
971   g_autoptr(IdeTask) self = NULL;
972   g_autoptr(IdeTaskResult) result = user_data;
973   g_autoptr(IdeTaskResult) result_copy = NULL;
974   g_autoptr(GCancellable) cancellable = NULL;
975   g_autoptr(GObject) source_object = NULL;
976   g_autoptr(GPtrArray) chained = NULL;
977   GAsyncReadyCallback callback = NULL;
978   gpointer callback_data = NULL;
979   IdeTaskPrivate *priv;
980 
981   g_assert (result != NULL);
982   g_assert (IDE_IS_TASK (result->task));
983 
984   /* We steal the task object, because we only stash it in the result
985    * structure to get it here. And if we held onto it, we would have
986    * a reference cycle.
987    */
988   self = g_steal_pointer (&result->task);
989   priv = ide_task_get_instance_private (self);
990 
991 #ifdef ENABLE_TIME_CHART
992   g_message ("TASK-END: %s: duration=%lf",
993              priv->name,
994              (g_get_monotonic_time () - priv->begin_time) / (gdouble)G_USEC_PER_SEC);
995 #endif
996 
997   g_mutex_lock (&priv->mutex);
998 
999   g_assert (priv->return_source != 0);
1000 
1001   priv->return_source = 0;
1002 
1003   if (priv->got_cancel && priv->result != NULL)
1004     {
1005       /* We can discard this since we already handled a result for the
1006        * task. We delivered this here just so that we could finalize
1007        * any objects back inside them main context.
1008        */
1009       g_mutex_unlock (&priv->mutex);
1010       return G_SOURCE_REMOVE;
1011     }
1012 
1013   g_assert (priv->result == NULL);
1014   g_assert (priv->return_called == TRUE);
1015 
1016   priv->result = g_steal_pointer (&result);
1017 
1018   callback = priv->callback;
1019   callback_data = priv->user_data;
1020 
1021   priv->callback = NULL;
1022   priv->user_data = NULL;
1023 
1024   source_object = priv->source_object ? g_object_ref (priv->source_object) : NULL;
1025   cancellable = priv->cancellable ? g_object_ref (priv->cancellable) : NULL;
1026 
1027   chained = g_steal_pointer (&priv->chained);
1028 
1029   /* Make a private copy of the result data if we're going to need to notify
1030    * other tasks of our result. We can't guarantee the result in @task will
1031    * stay alive during our dispatch callbacks, so we need to have a copy.
1032    */
1033   if (chained != NULL && chained->len > 0)
1034     result_copy = ide_task_result_copy (priv->result);
1035 
1036   g_mutex_unlock (&priv->mutex);
1037 
1038   if (callback)
1039     callback (source_object, G_ASYNC_RESULT (self), callback_data);
1040 
1041   if (chained)
1042     {
1043       for (guint i = 0; i < chained->len; i++)
1044         {
1045           IdeTask *other = g_ptr_array_index (chained, i);
1046           g_autoptr(IdeTaskResult) other_result = ide_task_result_copy (result_copy);
1047 
1048           ide_task_deliver_result (other, g_steal_pointer (&other_result));
1049         }
1050     }
1051 
1052   g_mutex_lock (&priv->mutex);
1053   priv->completed = TRUE;
1054   g_mutex_unlock (&priv->mutex);
1055 
1056   g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_COMPLETED]);
1057 
1058   ide_task_release (self, FALSE);
1059 
1060   return G_SOURCE_REMOVE;
1061 }
1062 
1063 static gboolean
ide_task_return_dummy_cb(gpointer data)1064 ide_task_return_dummy_cb (gpointer data)
1065 {
1066   return G_SOURCE_REMOVE;
1067 }
1068 
1069 static void
ide_task_return(IdeTask * self,IdeTaskResult * result)1070 ide_task_return (IdeTask       *self,
1071                  IdeTaskResult *result)
1072 {
1073   IdeTaskPrivate *priv = ide_task_get_instance_private (self);
1074   g_autoptr(GMutexLocker) locker = NULL;
1075 
1076   g_assert (IDE_IS_TASK (self));
1077   g_assert (result != NULL);
1078   g_assert (result->task == NULL);
1079 
1080   locker = g_mutex_locker_new (&priv->mutex);
1081 
1082   if (priv->cancel_handler && priv->cancellable)
1083     {
1084       g_cancellable_disconnect (priv->cancellable, priv->cancel_handler);
1085       priv->cancel_handler = 0;
1086     }
1087 
1088   if (priv->return_called)
1089     {
1090       GSource *source;
1091 
1092       if (result->type == IDE_TASK_RESULT_CANCELLED)
1093         {
1094           /* We already had a result, and now raced to be notified of
1095            * cancellation. We can safely free this result even if we're
1096            * currently in a worker thread.
1097            */
1098           ide_task_result_free (result);
1099           return;
1100         }
1101 
1102       /* If we haven't been cancelled, then we reached this path multiple
1103        * times by programmer error.
1104        */
1105       if (!priv->got_cancel)
1106         g_critical ("Attempted to set result on task [%s] multiple times", priv->name);
1107 
1108       /*
1109        * This task has already returned, but we need to ensure that we pass
1110        * the data back to the main context so that it is freed appropriately.
1111        */
1112 
1113       source = g_idle_source_new ();
1114       g_source_set_name (source, "[ide-task] finalize task result");
1115       g_source_set_ready_time (source, -1);
1116       g_source_set_callback (source,
1117                              ide_task_return_dummy_cb,
1118                              result,
1119                              (GDestroyNotify)ide_task_result_free);
1120       g_source_attach (source, priv->main_context);
1121       g_source_unref (source);
1122 
1123       return;
1124     }
1125 
1126   priv->return_called = TRUE;
1127 
1128   if (result->type == IDE_TASK_RESULT_CANCELLED)
1129     priv->got_cancel = TRUE;
1130 
1131   result->task = g_object_ref (self);
1132   result->main_context = g_main_context_ref (priv->main_context);
1133   result->complete_priority = priv->complete_priority;
1134 
1135   /* We can queue the result immediately if we're not being called
1136    * while we're inside of a ide_task_run_in_thread() callback. Otherwise,
1137    * that thread cleanup must complete this to ensure objects cannot
1138    * be finalized in that thread.
1139    */
1140   if (!priv->thread_called || IDE_IS_MAIN_THREAD ())
1141     priv->return_source = ide_task_complete (result);
1142   else if (priv->return_on_cancel && result->type == IDE_TASK_RESULT_CANCELLED)
1143     priv->return_source = ide_task_complete (result);
1144   else
1145     priv->thread_result = result;
1146 }
1147 
1148 /**
1149  * ide_task_return_int:
1150  * @self: a #IdeTask
1151  * @result: the result for the task
1152  *
1153  * Sets the result of the task to @result.
1154  *
1155  * Other tasks depending on the result will be notified after returning
1156  * to the #GMainContext of the task.
1157  *
1158  * Since: 3.32
1159  */
1160 void
ide_task_return_int(IdeTask * self,gssize result)1161 ide_task_return_int (IdeTask *self,
1162                      gssize   result)
1163 {
1164   IdeTaskResult *ret;
1165 
1166   g_return_if_fail (IDE_IS_TASK (self));
1167 
1168   ret = g_slice_new0 (IdeTaskResult);
1169   ret->type = IDE_TASK_RESULT_INT;
1170   ret->u.v_int = result;
1171 
1172   ide_task_return (self, g_steal_pointer (&ret));
1173 }
1174 
1175 /**
1176  * ide_task_return_boolean:
1177  * @self: a #IdeTask
1178  * @result: the result for the task
1179  *
1180  * Sets the result of the task to @result.
1181  *
1182  * Other tasks depending on the result will be notified after returning
1183  * to the #GMainContext of the task.
1184  *
1185  * Since: 3.32
1186  */
1187 void
ide_task_return_boolean(IdeTask * self,gboolean result)1188 ide_task_return_boolean (IdeTask  *self,
1189                          gboolean  result)
1190 {
1191   IdeTaskResult *ret;
1192 
1193   g_return_if_fail (IDE_IS_TASK (self));
1194 
1195   ret = g_slice_new0 (IdeTaskResult);
1196   ret->type = IDE_TASK_RESULT_BOOLEAN;
1197   ret->u.v_bool = !!result;
1198 
1199   ide_task_return (self, g_steal_pointer (&ret));
1200 }
1201 
1202 /**
1203  * ide_task_return_boxed: (skip)
1204  * @self: a #IdeTask
1205  * @result_type: the #GType of the boxed type
1206  * @result: (transfer full): the result to be returned
1207  *
1208  * This is similar to ide_task_return_pointer(), but allows the task to
1209  * know the boxed #GType so that the result may be propagated to chained
1210  * tasks.
1211  *
1212  * Since: 3.32
1213  */
1214 void
ide_task_return_boxed(IdeTask * self,GType result_type,gpointer result)1215 ide_task_return_boxed (IdeTask  *self,
1216                        GType     result_type,
1217                        gpointer  result)
1218 {
1219   IdeTaskResult *ret;
1220 
1221   g_return_if_fail (IDE_IS_TASK (self));
1222   g_return_if_fail (result_type != G_TYPE_INVALID);
1223   g_return_if_fail (G_TYPE_IS_BOXED (result_type));
1224 
1225   ret = g_slice_new0 (IdeTaskResult);
1226   ret->type = IDE_TASK_RESULT_BOXED;
1227   ret->u.v_boxed.type = result_type;
1228   ret->u.v_boxed.pointer = result;
1229 
1230   ide_task_return (self, g_steal_pointer (&ret));
1231 }
1232 
1233 /**
1234  * ide_task_return_object:
1235  * @self: a #IdeTask
1236  * @instance: (transfer full) (type GObject.Object): a #GObject instance
1237  *
1238  * Returns a new object instance.
1239  *
1240  * Takes ownership of @instance to allow saving a reference increment and
1241  * decrement by the caller.
1242  *
1243  * Since: 3.32
1244  */
1245 void
ide_task_return_object(IdeTask * self,gpointer instance)1246 ide_task_return_object (IdeTask  *self,
1247                         gpointer  instance)
1248 {
1249   IdeTaskResult *ret;
1250 
1251   g_return_if_fail (IDE_IS_TASK (self));
1252   g_return_if_fail (!instance || G_IS_OBJECT (instance));
1253 
1254   ret = g_slice_new0 (IdeTaskResult);
1255   ret->type = IDE_TASK_RESULT_OBJECT;
1256   ret->u.v_object = instance;
1257 
1258   ide_task_return (self, g_steal_pointer (&ret));
1259 }
1260 
1261 /**
1262  * ide_task_return_pointer: (skip)
1263  * @self: a #IdeTask
1264  * @data: the data to return
1265  * @destroy: an optional #GDestroyNotify to cleanup data if no handler
1266  *   propagates the result
1267  *
1268  * Returns a new raw pointer.
1269  *
1270  * Note that pointers cannot be chained to other tasks, so you may not
1271  * use ide_task_chain() in conjunction with a task returning a pointer
1272  * using ide_task_return_pointer().
1273  *
1274  * If you need task chaining with pointers, see ide_task_return_boxed()
1275  * or ide_task_return_object().
1276  *
1277  * Since: 3.32
1278  */
1279 void
1280 (ide_task_return_pointer) (IdeTask        *self,
1281                            gpointer        data,
1282                            GDestroyNotify  destroy)
1283 {
1284   IdeTaskResult *ret;
1285 
1286   g_return_if_fail (IDE_IS_TASK (self));
1287 
1288   ret = g_slice_new0 (IdeTaskResult);
1289   ret->type = IDE_TASK_RESULT_POINTER;
1290   ret->u.v_pointer.pointer = data;
1291   ret->u.v_pointer.destroy = destroy;
1292 
1293   ide_task_return (self, g_steal_pointer (&ret));
1294 }
1295 
1296 /**
1297  * ide_task_return_error:
1298  * @self: a #IdeTask
1299  * @error: (transfer full): a #GError
1300  *
1301  * Sets @error as the result of the #IdeTask
1302  *
1303  * Since: 3.32
1304  */
1305 void
ide_task_return_error(IdeTask * self,GError * error)1306 ide_task_return_error (IdeTask *self,
1307                        GError  *error)
1308 {
1309   IdeTaskResult *ret;
1310 
1311   g_return_if_fail (IDE_IS_TASK (self));
1312 
1313   ret = g_slice_new0 (IdeTaskResult);
1314   ret->type = IDE_TASK_RESULT_ERROR;
1315   ret->u.v_error = error;
1316 
1317   ide_task_return (self, g_steal_pointer (&ret));
1318 }
1319 
1320 /**
1321  * ide_task_return_new_error:
1322  * @self: a #IdeTask
1323  * @error_domain: the error domain of the #GError
1324  * @error_code: the error code for the #GError
1325  * @format: the printf-style format string
1326  *
1327  * Creates a new #GError and sets it as the result for the task.
1328  *
1329  * Since: 3.32
1330  */
1331 void
ide_task_return_new_error(IdeTask * self,GQuark error_domain,gint error_code,const gchar * format,...)1332 ide_task_return_new_error (IdeTask     *self,
1333                            GQuark       error_domain,
1334                            gint         error_code,
1335                            const gchar *format,
1336                            ...)
1337 {
1338   GError *error;
1339   va_list args;
1340 
1341   g_return_if_fail (IDE_IS_TASK (self));
1342 
1343   va_start (args, format);
1344   error = g_error_new_valist (error_domain, error_code, format, args);
1345   va_end (args);
1346 
1347   ide_task_return_error (self, g_steal_pointer (&error));
1348 }
1349 
1350 /**
1351  * ide_task_return_error_if_cancelled:
1352  * @self: a #IdeTask
1353  *
1354  * Returns a new #GError if the cancellable associated with the task
1355  * has been cancelled. If so, %TRUE is returned, otherwise %FALSE.
1356  *
1357  * If the source object related to the task is an #IdeObject and that
1358  * object has had been requested to destroy, it too will be considered
1359  * a cancellation state.
1360  *
1361  * Returns: %TRUE if the task was cancelled and error returned.
1362  *
1363  * Since: 3.32
1364  */
1365 gboolean
ide_task_return_error_if_cancelled(IdeTask * self)1366 ide_task_return_error_if_cancelled (IdeTask *self)
1367 {
1368   IdeTaskPrivate *priv = ide_task_get_instance_private (self);
1369   gboolean failed;
1370 
1371   g_return_val_if_fail (IDE_IS_TASK (self), FALSE);
1372 
1373   g_mutex_lock (&priv->mutex);
1374   failed = g_cancellable_is_cancelled (priv->cancellable) ||
1375     (IDE_IS_OBJECT (priv->source_object) &&
1376      ide_object_in_destruction (IDE_OBJECT (priv->source_object)));
1377   g_mutex_unlock (&priv->mutex);
1378 
1379   if (failed)
1380     ide_task_return_new_error (self,
1381                                G_IO_ERROR,
1382                                G_IO_ERROR_CANCELLED,
1383                                "The task was cancelled");
1384 
1385   return failed;
1386 }
1387 
1388 /**
1389  * ide_task_set_release_on_propagate:
1390  * @self: a #IdeTask
1391  * @release_on_propagate: if data should be released on propagate
1392  *
1393  * Setting this to %TRUE (the default) ensures that the task will release all
1394  * task data and source_object references after executing the configured
1395  * callback. This is useful to ensure that dependent objects are finalized
1396  * in the thread-default #GMainContext the task was created in.
1397  *
1398  * Generally, you want to leave this as %TRUE to ensure thread-safety on the
1399  * dependent objects and task data.
1400  *
1401  * Since: 3.32
1402  */
1403 void
ide_task_set_release_on_propagate(IdeTask * self,gboolean release_on_propagate)1404 ide_task_set_release_on_propagate (IdeTask  *self,
1405                                    gboolean  release_on_propagate)
1406 {
1407   IdeTaskPrivate *priv = ide_task_get_instance_private (self);
1408 
1409   g_return_if_fail (IDE_IS_TASK (self));
1410 
1411   release_on_propagate = !!release_on_propagate;
1412 
1413   g_mutex_lock (&priv->mutex);
1414   priv->release_on_propagate = release_on_propagate;
1415   g_mutex_unlock (&priv->mutex);
1416 }
1417 
1418 /**
1419  * ide_task_set_source_tag:
1420  * @self: a #IdeTask
1421  * @source_tag: a tag to identify the task, usual a function pointer
1422  *
1423  * Sets the source tag for the task. Generally this is a function pointer
1424  * of the function that created the task.
1425  *
1426  * Since: 3.32
1427  */
1428 void
ide_task_set_source_tag(IdeTask * self,gpointer source_tag)1429 ide_task_set_source_tag (IdeTask  *self,
1430                          gpointer  source_tag)
1431 {
1432   IdeTaskPrivate *priv = ide_task_get_instance_private (self);
1433 
1434   g_return_if_fail (IDE_IS_TASK (self));
1435 
1436   g_mutex_lock (&priv->mutex);
1437   priv->source_tag = source_tag;
1438   g_mutex_unlock (&priv->mutex);
1439 }
1440 
1441 /**
1442  * ide_task_set_check_cancellable:
1443  * @self: a #IdeTask
1444  * @check_cancellable: %TRUE if the cancellable should be checked
1445  *
1446  * Setting @check_cancellable to %TRUE (the default) ensures that the
1447  * #GCancellable used when creating the #IdeTask is checked for cancellation
1448  * before propagating a result. If cancelled, an error will be returned
1449  * instead of the result.
1450  *
1451  * Since: 3.32
1452  */
1453 void
ide_task_set_check_cancellable(IdeTask * self,gboolean check_cancellable)1454 ide_task_set_check_cancellable (IdeTask  *self,
1455                                 gboolean  check_cancellable)
1456 {
1457   IdeTaskPrivate *priv = ide_task_get_instance_private (self);
1458 
1459   g_return_if_fail (IDE_IS_TASK (self));
1460 
1461   check_cancellable = !!check_cancellable;
1462 
1463   g_mutex_lock (&priv->mutex);
1464   priv->check_cancellable = check_cancellable;
1465   g_mutex_unlock (&priv->mutex);
1466 }
1467 
1468 /**
1469  * ide_task_run_in_thread: (skip)
1470  * @self: a #IdeTask
1471  * @thread_func: a function to execute on a worker thread
1472  *
1473  * Scheules @thread_func to be executed on a worker thread.
1474  *
1475  * @thread_func must complete the task from the worker thread using one of
1476  * ide_task_return_boolean(), ide_task_return_int(), or
1477  * ide_task_return_pointer().
1478  *
1479  * Since: 3.32
1480  */
1481 void
ide_task_run_in_thread(IdeTask * self,IdeTaskThreadFunc thread_func)1482 ide_task_run_in_thread (IdeTask           *self,
1483                         IdeTaskThreadFunc  thread_func)
1484 {
1485   IdeTaskPrivate *priv = ide_task_get_instance_private (self);
1486   g_autoptr(GError) error = NULL;
1487 
1488   g_return_if_fail (IDE_IS_TASK (self));
1489   g_return_if_fail (thread_func != NULL);
1490 
1491   g_mutex_lock (&priv->mutex);
1492 
1493   if (priv->completed == TRUE)
1494     {
1495       g_critical ("Task already completed, cannot run in thread");
1496       goto unlock;
1497     }
1498 
1499   if (priv->thread_called)
1500     {
1501       g_critical ("Run in thread already called, cannot run again");
1502       goto unlock;
1503     }
1504 
1505   priv->thread_called = TRUE;
1506   priv->thread_func = thread_func;
1507 
1508   ide_thread_pool_push_with_priority ((IdeThreadPoolKind)priv->kind,
1509                                       priv->priority,
1510                                       ide_task_thread_func,
1511                                       g_object_ref (self));
1512 
1513 unlock:
1514   g_mutex_unlock (&priv->mutex);
1515 
1516   if (error != NULL)
1517     ide_task_return_error (self, g_steal_pointer (&error));
1518 }
1519 
1520 static IdeTaskResult *
ide_task_propagate_locked(IdeTask * self,IdeTaskResultType expected_type,GError ** error)1521 ide_task_propagate_locked (IdeTask            *self,
1522                            IdeTaskResultType   expected_type,
1523                            GError            **error)
1524 {
1525   IdeTaskPrivate *priv = ide_task_get_instance_private (self);
1526   IdeTaskResult *ret = NULL;
1527 
1528   g_assert (IDE_IS_TASK (self));
1529   g_assert (expected_type > IDE_TASK_RESULT_NONE);
1530 
1531   if (priv->result == NULL)
1532     {
1533       g_autoptr(GMainContext) context = g_main_context_ref (priv->main_context);
1534 
1535       while (priv->return_source)
1536         {
1537           g_mutex_unlock (&priv->mutex);
1538           g_main_context_iteration (context, FALSE);
1539           g_mutex_lock (&priv->mutex);
1540         }
1541     }
1542 
1543   if (priv->result == NULL)
1544     {
1545       g_set_error_literal (error,
1546                            G_IO_ERROR,
1547                            G_IO_ERROR_FAILED,
1548                            "No result available for task");
1549     }
1550   else if (priv->result->type == IDE_TASK_RESULT_ERROR)
1551     {
1552       if (error != NULL)
1553         *error = g_error_copy (priv->result->u.v_error);
1554     }
1555   else if ((priv->check_cancellable && g_cancellable_is_cancelled (priv->cancellable)) ||
1556            priv->result->type == IDE_TASK_RESULT_CANCELLED)
1557     {
1558       g_set_error (error,
1559                    G_IO_ERROR,
1560                    G_IO_ERROR_CANCELLED,
1561                    "The operation was cancelled");
1562     }
1563   else if (IDE_IS_OBJECT (priv->source_object) &&
1564            ide_object_in_destruction (IDE_OBJECT (priv->source_object)))
1565     {
1566       g_set_error (error,
1567                    G_IO_ERROR,
1568                    G_IO_ERROR_CANCELLED,
1569                    "The object was destroyed while the task executed");
1570     }
1571   else if (priv->result->type != expected_type)
1572     {
1573       g_set_error (error,
1574                    G_IO_ERROR,
1575                    G_IO_ERROR_FAILED,
1576                    "Task expected result of %s got %s",
1577                    result_type_name (expected_type),
1578                    result_type_name (priv->result->type));
1579     }
1580   else
1581     {
1582       g_assert (priv->result != NULL);
1583       g_assert (priv->result->type == expected_type);
1584 
1585       if (priv->release_on_propagate)
1586         ret = g_steal_pointer (&priv->result);
1587       else if (priv->result->type == IDE_TASK_RESULT_POINTER)
1588         ret = g_steal_pointer (&priv->result);
1589       else
1590         ret = ide_task_result_copy (priv->result);
1591 
1592       g_assert (ret != NULL);
1593     }
1594 
1595   return ret;
1596 }
1597 
1598 gboolean
ide_task_propagate_boolean(IdeTask * self,GError ** error)1599 ide_task_propagate_boolean (IdeTask  *self,
1600                             GError  **error)
1601 {
1602   IdeTaskPrivate *priv = ide_task_get_instance_private (self);
1603   g_autoptr(GMutexLocker) locker = NULL;
1604   g_autoptr(IdeTaskResult) res = NULL;
1605 
1606   g_return_val_if_fail (IDE_IS_TASK (self), FALSE);
1607 
1608   locker = g_mutex_locker_new (&priv->mutex);
1609 
1610   if (!(res = ide_task_propagate_locked (self, IDE_TASK_RESULT_BOOLEAN, error)))
1611     return FALSE;
1612 
1613   return res->u.v_bool;
1614 }
1615 
1616 gpointer
ide_task_propagate_boxed(IdeTask * self,GError ** error)1617 ide_task_propagate_boxed (IdeTask  *self,
1618                           GError  **error)
1619 {
1620   IdeTaskPrivate *priv = ide_task_get_instance_private (self);
1621   g_autoptr(GMutexLocker) locker = NULL;
1622   g_autoptr(IdeTaskResult) res = NULL;
1623 
1624   g_return_val_if_fail (IDE_IS_TASK (self), NULL);
1625 
1626   locker = g_mutex_locker_new (&priv->mutex);
1627 
1628   if (!(res = ide_task_propagate_locked (self, IDE_TASK_RESULT_BOXED, error)))
1629     return NULL;
1630 
1631   return g_boxed_copy (res->u.v_boxed.type, res->u.v_boxed.pointer);
1632 }
1633 
1634 gssize
ide_task_propagate_int(IdeTask * self,GError ** error)1635 ide_task_propagate_int (IdeTask  *self,
1636                         GError  **error)
1637 {
1638   IdeTaskPrivate *priv = ide_task_get_instance_private (self);
1639   g_autoptr(GMutexLocker) locker = NULL;
1640   g_autoptr(IdeTaskResult) res = NULL;
1641 
1642   g_return_val_if_fail (IDE_IS_TASK (self), 0);
1643 
1644   locker = g_mutex_locker_new (&priv->mutex);
1645 
1646   if (!(res = ide_task_propagate_locked (self, IDE_TASK_RESULT_INT, error)))
1647     return 0;
1648 
1649   return res->u.v_int;
1650 }
1651 
1652 /**
1653  * ide_task_propagate_object:
1654  * @self: a #IdeTask
1655  * @error: a location for a #GError, or %NULL
1656  *
1657  * Returns an object if the task completed with an object. Otherwise, %NULL
1658  * is returned.
1659  *
1660  * @error is set if the task completed with an error.
1661  *
1662  * Returns: (transfer full) (type GObject.Object): a #GObject or %NULL
1663  *   and @error may be set.
1664  *
1665  * Since: 3.32
1666  */
1667 gpointer
ide_task_propagate_object(IdeTask * self,GError ** error)1668 ide_task_propagate_object (IdeTask  *self,
1669                            GError  **error)
1670 {
1671   IdeTaskPrivate *priv = ide_task_get_instance_private (self);
1672   g_autoptr(GMutexLocker) locker = NULL;
1673   g_autoptr(IdeTaskResult) res = NULL;
1674 
1675   g_return_val_if_fail (IDE_IS_TASK (self), NULL);
1676 
1677   locker = g_mutex_locker_new (&priv->mutex);
1678 
1679   if (!(res = ide_task_propagate_locked (self, IDE_TASK_RESULT_OBJECT, error)))
1680     return NULL;
1681 
1682   return g_steal_pointer (&res->u.v_object);
1683 }
1684 
1685 gpointer
ide_task_propagate_pointer(IdeTask * self,GError ** error)1686 ide_task_propagate_pointer (IdeTask  *self,
1687                             GError  **error)
1688 {
1689   IdeTaskPrivate *priv = ide_task_get_instance_private (self);
1690   g_autoptr(GMutexLocker) locker = NULL;
1691   g_autoptr(IdeTaskResult) res = NULL;
1692   gpointer ret;
1693 
1694   g_return_val_if_fail (IDE_IS_TASK (self), NULL);
1695 
1696   locker = g_mutex_locker_new (&priv->mutex);
1697 
1698   if (!(res = ide_task_propagate_locked (self, IDE_TASK_RESULT_POINTER, error)))
1699     return NULL;
1700 
1701   ret = g_steal_pointer (&res->u.v_pointer.pointer);
1702   res->u.v_pointer.destroy = NULL;
1703 
1704   return ret;
1705 }
1706 
1707 /**
1708  * ide_task_chain:
1709  * @self: a #IdeTask
1710  * @other_task: a #IdeTask
1711  *
1712  * Causes the result of @self to also be delivered to @other_task.
1713  *
1714  * This API is useful in situations when you want to avoid doing the same
1715  * work multiple times, and can share the result between mutliple async
1716  * operations requesting the same work.
1717  *
1718  * Users of this API must make sure one of two things is true. Either they
1719  * have called ide_task_set_release_on_propagate() with @self and set
1720  * release_on_propagate to %FALSE, or @self has not yet completed.
1721  *
1722  * Since: 3.32
1723  */
1724 void
ide_task_chain(IdeTask * self,IdeTask * other_task)1725 ide_task_chain (IdeTask *self,
1726                 IdeTask *other_task)
1727 {
1728   IdeTaskPrivate *self_priv = ide_task_get_instance_private (self);
1729 
1730   g_return_if_fail (IDE_IS_TASK (self));
1731   g_return_if_fail (IDE_IS_TASK (other_task));
1732   g_return_if_fail (self != other_task);
1733 
1734   g_mutex_lock (&self_priv->mutex);
1735 
1736   if (self_priv->result)
1737     {
1738       IdeTaskResult *copy = ide_task_result_copy (self_priv->result);
1739 
1740       if (copy != NULL)
1741         ide_task_deliver_result (other_task, g_steal_pointer (&copy));
1742       else
1743         ide_task_return_new_error (other_task,
1744                                    G_IO_ERROR,
1745                                    G_IO_ERROR_FAILED,
1746                                    "Result could not be copied to task");
1747     }
1748   else
1749     {
1750       if (self_priv->chained == NULL)
1751         self_priv->chained = g_ptr_array_new_with_free_func (g_object_unref);
1752       g_ptr_array_add (self_priv->chained, g_object_ref (other_task));
1753     }
1754 
1755   g_mutex_unlock (&self_priv->mutex);
1756 }
1757 
1758 gpointer
ide_task_get_source_tag(IdeTask * self)1759 ide_task_get_source_tag (IdeTask *self)
1760 {
1761   IdeTaskPrivate *priv = ide_task_get_instance_private (self);
1762   gpointer ret;
1763 
1764   g_return_val_if_fail (IDE_IS_TASK (self), NULL);
1765 
1766   g_mutex_lock (&priv->mutex);
1767   ret = priv->source_tag;
1768   g_mutex_unlock (&priv->mutex);
1769 
1770   return ret;
1771 }
1772 
1773 IdeTaskKind
ide_task_get_kind(IdeTask * self)1774 ide_task_get_kind (IdeTask *self)
1775 {
1776   IdeTaskPrivate *priv = ide_task_get_instance_private (self);
1777   IdeTaskKind kind;
1778 
1779   g_return_val_if_fail (IDE_IS_TASK (self), 0);
1780 
1781   g_mutex_lock (&priv->mutex);
1782   kind = priv->kind;
1783   g_mutex_unlock (&priv->mutex);
1784 
1785   return kind;
1786 }
1787 
1788 void
ide_task_set_kind(IdeTask * self,IdeTaskKind kind)1789 ide_task_set_kind (IdeTask     *self,
1790                    IdeTaskKind  kind)
1791 {
1792   IdeTaskPrivate *priv = ide_task_get_instance_private (self);
1793 
1794   g_return_if_fail (IDE_IS_TASK (self));
1795   g_return_if_fail (kind >= IDE_TASK_KIND_DEFAULT);
1796   g_return_if_fail (kind < IDE_TASK_KIND_LAST);
1797 
1798   g_mutex_lock (&priv->mutex);
1799   priv->kind = kind;
1800   g_mutex_unlock (&priv->mutex);
1801 }
1802 
1803 /**
1804  * ide_task_get_task_data: (skip)
1805  * @self: a #IdeTask
1806  *
1807  * Gets the task data previously set with ide_task_set_task_data().
1808  *
1809  * Returns: (transfer none): previously registered task data or %NULL
1810  *
1811  * Since: 3.32
1812  */
1813 gpointer
ide_task_get_task_data(IdeTask * self)1814 ide_task_get_task_data (IdeTask *self)
1815 {
1816   IdeTaskPrivate *priv = ide_task_get_instance_private (self);
1817   gpointer task_data = NULL;
1818 
1819   g_assert (IDE_IS_TASK (self));
1820 
1821   g_mutex_lock (&priv->mutex);
1822   if (priv->task_data)
1823     task_data = priv->task_data->data;
1824   g_mutex_unlock (&priv->mutex);
1825 
1826   return task_data;
1827 }
1828 
1829 static gboolean
ide_task_set_task_data_cb(gpointer data)1830 ide_task_set_task_data_cb (gpointer data)
1831 {
1832   IdeTaskData *task_data = data;
1833   ide_task_data_free (task_data);
1834   return G_SOURCE_REMOVE;
1835 }
1836 
1837 void
1838 (ide_task_set_task_data) (IdeTask        *self,
1839                           gpointer        task_data,
1840                           GDestroyNotify  task_data_destroy)
1841 {
1842   IdeTaskPrivate *priv = ide_task_get_instance_private (self);
1843   g_autoptr(IdeTaskData) old_task_data = NULL;
1844   g_autoptr(IdeTaskData) new_task_data = NULL;
1845 
1846   g_return_if_fail (IDE_IS_TASK (self));
1847 
1848   new_task_data = g_slice_new0 (IdeTaskData);
1849   new_task_data->data = task_data;
1850   new_task_data->data_destroy = task_data_destroy;
1851 
1852   g_mutex_lock (&priv->mutex);
1853 
1854   if (priv->return_called)
1855     {
1856       g_critical ("Cannot set task data after returning value");
1857       goto unlock;
1858     }
1859 
1860   old_task_data = g_steal_pointer (&priv->task_data);
1861   priv->task_data = g_steal_pointer (&new_task_data);
1862 
1863   if (priv->thread_called && old_task_data)
1864     {
1865       GSource *source;
1866 
1867       source = g_idle_source_new ();
1868       g_source_set_name (source, "[ide-task] finalize task data");
1869       g_source_set_ready_time (source, -1);
1870       g_source_set_callback (source,
1871                              ide_task_set_task_data_cb,
1872                              NULL, NULL);
1873       g_source_set_priority (source, priv->priority);
1874       g_source_attach (source, priv->main_context);
1875       g_source_unref (source);
1876     }
1877 
1878 unlock:
1879   g_mutex_unlock (&priv->mutex);
1880 }
1881 
1882 static gboolean
ide_task_cancel_cb(gpointer user_data)1883 ide_task_cancel_cb (gpointer user_data)
1884 {
1885   IdeTask *self = user_data;
1886   IdeTaskResult *ret;
1887 
1888   g_assert (IDE_IS_TASK (self));
1889 
1890   ret = g_slice_new0 (IdeTaskResult);
1891   ret->type = IDE_TASK_RESULT_CANCELLED;
1892 
1893   ide_task_return (self, g_steal_pointer (&ret));
1894 
1895   return G_SOURCE_REMOVE;
1896 }
1897 
1898 static void
ide_task_cancellable_cancelled_cb(GCancellable * cancellable,IdeTaskCancel * cancel)1899 ide_task_cancellable_cancelled_cb (GCancellable  *cancellable,
1900                                    IdeTaskCancel *cancel)
1901 {
1902   GSource *source;
1903 
1904   g_assert (G_IS_CANCELLABLE (cancellable));
1905   g_assert (cancel != NULL);
1906   g_assert (IDE_IS_TASK (cancel->task));
1907   g_assert (cancel->main_context != NULL);
1908 
1909   /*
1910    * This can be called synchronously from g_cancellable_connect(), which
1911    * could still be holding priv->mutex. So we need to queue the cancellation
1912    * request back through the main context.
1913    */
1914 
1915   source = g_idle_source_new ();
1916   g_source_set_name (source, "[ide-task] cancel task");
1917   g_source_set_ready_time (source, -1);
1918   g_source_set_callback (source, ide_task_cancel_cb, g_object_ref (cancel->task), g_object_unref);
1919   g_source_set_priority (source, cancel->priority);
1920   g_source_attach (source, cancel->main_context);
1921   g_source_unref (source);
1922 }
1923 
1924 /**
1925  * ide_task_get_return_on_cancel:
1926  * @self: a #IdeTask
1927  *
1928  * Gets the return_on_cancel value, which means the task will return
1929  * immediately when the #GCancellable is cancelled.
1930  *
1931  * Since: 3.32
1932  */
1933 gboolean
ide_task_get_return_on_cancel(IdeTask * self)1934 ide_task_get_return_on_cancel (IdeTask *self)
1935 {
1936   IdeTaskPrivate *priv = ide_task_get_instance_private (self);
1937   gboolean ret;
1938 
1939   g_return_val_if_fail (IDE_IS_TASK (self), FALSE);
1940 
1941   g_mutex_lock (&priv->mutex);
1942   ret = priv->return_on_cancel;
1943   g_mutex_unlock (&priv->mutex);
1944 
1945   return ret;
1946 }
1947 
1948 /**
1949  * ide_task_set_return_on_cancel:
1950  * @self: a #IdeTask
1951  * @return_on_cancel: if the task should return immediately when the
1952  *   #GCancellable has been cancelled.
1953  *
1954  * Setting @return_on_cancel to %TRUE ensures that the task will cancel
1955  * immediately when #GCancellable::cancelled is emitted by the configured
1956  * cancellable.
1957  *
1958  * Setting this requires that the caller can ensure the configured #GMainContext
1959  * will outlive the threaded worker so that task state can be freed in a delayed
1960  * fashion.
1961  *
1962  * Since: 3.32
1963  */
1964 void
ide_task_set_return_on_cancel(IdeTask * self,gboolean return_on_cancel)1965 ide_task_set_return_on_cancel (IdeTask  *self,
1966                                gboolean  return_on_cancel)
1967 {
1968   IdeTaskPrivate *priv = ide_task_get_instance_private (self);
1969   g_autoptr(GMutexLocker) locker = NULL;
1970 
1971   g_return_if_fail (IDE_IS_TASK (self));
1972 
1973   locker = g_mutex_locker_new (&priv->mutex);
1974 
1975   if (priv->cancellable == NULL)
1976     return;
1977 
1978   return_on_cancel = !!return_on_cancel;
1979 
1980   if (priv->return_on_cancel != return_on_cancel)
1981     {
1982       priv->return_on_cancel = return_on_cancel;
1983 
1984       if (return_on_cancel)
1985         {
1986           IdeTaskCancel *cancel;
1987 
1988           /* This creates a reference cycle, but it gets destroyed when the
1989            * appropriate ide_task_return() API is called.
1990            */
1991           cancel = g_slice_new0 (IdeTaskCancel);
1992           cancel->main_context = g_main_context_ref (priv->main_context);
1993           cancel->task = g_object_ref (self);
1994           cancel->priority = priv->priority;
1995 
1996           priv->cancel_handler =
1997             g_cancellable_connect (priv->cancellable,
1998                                    G_CALLBACK (ide_task_cancellable_cancelled_cb),
1999                                    g_steal_pointer (&cancel),
2000                                    (GDestroyNotify)ide_task_cancel_free);
2001 
2002         }
2003       else
2004         {
2005           if (priv->cancel_handler)
2006             {
2007               g_cancellable_disconnect (priv->cancellable, priv->cancel_handler);
2008               priv->cancel_handler = 0;
2009             }
2010         }
2011     }
2012 }
2013 
2014 void
ide_task_report_new_error(gpointer source_object,GAsyncReadyCallback callback,gpointer callback_data,gpointer source_tag,GQuark domain,gint code,const gchar * format,...)2015 ide_task_report_new_error (gpointer              source_object,
2016                            GAsyncReadyCallback   callback,
2017                            gpointer              callback_data,
2018                            gpointer              source_tag,
2019                            GQuark                domain,
2020                            gint                  code,
2021                            const gchar          *format,
2022                            ...)
2023 {
2024   g_autoptr(IdeTask) task = NULL;
2025   GError *error;
2026   va_list args;
2027 
2028   va_start (args, format);
2029   error = g_error_new_valist (domain, code, format, args);
2030   va_end (args);
2031 
2032   task = ide_task_new (source_object, NULL, callback, callback_data);
2033   ide_task_set_source_tag (task, source_tag);
2034   ide_task_return_error (task, g_steal_pointer (&error));
2035 }
2036 
2037 /**
2038  * ide_task_get_name:
2039  * @self: a #IdeTask
2040  *
2041  * Gets the name assigned for the task.
2042  *
2043  * Returns: (nullable): a string or %NULL
2044  *
2045  * Since: 3.32
2046  */
2047 const gchar *
ide_task_get_name(IdeTask * self)2048 ide_task_get_name (IdeTask *self)
2049 {
2050   IdeTaskPrivate *priv = ide_task_get_instance_private (self);
2051   const gchar *ret;
2052 
2053   g_return_val_if_fail (IDE_IS_TASK (self), NULL);
2054 
2055   g_mutex_lock (&priv->mutex);
2056   ret = priv->name;
2057   g_mutex_unlock (&priv->mutex);
2058 
2059   return ret;
2060 }
2061 
2062 /**
2063  * ide_task_set_name:
2064  * @self: a #IdeTask
2065  *
2066  * Sets a useful name for the task.
2067  *
2068  * This string is interned, so it is best to avoid dynamic names as
2069  * that can result in lots of unnecessary strings being interned for
2070  * the lifetime of the process.
2071  *
2072  * This name may be used in various g_critical() messages which can
2073  * be useful in troubleshooting.
2074  *
2075  * If using #IdeTask from C, a default name is set using the source
2076  * file name and line number.
2077  *
2078  * Since: 3.32
2079  */
2080 void
ide_task_set_name(IdeTask * self,const gchar * name)2081 ide_task_set_name (IdeTask *self,
2082                    const gchar *name)
2083 {
2084   IdeTaskPrivate *priv = ide_task_get_instance_private (self);
2085 
2086   g_return_if_fail (IDE_IS_TASK (self));
2087 
2088   name = g_intern_string (name);
2089 
2090   g_mutex_lock (&priv->mutex);
2091   priv->name = name;
2092   g_mutex_unlock (&priv->mutex);
2093 
2094 #ifdef ENABLE_TIME_CHART
2095   g_message ("TASK-BEGIN: %s", name);
2096 #endif
2097 }
2098 
2099 /**
2100  * ide_task_had_error:
2101  * @self: a #IdeTask
2102  *
2103  * Checks to see if the task had an error.
2104  *
2105  * Returns: %TRUE if an error has occurred
2106  *
2107  * Since: 3.32
2108  */
2109 gboolean
ide_task_had_error(IdeTask * self)2110 ide_task_had_error (IdeTask *self)
2111 {
2112   IdeTaskPrivate *priv = ide_task_get_instance_private (self);
2113   gboolean ret;
2114 
2115   g_return_val_if_fail (IDE_IS_TASK (self), FALSE);
2116 
2117   g_mutex_lock (&priv->mutex);
2118   ret = (priv->result != NULL && priv->result->type == IDE_TASK_RESULT_ERROR) ||
2119         (priv->thread_result != NULL && priv->thread_result->type == IDE_TASK_RESULT_ERROR);
2120   g_mutex_unlock (&priv->mutex);
2121 
2122   return ret;
2123 }
2124 
2125 static gpointer
ide_task_get_user_data(GAsyncResult * result)2126 ide_task_get_user_data (GAsyncResult *result)
2127 {
2128   IdeTask *self = (IdeTask *)result;
2129   IdeTaskPrivate *priv = ide_task_get_instance_private (self);
2130 
2131   g_assert (IDE_IS_TASK (self));
2132 
2133   return priv->user_data;
2134 }
2135 
2136 static GObject *
ide_task_get_source_object_full(GAsyncResult * result)2137 ide_task_get_source_object_full (GAsyncResult *result)
2138 {
2139   IdeTask *self = (IdeTask *)result;
2140   IdeTaskPrivate *priv = ide_task_get_instance_private (self);
2141 
2142   g_assert (IDE_IS_TASK (self));
2143 
2144   return priv->source_object ? g_object_ref (priv->source_object) : NULL;
2145 }
2146 
2147 static gboolean
ide_task_is_tagged(GAsyncResult * result,gpointer source_tag)2148 ide_task_is_tagged (GAsyncResult *result,
2149                     gpointer      source_tag)
2150 {
2151   IdeTask *self = (IdeTask *)result;
2152   IdeTaskPrivate *priv = ide_task_get_instance_private (self);
2153 
2154   g_assert (IDE_IS_TASK (self));
2155 
2156   return source_tag == priv->source_tag;
2157 }
2158 
2159 static void
async_result_init_iface(GAsyncResultIface * iface)2160 async_result_init_iface (GAsyncResultIface *iface)
2161 {
2162   iface->get_user_data = ide_task_get_user_data;
2163   iface->get_source_object = ide_task_get_source_object_full;
2164   iface->is_tagged = ide_task_is_tagged;
2165 }
2166 
2167 void
_ide_dump_tasks(void)2168 _ide_dump_tasks (void)
2169 {
2170   guint i = 0;
2171 
2172   G_LOCK (global_task_list);
2173 
2174   for (const GList *iter = global_task_list.head; iter; iter = iter->next)
2175     {
2176       IdeTask *self = iter->data;
2177       IdeTaskPrivate *priv = ide_task_get_instance_private (self);
2178 
2179       g_printerr ("[%02d]: %s %s\n", i++, priv->name,
2180                   priv->completed ? "completed" : "");
2181     }
2182 
2183   G_UNLOCK (global_task_list);
2184 }
2185