Branch data Line data Source code
1 : : // SPDX-License-Identifier: GPL-3.0-or-later
2 : : // SPDX-FileCopyrightText: 2014-2019 Christian Hergert <chergert@redhat.com>
3 : : // SPDX-FileCopyrightText: Andy Holmes <andrew.g.r.holmes@gmail.com>
4 : :
5 : : #define G_LOG_DOMAIN "valent-object"
6 : :
7 : : #include "config.h"
8 : :
9 : : #include "valent-debug.h"
10 : : #include "valent-macros.h"
11 : : #include "valent-object.h"
12 : :
13 : : /**
14 : : * ValentObject:
15 : : *
16 : : * A base class for objects.
17 : : *
18 : : * `ValentObject` is a specialized `GObject` class, based on GNOME Builder's
19 : : * `IdeObject`. It provides a simple base class with helpers for working in
20 : : * threads.
21 : : *
22 : : * Instances have a `GRecMutex` and a `GCancellable` that is created on demand by
23 : : * valent_object_ref_cancellable() is called. When the object is destroyed, the
24 : : * `GCancellable`::cancel signal is emitted.
25 : : *
26 : : * Since: 1.0
27 : : */
28 : :
29 : : typedef struct
30 : : {
31 : : GRecMutex mutex;
32 : : GCancellable *cancellable;
33 : : unsigned int in_destruction : 1;
34 : : unsigned int destroyed : 1;
35 : : } ValentObjectPrivate;
36 : :
37 [ + + + - ]: 68562 : G_DEFINE_TYPE_WITH_PRIVATE (ValentObject, valent_object, G_TYPE_OBJECT)
38 : :
39 : : enum {
40 : : PROP_0,
41 : : PROP_CANCELLABLE,
42 : : N_PROPERTIES
43 : : };
44 : :
45 : : static GParamSpec *properties[N_PROPERTIES] = { NULL, };
46 : :
47 : : enum {
48 : : DESTROY,
49 : : N_SIGNALS
50 : : };
51 : :
52 : : static guint signals[N_SIGNALS] = { 0, };
53 : :
54 : :
55 : : static GQueue finalizer_queue = G_QUEUE_INIT;
56 : : static GMutex finalizer_mutex;
57 : : static GSource *finalizer_source;
58 : :
59 : : static gboolean
60 : 5352135 : valent_object_finalizer_source_check (GSource *source)
61 : : {
62 : 5352135 : return finalizer_queue.length > 0;
63 : : }
64 : :
65 : : static gboolean
66 : 3 : valent_object_finalizer_source_dispatch (GSource *source,
67 : : GSourceFunc callback,
68 : : gpointer user_data)
69 : : {
70 : 3 : while (finalizer_queue.length)
71 : : {
72 [ + + ]: 6 : g_autoptr (GObject) object = g_queue_pop_head (&finalizer_queue);
73 [ - + ]: 3 : g_object_run_dispose (object);
74 : : }
75 : :
76 : 3 : return G_SOURCE_CONTINUE;
77 : : }
78 : :
79 : : static GSourceFuncs finalizer_source_funcs = {
80 : : .check = valent_object_finalizer_source_check,
81 : : .dispatch = valent_object_finalizer_source_dispatch,
82 : : };
83 : :
84 : :
85 : : static inline void
86 : 16003 : valent_object_private_lock (ValentObjectPrivate *priv)
87 : : {
88 : 16003 : g_rec_mutex_lock (&priv->mutex);
89 : 14065 : }
90 : :
91 : : static inline void
92 : 16003 : valent_object_private_unlock (ValentObjectPrivate *priv)
93 : : {
94 : 16003 : g_rec_mutex_unlock (&priv->mutex);
95 : 14065 : }
96 : :
97 : : /*
98 : : * GObject.Object::notify
99 : : */
100 : : typedef struct
101 : : {
102 : : GRecMutex mutex;
103 : : GWeakRef object;
104 : : GParamSpec *pspec;
105 : : char *property_name;
106 : : } NotifyEmission;
107 : :
108 : : static gboolean
109 : 1 : valent_object_notify_main (gpointer data)
110 : : {
111 : 1 : NotifyEmission *emission = data;
112 : 2 : g_autoptr (GObject) object = NULL;
113 : :
114 [ + - ]: 1 : g_assert (emission != NULL);
115 : :
116 : 1 : g_rec_mutex_lock (&emission->mutex);
117 [ + - ]: 1 : if ((object = g_weak_ref_get (&emission->object)) != NULL)
118 : : {
119 [ - + ]: 1 : if (emission->pspec != NULL)
120 : 0 : g_object_notify_by_pspec (object, emission->pspec);
121 : : else
122 : 1 : g_object_notify (object, emission->property_name);
123 : : }
124 : :
125 : 1 : g_weak_ref_clear (&emission->object);
126 [ + - ]: 1 : g_clear_pointer (&emission->property_name, g_free);
127 [ - + ]: 1 : g_clear_pointer (&emission->pspec, g_param_spec_unref);
128 : 1 : g_rec_mutex_unlock (&emission->mutex);
129 : 1 : g_rec_mutex_clear (&emission->mutex);
130 : 1 : g_clear_pointer (&emission, g_free);
131 : :
132 [ + - ]: 1 : return G_SOURCE_REMOVE;
133 : : }
134 : :
135 : : /*
136 : : * ValentObject
137 : : */
138 : : static void
139 : 1488 : valent_object_real_destroy (ValentObject *self)
140 : : {
141 : 1488 : ValentObjectPrivate *priv = valent_object_get_instance_private (self);
142 : :
143 [ + - ]: 1488 : g_assert (VALENT_IS_OBJECT (self));
144 : :
145 : 1488 : g_cancellable_cancel (priv->cancellable);
146 : 1488 : priv->destroyed = TRUE;
147 : 1488 : }
148 : :
149 : : /*
150 : : * GObject
151 : : */
152 : : static void
153 : 1489 : valent_object_dispose (GObject *object)
154 : : {
155 : 1489 : ValentObject *self = VALENT_OBJECT (object);
156 : 1489 : ValentObjectPrivate *priv = valent_object_get_instance_private (self);
157 : :
158 [ + + ]: 1489 : if (!VALENT_IS_MAIN_THREAD ())
159 : : {
160 : 1 : g_mutex_lock (&finalizer_mutex);
161 : 1 : g_queue_push_tail (&finalizer_queue, g_object_ref (self));
162 : 1 : g_mutex_unlock (&finalizer_mutex);
163 : 1 : g_main_context_wakeup (NULL);
164 : 1 : return;
165 : : }
166 : :
167 [ + - ]: 1488 : g_assert (VALENT_IS_OBJECT (object));
168 [ - + ]: 1488 : g_assert (VALENT_IS_MAIN_THREAD ());
169 : :
170 : 1488 : valent_object_private_lock (priv);
171 [ + - ]: 1488 : if (!priv->in_destruction)
172 : : {
173 : 1488 : priv->in_destruction = TRUE;
174 : 1488 : g_signal_emit (self, signals [DESTROY], 0);
175 : 1488 : priv->in_destruction = FALSE;
176 : : }
177 : 1488 : valent_object_private_unlock (priv);
178 : :
179 : 1488 : G_OBJECT_CLASS (valent_object_parent_class)->dispose (object);
180 : : }
181 : :
182 : : static void
183 : 1379 : valent_object_constructed (GObject *object)
184 : : {
185 [ - + ]: 1379 : if G_UNLIKELY (G_OBJECT_GET_CLASS (object)->dispose != valent_object_dispose)
186 : : {
187 : 0 : V_GNUC_BEGIN_IGNORE_INFINITE_LOOP;
188 : 0 : g_error ("%s overrides GObject.Object.dispose() instead of "
189 : : "Valent.Object.destroy(), which is not thread safe",
190 : : G_OBJECT_TYPE_NAME (object));
191 : 1379 : V_GNUC_END_IGNORE_INFINITE_LOOP;
192 : : }
193 : :
194 : 1379 : G_OBJECT_CLASS (valent_object_parent_class)->constructed (object);
195 : 1379 : }
196 : :
197 : : static void
198 : 1271 : valent_object_finalize (GObject *object)
199 : : {
200 : 1271 : ValentObject *self = VALENT_OBJECT (object);
201 : 1271 : ValentObjectPrivate *priv = valent_object_get_instance_private (self);
202 : :
203 [ + + ]: 1271 : g_clear_object (&priv->cancellable);
204 : 1271 : g_rec_mutex_clear (&priv->mutex);
205 : :
206 : 1271 : G_OBJECT_CLASS (valent_object_parent_class)->finalize (object);
207 : 1271 : }
208 : :
209 : : static void
210 : 1 : valent_object_get_property (GObject *object,
211 : : guint prop_id,
212 : : GValue *value,
213 : : GParamSpec *pspec)
214 : : {
215 : 1 : ValentObject *self = VALENT_OBJECT (object);
216 : :
217 [ + - ]: 1 : switch (prop_id)
218 : : {
219 : 1 : case PROP_CANCELLABLE:
220 : 1 : g_value_take_object (value, valent_object_ref_cancellable (self));
221 : 1 : break;
222 : :
223 : 0 : default:
224 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
225 : : }
226 : 1 : }
227 : :
228 : : static void
229 : 1379 : valent_object_set_property (GObject *object,
230 : : guint prop_id,
231 : : const GValue *value,
232 : : GParamSpec *pspec)
233 : : {
234 : 1379 : ValentObject *self = VALENT_OBJECT (object);
235 : 1379 : ValentObjectPrivate *priv = valent_object_get_instance_private (self);
236 : :
237 [ + - ]: 1379 : switch (prop_id)
238 : : {
239 : 1379 : case PROP_CANCELLABLE:
240 : 1379 : priv->cancellable = g_value_dup_object (value);
241 : 1379 : break;
242 : :
243 : 0 : default:
244 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
245 : : }
246 : 1379 : }
247 : :
248 : : static void
249 : 74 : valent_object_class_init (ValentObjectClass *klass)
250 : : {
251 : 74 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
252 : :
253 : 74 : object_class->constructed = valent_object_constructed;
254 : 74 : object_class->dispose = valent_object_dispose;
255 : 74 : object_class->finalize = valent_object_finalize;
256 : 74 : object_class->get_property = valent_object_get_property;
257 : 74 : object_class->set_property = valent_object_set_property;
258 : :
259 : 74 : klass->destroy = valent_object_real_destroy;
260 : :
261 : : /**
262 : : * ValentObject:cancellable: (getter ref_cancellable)
263 : : *
264 : : * The object [class@Gio.Cancellable].
265 : : *
266 : : * A `GCancellable` that can be used by operations that should be cancelled
267 : : * when the object is destroyed (i.e. enters disposal).
268 : : *
269 : : * Since: 1.0
270 : : */
271 : 148 : properties [PROP_CANCELLABLE] =
272 : 74 : g_param_spec_object ("cancellable", NULL, NULL,
273 : : G_TYPE_CANCELLABLE,
274 : : (G_PARAM_READWRITE |
275 : : G_PARAM_CONSTRUCT_ONLY |
276 : : G_PARAM_EXPLICIT_NOTIFY |
277 : : G_PARAM_STATIC_STRINGS));
278 : :
279 : 74 : g_object_class_install_properties (object_class, N_PROPERTIES, properties);
280 : :
281 : : /**
282 : : * ValentObject::destroy:
283 : : *
284 : : * Emitted when the object is being destroyed.
285 : : *
286 : : * This signal is emitted when the object enters disposal and always on the
287 : : * main thread, with the object lock acquired. Note that you must still drop
288 : : * any references you hold to avoid leaking memory.
289 : : *
290 : : * Implementations must override [vfunc@Valent.Object.destroy] instead of
291 : : * [vfunc@GObject.Object.dispose] to ensure the instance is finalized on the
292 : : * main thread.
293 : : *
294 : : * Since: 1.0
295 : : */
296 : 148 : signals [DESTROY] =
297 : 74 : g_signal_new ("destroy",
298 : : G_TYPE_FROM_CLASS (klass),
299 : : (G_SIGNAL_RUN_CLEANUP |
300 : : G_SIGNAL_NO_RECURSE |
301 : : G_SIGNAL_NO_HOOKS),
302 : : G_STRUCT_OFFSET (ValentObjectClass, destroy),
303 : : NULL, NULL,
304 : : g_cclosure_marshal_VOID__VOID,
305 : : G_TYPE_NONE, 0);
306 : 74 : g_signal_set_va_marshaller (signals [DESTROY],
307 : : G_TYPE_FROM_CLASS (klass),
308 : : g_cclosure_marshal_VOID__VOIDv);
309 : :
310 : : /* Setup the finalizer in main thread to receive off-thread objects */
311 : 74 : finalizer_source = g_source_new (&finalizer_source_funcs, sizeof (GSource));
312 : 74 : g_source_set_static_name (finalizer_source, "[valent-object-finalizer]");
313 : 74 : g_source_set_priority (finalizer_source, G_MAXINT);
314 : 74 : g_source_attach (finalizer_source, NULL);
315 : 74 : }
316 : :
317 : : static void
318 : 1379 : valent_object_init (ValentObject *self)
319 : : {
320 : 1379 : ValentObjectPrivate *priv = valent_object_get_instance_private (self);
321 : :
322 : 1379 : g_rec_mutex_init (&priv->mutex);
323 : 1379 : }
324 : :
325 : : /**
326 : : * valent_object_lock:
327 : : * @object: a `ValentObject`
328 : : *
329 : : * Acquire a lock on @object.
330 : : *
331 : : * Call [method@Valent.Object.unlock] to release the lock.
332 : : *
333 : : * Since: 1.0
334 : : */
335 : : void
336 : 14065 : valent_object_lock (ValentObject *object)
337 : : {
338 : 14065 : ValentObjectPrivate *priv = valent_object_get_instance_private (object);
339 : :
340 [ + - ]: 14065 : g_return_if_fail (VALENT_IS_OBJECT (object));
341 : :
342 : 14065 : valent_object_private_lock (priv);
343 : : }
344 : :
345 : : /**
346 : : * valent_object_unlock:
347 : : * @object: a `ValentObject`
348 : : *
349 : : * Release a lock on @object.
350 : : *
351 : : * The lock must have previously been acquired by [method@Valent.Object.lock].
352 : : *
353 : : * Since: 1.0
354 : : */
355 : : void
356 : 14065 : valent_object_unlock (ValentObject *object)
357 : : {
358 : 14065 : ValentObjectPrivate *priv = valent_object_get_instance_private (object);
359 : :
360 [ + - ]: 14065 : g_return_if_fail (VALENT_IS_OBJECT (object));
361 : :
362 : 14065 : valent_object_private_unlock (priv);
363 : : }
364 : :
365 : : /**
366 : : * valent_object_ref_cancellable:
367 : : * @object: a `ValentObject`
368 : : *
369 : : * Get a [class@Gio.Cancellable] for the object.
370 : : *
371 : : * Returns: (transfer full) (not nullable): @object's `GCancellable`
372 : : *
373 : : * Since: 1.0
374 : : */
375 : : GCancellable *
376 : 166 : valent_object_ref_cancellable (ValentObject *object)
377 : : {
378 : 166 : ValentObjectPrivate *priv = valent_object_get_instance_private (object);
379 : 166 : GCancellable *ret;
380 : :
381 [ + - ]: 166 : g_return_val_if_fail (VALENT_IS_OBJECT (object), NULL);
382 : :
383 : 166 : valent_object_private_lock (priv);
384 [ + + ]: 166 : if (priv->cancellable == NULL)
385 : 57 : priv->cancellable = g_cancellable_new ();
386 : 166 : ret = g_object_ref (priv->cancellable);
387 : 166 : valent_object_private_unlock (priv);
388 : :
389 : 166 : return g_steal_pointer (&ret);
390 : : }
391 : :
392 : : /**
393 : : * valent_object_chain_cancellable:
394 : : * @object: a `ValentObject`
395 : : * @cancellable: (nullable): a `GCancellable`
396 : : *
397 : : * Chain a cancellable to the object's cancellable.
398 : : *
399 : : * This connects @cancellable to @objects's [signal@Gio.Cancellable::cancelled]
400 : : * so that if @object is destroyed, @cancellable will be cancelled. If
401 : : * @cancellable is %NULL, this method will return a new reference to
402 : : * [property@Valent.Object:cancellable], otherwise it returns a new reference to
403 : : * @cancellable.
404 : : *
405 : : * Typically the returned [class@Gio.Cancellable] is passed to an internal
406 : : * asynchronous operation, to ensure it is cancelled if @cancellable is
407 : : * triggered or @object is destroyed.
408 : : *
409 : : * Returns: (transfer full) (not nullable): a `GCancellable`
410 : : *
411 : : * Since: 1.0
412 : : */
413 : : GCancellable *
414 : 75 : valent_object_chain_cancellable (ValentObject *object,
415 : : GCancellable *cancellable)
416 : : {
417 : 75 : ValentObjectPrivate *priv = valent_object_get_instance_private (object);
418 : 75 : GCancellable *ret;
419 : :
420 [ + - ]: 75 : g_return_val_if_fail (VALENT_IS_OBJECT (object), NULL);
421 [ + + + - : 75 : g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
- + - - ]
422 : :
423 : 75 : valent_object_private_lock (priv);
424 [ + + ]: 75 : if (priv->cancellable == NULL)
425 : 71 : priv->cancellable = g_cancellable_new ();
426 : :
427 [ + + ]: 75 : if (cancellable != NULL)
428 : : {
429 : 55 : g_signal_connect_object (priv->cancellable,
430 : : "cancelled",
431 : : G_CALLBACK (g_cancellable_cancel),
432 : : cancellable,
433 : : G_CONNECT_SWAPPED);
434 : 55 : ret = g_object_ref (cancellable);
435 : : }
436 : : else
437 : : {
438 : 20 : ret = g_object_ref (priv->cancellable);
439 : : }
440 : 75 : valent_object_private_unlock (priv);
441 : :
442 : 75 : return g_steal_pointer (&ret);
443 : : }
444 : :
445 : : /**
446 : : * valent_object_destroy:
447 : : * @object: a `ValentObject`
448 : : *
449 : : * Destroy the object.
450 : : *
451 : : * If called from the main thread, it calls [method@GObject.Object.run_dispose],
452 : : * which activates the object [class@Gio.Cancellable] and emits
453 : : * [signal@Valent.Object::destroy].
454 : : *
455 : : * If called from another thread, an idle source will be added to invoke it on
456 : : * the main thread.
457 : : *
458 : : * Since: 1.0
459 : : */
460 : : void
461 : 209 : valent_object_destroy (ValentObject *object)
462 : : {
463 : 209 : ValentObjectPrivate *priv = valent_object_get_instance_private (object);
464 : :
465 [ + - ]: 209 : g_return_if_fail (VALENT_IS_OBJECT (object));
466 : :
467 : 209 : g_object_ref (object);
468 : 209 : valent_object_private_lock (priv);
469 : :
470 [ + + ]: 209 : if (VALENT_IS_MAIN_THREAD ())
471 : : {
472 : 207 : g_cancellable_cancel (priv->cancellable);
473 : :
474 [ + - ]: 207 : if (!priv->in_destruction && !priv->destroyed)
475 : 207 : g_object_run_dispose (G_OBJECT (object));
476 : : }
477 : : else
478 : : {
479 : 2 : g_mutex_lock (&finalizer_mutex);
480 : 2 : g_queue_push_tail (&finalizer_queue, g_object_ref (object));
481 : 2 : g_mutex_unlock (&finalizer_mutex);
482 : : }
483 : :
484 : 209 : valent_object_private_unlock (priv);
485 : 209 : g_object_unref (object);
486 : : }
487 : :
488 : : /**
489 : : * valent_object_in_destruction:
490 : : * @object: a `ValentObject`
491 : : *
492 : : * Get whether the object is destroyed or in destruction.
493 : : *
494 : : * Returns: %TRUE if destroyed, or %FALSE if not
495 : : *
496 : : * Since: 1.0
497 : : */
498 : : gboolean
499 : 43 : valent_object_in_destruction (ValentObject *object)
500 : : {
501 : 43 : ValentObjectPrivate *priv = valent_object_get_instance_private (object);
502 : 43 : gboolean ret;
503 : :
504 [ + - ]: 43 : g_return_val_if_fail (VALENT_IS_OBJECT (object), FALSE);
505 : :
506 : 43 : valent_object_lock (object);
507 : 43 : ret = priv->in_destruction || priv->destroyed;
508 : 43 : valent_object_unlock (object);
509 : :
510 : 43 : return ret;
511 : : }
512 : :
513 : : /**
514 : : * valent_object_notify:
515 : : * @object: a `ValentObject`
516 : : * @property_name: a property name
517 : : *
518 : : * Emit [signal@GObject.Object::notify] on @object, on the main thread.
519 : : *
520 : : * Like [method@GObject.Object.notify] if the caller is in the main thread,
521 : : * otherwise the invocation is deferred to the main thread.
522 : : *
523 : : * Since: 1.0
524 : : */
525 : : void
526 : 1 : valent_object_notify (ValentObject *object,
527 : : const char *property_name)
528 : : {
529 : 1 : NotifyEmission *emission = NULL;
530 : :
531 [ + - ]: 1 : g_return_if_fail (VALENT_IS_OBJECT (object));
532 [ - + ]: 1 : g_return_if_fail (property_name != NULL);
533 : :
534 [ + - ]: 1 : if G_LIKELY (VALENT_IS_MAIN_THREAD ())
535 : : {
536 : 0 : g_object_notify (G_OBJECT (object), property_name);
537 : 0 : return;
538 : : }
539 : :
540 : 1 : emission = g_new0 (NotifyEmission, 1);
541 : 1 : g_rec_mutex_init (&emission->mutex);
542 : 1 : g_rec_mutex_lock (&emission->mutex);
543 : 1 : g_weak_ref_init (&emission->object, object);
544 [ - + ]: 1 : emission->property_name = g_strdup (property_name);
545 : 1 : g_rec_mutex_unlock (&emission->mutex);
546 : :
547 : 1 : g_idle_add_full (G_PRIORITY_DEFAULT,
548 : : valent_object_notify_main,
549 : : g_steal_pointer (&emission),
550 : : NULL);
551 : : }
552 : :
553 : : /**
554 : : * valent_object_notify_by_pspec:
555 : : * @object: a `ValentObject`
556 : : * @pspec: a `GParamSpec`
557 : : *
558 : : * Emit [signal@GObject.Object::notify] on @object, on the main thread.
559 : : *
560 : : * Like [method@GObject.Object.notify_by_pspec] if the caller is in the main
561 : : * thread, otherwise the invocation is deferred to the main thread.
562 : : *
563 : : * Since: 1.0
564 : : */
565 : : void
566 : 387 : valent_object_notify_by_pspec (ValentObject *object,
567 : : GParamSpec *pspec)
568 : : {
569 : 387 : NotifyEmission *emission = NULL;
570 : :
571 [ + - ]: 387 : g_return_if_fail (VALENT_IS_OBJECT (object));
572 [ - + ]: 387 : g_return_if_fail (G_IS_PARAM_SPEC (pspec));
573 : :
574 [ - + ]: 387 : if G_LIKELY (VALENT_IS_MAIN_THREAD ())
575 : : {
576 : 387 : g_object_notify_by_pspec (G_OBJECT (object), pspec);
577 : 387 : return;
578 : : }
579 : :
580 : 0 : emission = g_new0 (NotifyEmission, 1);
581 : 0 : g_rec_mutex_init (&emission->mutex);
582 : 0 : g_rec_mutex_lock (&emission->mutex);
583 : 0 : g_weak_ref_init (&emission->object, object);
584 : 0 : emission->pspec = g_param_spec_ref (pspec);
585 : 0 : g_rec_mutex_unlock (&emission->mutex);
586 : :
587 : 0 : g_idle_add_full (G_PRIORITY_DEFAULT,
588 : : valent_object_notify_main,
589 : : g_steal_pointer (&emission),
590 : : NULL);
591 : : }
|