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 (©));
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