1 /* GIMP - The GNU Image Manipulation Program
2 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3 *
4 * gimpasync.c
5 * Copyright (C) 2018 Ell
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
21 #include "config.h"
22
23 #include <gdk-pixbuf/gdk-pixbuf.h>
24 #include <gegl.h>
25
26 #include "core-types.h"
27
28 #include "gimpasync.h"
29 #include "gimpcancelable.h"
30 #include "gimpmarshal.h"
31 #include "gimpwaitable.h"
32
33
34 /* GimpAsync represents an asynchronous task. Both the public and the
35 * protected interfaces are intentionally minimal at this point, to keep things
36 * simple. They may be extended in the future as needed.
37 *
38 * GimpAsync implements the GimpWaitable and GimpCancelable interfaces.
39 *
40 * Upon creation, a GimpAsync object is in the "running" state. Once the task
41 * is complete (and before the object's destruction), it should be transitioned
42 * to the "stopped" state, using either 'gimp_async_finish()' or
43 * 'gimp_async_abort()'.
44 *
45 * Similarly, upon creation, a GimpAsync object is said to be "unsynced". It
46 * becomes synced once the execution of any of the completion callbacks added
47 * through 'gimp_async_add_callback()' had started, or after a successful call
48 * to one of the 'gimp_waitable_wait()' family of functions.
49 *
50 * Note that certain GimpAsync functions may only be called during a certain
51 * state, on a certain thread, or depending on whether or not the object is
52 * synced, as detailed for each function. When referring to threads, the "main
53 * thread" is the thread running the main loop, or any thread whose execution
54 * is synchronized with the main thread, and the "async thread" is the thread
55 * calling 'gimp_async_finish()' or 'gimp_async_abort()' (which may also be the
56 * main thread), or any thread whose execution is synchronized with the async
57 * thread.
58 */
59
60
61 /* #define TIME_ASYNC_OPS */
62
63
64 enum
65 {
66 WAITING,
67 LAST_SIGNAL
68 };
69
70
71 typedef struct _GimpAsyncCallbackInfo GimpAsyncCallbackInfo;
72
73
74 struct _GimpAsyncCallbackInfo
75 {
76 GimpAsync *async;
77 GimpAsyncCallback callback;
78 gpointer data;
79 gpointer gobject;
80 };
81
82 struct _GimpAsyncPrivate
83 {
84 GMutex mutex;
85 GCond cond;
86
87 GQueue callbacks;
88
89 gpointer result;
90 GDestroyNotify result_destroy_func;
91
92 guint idle_id;
93
94 gboolean stopped;
95 gboolean finished;
96 gboolean synced;
97 gboolean canceled;
98
99 #ifdef TIME_ASYNC_OPS
100 guint64 start_time;
101 #endif
102 };
103
104
105 /* local function prototypes */
106
107 static void gimp_async_waitable_iface_init (GimpWaitableInterface *iface);
108
109 static void gimp_async_cancelable_iface_init (GimpCancelableInterface *iface);
110
111 static void gimp_async_finalize (GObject *object);
112
113 static void gimp_async_wait (GimpWaitable *waitable);
114 static gboolean gimp_async_try_wait (GimpWaitable *waitable);
115 static gboolean gimp_async_wait_until (GimpWaitable *waitable,
116 gint64 end_time);
117
118 static void gimp_async_cancel (GimpCancelable *cancelable);
119
120 static gboolean gimp_async_idle (GimpAsync *async);
121
122 static void gimp_async_callback_weak_notify (GimpAsyncCallbackInfo *callback_info,
123 GObject *gobject);
124
125 static void gimp_async_stop (GimpAsync *async);
126 static void gimp_async_run_callbacks (GimpAsync *async);
127
128
129 G_DEFINE_TYPE_WITH_CODE (GimpAsync, gimp_async, G_TYPE_OBJECT,
130 G_ADD_PRIVATE (GimpAsync)
131 G_IMPLEMENT_INTERFACE (GIMP_TYPE_WAITABLE,
132 gimp_async_waitable_iface_init)
133 G_IMPLEMENT_INTERFACE (GIMP_TYPE_CANCELABLE,
134 gimp_async_cancelable_iface_init))
135
136 #define parent_class gimp_async_parent_class
137
138 static guint async_signals[LAST_SIGNAL] = { 0 };
139
140
141 /* local variables */
142
143 static volatile gint gimp_async_n_running = 0;
144
145
146 /* private functions */
147
148
149 static void
gimp_async_class_init(GimpAsyncClass * klass)150 gimp_async_class_init (GimpAsyncClass *klass)
151 {
152 GObjectClass *object_class = G_OBJECT_CLASS (klass);
153
154 async_signals[WAITING] =
155 g_signal_new ("waiting",
156 G_TYPE_FROM_CLASS (klass),
157 G_SIGNAL_RUN_FIRST,
158 G_STRUCT_OFFSET (GimpAsyncClass, waiting),
159 NULL, NULL,
160 gimp_marshal_VOID__VOID,
161 G_TYPE_NONE, 0);
162
163 object_class->finalize = gimp_async_finalize;
164 }
165
166 static void
gimp_async_waitable_iface_init(GimpWaitableInterface * iface)167 gimp_async_waitable_iface_init (GimpWaitableInterface *iface)
168 {
169 iface->wait = gimp_async_wait;
170 iface->try_wait = gimp_async_try_wait;
171 iface->wait_until = gimp_async_wait_until;
172 }
173
174 static void
gimp_async_cancelable_iface_init(GimpCancelableInterface * iface)175 gimp_async_cancelable_iface_init (GimpCancelableInterface *iface)
176 {
177 iface->cancel = gimp_async_cancel;
178 }
179
180 static void
gimp_async_init(GimpAsync * async)181 gimp_async_init (GimpAsync *async)
182 {
183 async->priv = gimp_async_get_instance_private (async);
184
185 g_mutex_init (&async->priv->mutex);
186 g_cond_init (&async->priv->cond);
187
188 g_queue_init (&async->priv->callbacks);
189
190 g_atomic_int_inc (&gimp_async_n_running);
191
192 #ifdef TIME_ASYNC_OPS
193 async->priv->start_time = g_get_monotonic_time ();
194 #endif
195 }
196
197 static void
gimp_async_finalize(GObject * object)198 gimp_async_finalize (GObject *object)
199 {
200 GimpAsync *async = GIMP_ASYNC (object);
201
202 g_warn_if_fail (async->priv->stopped);
203 g_warn_if_fail (async->priv->idle_id == 0);
204 g_warn_if_fail (g_queue_is_empty (&async->priv->callbacks));
205
206 if (async->priv->finished &&
207 async->priv->result &&
208 async->priv->result_destroy_func)
209 {
210 async->priv->result_destroy_func (async->priv->result);
211
212 async->priv->result = NULL;
213 }
214
215 g_cond_clear (&async->priv->cond);
216 g_mutex_clear (&async->priv->mutex);
217
218 G_OBJECT_CLASS (parent_class)->finalize (object);
219 }
220
221 /* waits for 'waitable' to transition to the "stopped" state. if 'waitable' is
222 * already stopped, returns immediately.
223 *
224 * after the call, all callbacks previously added through
225 * 'gimp_async_add_callback()' are guaranteed to have been called.
226 *
227 * may only be called on the main thread.
228 */
229 static void
gimp_async_wait(GimpWaitable * waitable)230 gimp_async_wait (GimpWaitable *waitable)
231 {
232 GimpAsync *async = GIMP_ASYNC (waitable);
233
234 g_mutex_lock (&async->priv->mutex);
235
236 if (! async->priv->stopped)
237 {
238 g_signal_emit (async, async_signals[WAITING], 0);
239
240 while (! async->priv->stopped)
241 g_cond_wait (&async->priv->cond, &async->priv->mutex);
242 }
243
244 g_mutex_unlock (&async->priv->mutex);
245
246 gimp_async_run_callbacks (async);
247 }
248
249 /* same as 'gimp_async_wait()', but returns immediately if 'waitable' is not in
250 * the "stopped" state.
251 *
252 * returns TRUE if 'waitable' has transitioned to the "stopped" state, or FALSE
253 * otherwise.
254 */
255 static gboolean
gimp_async_try_wait(GimpWaitable * waitable)256 gimp_async_try_wait (GimpWaitable *waitable)
257 {
258 GimpAsync *async = GIMP_ASYNC (waitable);
259
260 g_mutex_lock (&async->priv->mutex);
261
262 if (! async->priv->stopped)
263 {
264 g_mutex_unlock (&async->priv->mutex);
265
266 return FALSE;
267 }
268
269 g_mutex_unlock (&async->priv->mutex);
270
271 gimp_async_run_callbacks (async);
272
273 return TRUE;
274 }
275
276 /* same as 'gimp_async_wait()', taking an additional 'end_time' parameter,
277 * specifying the maximal monotonic time until which to wait for 'waitable' to
278 * stop.
279 *
280 * returns TRUE if 'waitable' has transitioned to the "stopped" state, or FALSE
281 * if the wait was interrupted before the transition.
282 */
283 static gboolean
gimp_async_wait_until(GimpWaitable * waitable,gint64 end_time)284 gimp_async_wait_until (GimpWaitable *waitable,
285 gint64 end_time)
286 {
287 GimpAsync *async = GIMP_ASYNC (waitable);
288
289 g_mutex_lock (&async->priv->mutex);
290
291 if (! async->priv->stopped)
292 {
293 g_signal_emit (async, async_signals[WAITING], 0);
294
295 while (! async->priv->stopped)
296 {
297 if (! g_cond_wait_until (&async->priv->cond, &async->priv->mutex,
298 end_time))
299 {
300 g_mutex_unlock (&async->priv->mutex);
301
302 return FALSE;
303 }
304 }
305 }
306
307 g_mutex_unlock (&async->priv->mutex);
308
309 gimp_async_run_callbacks (async);
310
311 return TRUE;
312 }
313
314 /* requests the cancellation of the task managed by 'cancelable'.
315 *
316 * note that 'gimp_async_cancel()' doesn't directly cause 'cancelable' to be
317 * stopped, nor synced. furthermore, 'cancelable' may still complete
318 * successfully even when cancellation has been requested.
319 *
320 * may only be called on the main thread.
321 */
322 static void
gimp_async_cancel(GimpCancelable * cancelable)323 gimp_async_cancel (GimpCancelable *cancelable)
324 {
325 GimpAsync *async = GIMP_ASYNC (cancelable);
326
327 async->priv->canceled = TRUE;
328 }
329
330 static gboolean
gimp_async_idle(GimpAsync * async)331 gimp_async_idle (GimpAsync *async)
332 {
333 gimp_waitable_wait (GIMP_WAITABLE (async));
334
335 return G_SOURCE_REMOVE;
336 }
337
338 static void
gimp_async_callback_weak_notify(GimpAsyncCallbackInfo * callback_info,GObject * gobject)339 gimp_async_callback_weak_notify (GimpAsyncCallbackInfo *callback_info,
340 GObject *gobject)
341 {
342 GimpAsync *async = callback_info->async;
343 gboolean unref_async = FALSE;
344
345 g_mutex_lock (&async->priv->mutex);
346
347 g_queue_remove (&async->priv->callbacks, callback_info);
348
349 g_slice_free (GimpAsyncCallbackInfo, callback_info);
350
351 if (g_queue_is_empty (&async->priv->callbacks) && async->priv->idle_id)
352 {
353 g_source_remove (async->priv->idle_id);
354 async->priv->idle_id = 0;
355
356 unref_async = TRUE;
357 }
358
359 g_mutex_unlock (&async->priv->mutex);
360
361 if (unref_async)
362 g_object_unref (async);
363 }
364
365 static void
gimp_async_stop(GimpAsync * async)366 gimp_async_stop (GimpAsync *async)
367 {
368 #ifdef TIME_ASYNC_OPS
369 {
370 guint64 time = g_get_monotonic_time ();
371
372 g_printerr ("Asynchronous operation took %g seconds%s\n",
373 (time - async->priv->start_time) / 1000000.0,
374 async->priv->finished ? "" : " (aborted)");
375 }
376 #endif
377
378 g_atomic_int_dec_and_test (&gimp_async_n_running);
379
380 if (! g_queue_is_empty (&async->priv->callbacks))
381 {
382 g_object_ref (async);
383
384 async->priv->idle_id = g_idle_add_full (G_PRIORITY_DEFAULT,
385 (GSourceFunc) gimp_async_idle,
386 async, NULL);
387 }
388
389 async->priv->stopped = TRUE;
390
391 g_cond_broadcast (&async->priv->cond);
392 }
393
394 static void
gimp_async_run_callbacks(GimpAsync * async)395 gimp_async_run_callbacks (GimpAsync *async)
396 {
397 GimpAsyncCallbackInfo *callback_info;
398 gboolean unref_async = FALSE;
399
400 if (async->priv->idle_id)
401 {
402 g_source_remove (async->priv->idle_id);
403 async->priv->idle_id = 0;
404
405 unref_async = TRUE;
406 }
407
408 async->priv->synced = TRUE;
409
410 while ((callback_info = g_queue_pop_head (&async->priv->callbacks)))
411 {
412 if (callback_info->gobject)
413 {
414 g_object_ref (callback_info->gobject);
415
416 g_object_weak_unref (callback_info->gobject,
417 (GWeakNotify) gimp_async_callback_weak_notify,
418 callback_info);
419 }
420
421 callback_info->callback (async, callback_info->data);
422
423 if (callback_info->gobject)
424 g_object_unref (callback_info->gobject);
425
426 g_slice_free (GimpAsyncCallbackInfo, callback_info);
427 }
428
429 if (unref_async)
430 g_object_unref (async);
431 }
432
433
434 /* public functions */
435
436
437 /* creates a new GimpAsync object, initially unsynced and placed in the
438 * "running" state.
439 */
440 GimpAsync *
gimp_async_new(void)441 gimp_async_new (void)
442 {
443 return g_object_new (GIMP_TYPE_ASYNC,
444 NULL);
445 }
446
447 /* checks if 'async' is synced.
448 *
449 * may only be called on the main thread.
450 */
451 gboolean
gimp_async_is_synced(GimpAsync * async)452 gimp_async_is_synced (GimpAsync *async)
453 {
454 g_return_val_if_fail (GIMP_IS_ASYNC (async), FALSE);
455
456 return async->priv->synced;
457 }
458
459 /* registers a callback to be called when 'async' transitions to the "stopped"
460 * state. if 'async' is already stopped, the callback may be called directly.
461 *
462 * callbacks are called in the order in which they were added. 'async' is
463 * guaranteed to be kept alive, even without an external reference, between the
464 * point where it was stopped, and until all callbacks added while 'async' was
465 * externally referenced have been called.
466 *
467 * the callback is guaranteed to be called on the main thread.
468 *
469 * may only be called on the main thread.
470 */
471 void
gimp_async_add_callback(GimpAsync * async,GimpAsyncCallback callback,gpointer data)472 gimp_async_add_callback (GimpAsync *async,
473 GimpAsyncCallback callback,
474 gpointer data)
475 {
476 GimpAsyncCallbackInfo *callback_info;
477
478 g_return_if_fail (GIMP_IS_ASYNC (async));
479 g_return_if_fail (callback != NULL);
480
481 g_mutex_lock (&async->priv->mutex);
482
483 if (async->priv->stopped && g_queue_is_empty (&async->priv->callbacks))
484 {
485 async->priv->synced = TRUE;
486
487 g_mutex_unlock (&async->priv->mutex);
488
489 callback (async, data);
490
491 return;
492 }
493
494 callback_info = g_slice_new0 (GimpAsyncCallbackInfo);
495 callback_info->async = async;
496 callback_info->callback = callback;
497 callback_info->data = data;
498
499 g_queue_push_tail (&async->priv->callbacks, callback_info);
500
501 g_mutex_unlock (&async->priv->mutex);
502 }
503
504 /* same as 'gimp_async_add_callback()', however, takes an additional 'gobject'
505 * argument, which should be a GObject.
506 *
507 * 'gobject' is guaranteed to remain alive for the duration of the callback.
508 *
509 * When 'gobject' is destroyed, the callback is automatically removed.
510 */
511 void
gimp_async_add_callback_for_object(GimpAsync * async,GimpAsyncCallback callback,gpointer data,gpointer gobject)512 gimp_async_add_callback_for_object (GimpAsync *async,
513 GimpAsyncCallback callback,
514 gpointer data,
515 gpointer gobject)
516 {
517 GimpAsyncCallbackInfo *callback_info;
518
519 g_return_if_fail (GIMP_IS_ASYNC (async));
520 g_return_if_fail (callback != NULL);
521 g_return_if_fail (G_IS_OBJECT (gobject));
522
523 g_mutex_lock (&async->priv->mutex);
524
525 if (async->priv->stopped && g_queue_is_empty (&async->priv->callbacks))
526 {
527 async->priv->synced = TRUE;
528
529 g_mutex_unlock (&async->priv->mutex);
530
531 g_object_ref (gobject);
532
533 callback (async, data);
534
535 g_object_unref (gobject);
536
537 return;
538 }
539
540 callback_info = g_slice_new0 (GimpAsyncCallbackInfo);
541 callback_info->async = async;
542 callback_info->callback = callback;
543 callback_info->data = data;
544 callback_info->gobject = gobject;
545
546 g_queue_push_tail (&async->priv->callbacks, callback_info);
547
548 g_object_weak_ref (gobject,
549 (GWeakNotify) gimp_async_callback_weak_notify,
550 callback_info);
551
552 g_mutex_unlock (&async->priv->mutex);
553 }
554
555 /* removes all callbacks previously registered through
556 * 'gimp_async_add_callback()', matching 'callback' and 'data', which hasn't
557 * been called yet.
558 *
559 * may only be called on the main thread.
560 */
561 void
gimp_async_remove_callback(GimpAsync * async,GimpAsyncCallback callback,gpointer data)562 gimp_async_remove_callback (GimpAsync *async,
563 GimpAsyncCallback callback,
564 gpointer data)
565 {
566 GList *iter;
567 gboolean unref_async = FALSE;
568
569 g_return_if_fail (GIMP_IS_ASYNC (async));
570 g_return_if_fail (callback != NULL);
571
572 g_mutex_lock (&async->priv->mutex);
573
574 iter = g_queue_peek_head_link (&async->priv->callbacks);
575
576 while (iter)
577 {
578 GimpAsyncCallbackInfo *callback_info = iter->data;
579 GList *next = g_list_next (iter);
580
581 if (callback_info->callback == callback &&
582 callback_info->data == data)
583 {
584 if (callback_info->gobject)
585 {
586 g_object_weak_unref (
587 callback_info->gobject,
588 (GWeakNotify) gimp_async_callback_weak_notify,
589 callback_info);
590 }
591
592 g_queue_delete_link (&async->priv->callbacks, iter);
593
594 g_slice_free (GimpAsyncCallbackInfo, callback_info);
595 }
596
597 iter = next;
598 }
599
600 if (g_queue_is_empty (&async->priv->callbacks) && async->priv->idle_id)
601 {
602 g_source_remove (async->priv->idle_id);
603 async->priv->idle_id = 0;
604
605 unref_async = TRUE;
606 }
607
608 g_mutex_unlock (&async->priv->mutex);
609
610 if (unref_async)
611 g_object_unref (async);
612 }
613
614 /* checks if 'async' is in the "stopped" state.
615 *
616 * may only be called on the async thread.
617 */
618 gboolean
gimp_async_is_stopped(GimpAsync * async)619 gimp_async_is_stopped (GimpAsync *async)
620 {
621 g_return_val_if_fail (GIMP_IS_ASYNC (async), FALSE);
622
623 return async->priv->stopped;
624 }
625
626 /* transitions 'async' to the "stopped" state, indicating that the task
627 * completed normally, possibly providing a result.
628 *
629 * 'async' shall be in the "running" state.
630 *
631 * may only be called on the async thread.
632 */
633 void
gimp_async_finish(GimpAsync * async,gpointer result)634 gimp_async_finish (GimpAsync *async,
635 gpointer result)
636 {
637 gimp_async_finish_full (async, result, NULL);
638 }
639
640 /* same as 'gimp_async_finish()', taking an additional GDestroyNotify function,
641 * used for freeing the result when 'async' is destroyed.
642 */
643 void
gimp_async_finish_full(GimpAsync * async,gpointer result,GDestroyNotify result_destroy_func)644 gimp_async_finish_full (GimpAsync *async,
645 gpointer result,
646 GDestroyNotify result_destroy_func)
647 {
648 g_return_if_fail (GIMP_IS_ASYNC (async));
649 g_return_if_fail (! async->priv->stopped);
650
651 g_mutex_lock (&async->priv->mutex);
652
653 async->priv->finished = TRUE;
654 async->priv->result = result;
655 async->priv->result_destroy_func = result_destroy_func;
656
657 gimp_async_stop (async);
658
659 g_mutex_unlock (&async->priv->mutex);
660 }
661
662 /* checks if 'async' completed normally, using 'gimp_async_finish()' (in
663 * contrast to 'gimp_async_abort()').
664 *
665 * 'async' shall be in the "stopped" state.
666 *
667 * may only be called on the async thread, or on the main thread when 'async'
668 * is synced.
669 */
670 gboolean
gimp_async_is_finished(GimpAsync * async)671 gimp_async_is_finished (GimpAsync *async)
672 {
673 g_return_val_if_fail (GIMP_IS_ASYNC (async), FALSE);
674 g_return_val_if_fail (async->priv->stopped, FALSE);
675
676 return async->priv->finished;
677 }
678
679 /* returns the result of 'async', as passed to 'gimp_async_finish()'.
680 *
681 * 'async' shall be in the "stopped" state, and should have completed normally.
682 *
683 * may only be called on the async thread, or on the main thread when 'async'
684 * is synced.
685 */
686 gpointer
gimp_async_get_result(GimpAsync * async)687 gimp_async_get_result (GimpAsync *async)
688 {
689 g_return_val_if_fail (GIMP_IS_ASYNC (async), NULL);
690 g_return_val_if_fail (async->priv->stopped, NULL);
691 g_return_val_if_fail (async->priv->finished, NULL);
692
693 return async->priv->result;
694 }
695
696 /* transitions 'async' to the "stopped" state, indicating that the task
697 * was stopped before completion (normally, in response to a
698 * 'gimp_cancelable_cancel()' call).
699 *
700 * 'async' shall be in the "running" state.
701 *
702 * may only be called on the async thread.
703 */
704 void
gimp_async_abort(GimpAsync * async)705 gimp_async_abort (GimpAsync *async)
706 {
707 g_return_if_fail (GIMP_IS_ASYNC (async));
708 g_return_if_fail (! async->priv->stopped);
709
710 g_mutex_lock (&async->priv->mutex);
711
712 gimp_async_stop (async);
713
714 g_mutex_unlock (&async->priv->mutex);
715 }
716
717 /* checks if cancellation of 'async' has been requested.
718 *
719 * note that a return value of TRUE only indicates that
720 * 'gimp_cancelable_cancel()' has been called for 'async', and not that 'async'
721 * is stopped, or, if it is stopped, that it was aborted.
722 */
723 gboolean
gimp_async_is_canceled(GimpAsync * async)724 gimp_async_is_canceled (GimpAsync *async)
725 {
726 g_return_val_if_fail (GIMP_IS_ASYNC (async), FALSE);
727
728 return async->priv->canceled;
729 }
730
731 /* a convenience function, canceling 'async' and waiting for it to stop.
732 *
733 * may only be called on the main thread.
734 */
735 void
gimp_async_cancel_and_wait(GimpAsync * async)736 gimp_async_cancel_and_wait (GimpAsync *async)
737 {
738 g_return_if_fail (GIMP_IS_ASYNC (async));
739
740 gimp_cancelable_cancel (GIMP_CANCELABLE (async));
741 gimp_waitable_wait (GIMP_WAITABLE (async));
742 }
743
744
745 /* public functions (stats) */
746
747
748 gint
gimp_async_get_n_running(void)749 gimp_async_get_n_running (void)
750 {
751 return gimp_async_n_running;
752 }
753