1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * Copyright (C) 2008 Novell, Inc.
4 *
5 * This library is free software: you can redistribute it and/or modify it
6 * under the terms of the GNU Lesser General Public License as published by
7 * the Free Software Foundation.
8 *
9 * This library is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
12 * for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this library. If not, see <http://www.gnu.org/licenses/>.
16 *
17 */
18
19 #include "camel-object-bag.h"
20
21 #include <glib-object.h>
22
23 typedef struct _KeyReservation KeyReservation;
24
25 struct _KeyReservation {
26 gpointer key;
27 gint waiters;
28 GThread *owner;
29 GCond cond;
30 };
31
32 struct _CamelObjectBag {
33 GHashTable *key_table;
34 GHashTable *object_table;
35 GEqualFunc key_equal_func;
36 CamelCopyFunc key_copy_func;
37 GFreeFunc key_free_func;
38 GList *reserved; /* list of KeyReservations */
39 GMutex mutex;
40 };
41
42 static KeyReservation *
key_reservation_new(CamelObjectBag * bag,gconstpointer key)43 key_reservation_new (CamelObjectBag *bag,
44 gconstpointer key)
45 {
46 KeyReservation *reservation;
47
48 reservation = g_slice_new0 (KeyReservation);
49 reservation->key = bag->key_copy_func (key);
50 reservation->owner = g_thread_self ();
51 g_cond_init (&reservation->cond);
52
53 bag->reserved = g_list_prepend (bag->reserved, reservation);
54
55 return reservation;
56 }
57
58 static KeyReservation *
key_reservation_lookup(CamelObjectBag * bag,gconstpointer key)59 key_reservation_lookup (CamelObjectBag *bag,
60 gconstpointer key)
61 {
62 GList *iter;
63
64 /* XXX Might be easier to use a GHashTable for reservations. */
65 for (iter = bag->reserved; iter != NULL; iter = iter->next) {
66 KeyReservation *reservation = iter->data;
67 if (bag->key_equal_func (reservation->key, key))
68 return reservation;
69 }
70
71 return NULL;
72 }
73
74 static void
key_reservation_free(CamelObjectBag * bag,KeyReservation * reservation)75 key_reservation_free (CamelObjectBag *bag,
76 KeyReservation *reservation)
77 {
78 /* Make sure the reservation is actually in the object bag. */
79 g_return_if_fail (key_reservation_lookup (bag, reservation->key) != NULL);
80
81 bag->reserved = g_list_remove (bag->reserved, reservation);
82
83 bag->key_free_func (reservation->key);
84 g_cond_clear (&reservation->cond);
85 g_slice_free (KeyReservation, reservation);
86 }
87
88 static void
object_bag_unreserve(CamelObjectBag * bag,gconstpointer key)89 object_bag_unreserve (CamelObjectBag *bag,
90 gconstpointer key)
91 {
92 KeyReservation *reservation;
93
94 reservation = key_reservation_lookup (bag, key);
95 g_return_if_fail (reservation != NULL);
96 g_return_if_fail (reservation->owner == g_thread_self ());
97
98 if (reservation->waiters > 0) {
99 reservation->owner = NULL;
100 g_cond_signal (&reservation->cond);
101 } else
102 key_reservation_free (bag, reservation);
103 }
104
105 /* Ick. We need to store the original gobject pointer too, since that's
106 * used as the key in one of the hash tables. So to clean up after the
107 * object dies and the GWeakRef starts returning NULL, we'll need to
108 * know where it *was*. This is safe because we'll always check and
109 * avoid adding a duplicate. But still, ick. */
110 typedef struct {
111 GWeakRef ref;
112 gpointer obj;
113 CamelObjectBag *bag;
114 } ObjRef;
115
116 static void
object_bag_notify(CamelObjectBag * bag,GObject * where_the_object_was)117 object_bag_notify (CamelObjectBag *bag,
118 GObject *where_the_object_was)
119 {
120 gconstpointer key;
121
122 g_mutex_lock (&bag->mutex);
123
124 key = g_hash_table_lookup (bag->key_table, where_the_object_was);
125 if (key != NULL) {
126 g_hash_table_remove (bag->key_table, where_the_object_was);
127 g_hash_table_remove (bag->object_table, key);
128 }
129
130 g_mutex_unlock (&bag->mutex);
131 }
132
133 /* Properly destroy an ObjRef as it's freed from the hash table */
134 static void
wref_free_func(gpointer p)135 wref_free_func (gpointer p)
136 {
137 ObjRef *ref = p;
138 GObject *obj = g_weak_ref_get (&ref->ref);
139
140 if (obj) {
141 /* The object is being removed from the bag while it's
142 * still alive, e.g. by camel_object_bag_remove()
143 * or camel_object_bag_destroy(). Drop the weak_ref. */
144 g_object_weak_unref (
145 obj, (GWeakNotify) object_bag_notify,
146 ref->bag);
147 g_object_unref (obj);
148 }
149 g_weak_ref_clear (&ref->ref);
150 g_slice_free (ObjRef, ref);
151 }
152
153 /**
154 * camel_object_bag_new: (skip)
155 * @key_hash_func: (scope call): a hashing function for keys
156 * @key_equal_func: (scope call): a comparison function for keys
157 * @key_copy_func: (scope call): a function to copy keys
158 * @key_free_func: (scope call): a function to free keys
159 *
160 * Returns a new object bag. Object bags are keyed hash tables of objects
161 * that can be updated atomically using transaction semantics. Use
162 * camel_object_bag_destroy() to free the object bag.
163 *
164 * Returns: a newly-allocated #CamelObjectBag
165 **/
166 CamelObjectBag *
camel_object_bag_new(GHashFunc key_hash_func,GEqualFunc key_equal_func,CamelCopyFunc key_copy_func,GFreeFunc key_free_func)167 camel_object_bag_new (GHashFunc key_hash_func,
168 GEqualFunc key_equal_func,
169 CamelCopyFunc key_copy_func,
170 GFreeFunc key_free_func)
171 {
172 CamelObjectBag *bag;
173 GHashTable *key_table;
174 GHashTable *object_table;
175
176 g_return_val_if_fail (key_hash_func != NULL, NULL);
177 g_return_val_if_fail (key_equal_func != NULL, NULL);
178 g_return_val_if_fail (key_copy_func != NULL, NULL);
179 g_return_val_if_fail (key_free_func != NULL, NULL);
180
181 /* Each key is shared between both hash tables, so only one
182 * table needs to be responsible for destroying keys. */
183
184 key_table = g_hash_table_new (g_direct_hash, g_direct_equal);
185
186 object_table = g_hash_table_new_full (
187 key_hash_func, key_equal_func,
188 (GDestroyNotify) key_free_func,
189 (GDestroyNotify) wref_free_func);
190
191 bag = g_slice_new0 (CamelObjectBag);
192 bag->key_table = key_table;
193 bag->object_table = object_table;
194 bag->key_equal_func = key_equal_func;
195 bag->key_copy_func = key_copy_func;
196 bag->key_free_func = key_free_func;
197 g_mutex_init (&bag->mutex);
198
199 return bag;
200 }
201
202 /**
203 * camel_object_bag_get:
204 * @bag: a #CamelObjectBag
205 * @key: a key
206 *
207 * Lookup an object by @key. If the key is currently reserved, the function
208 * will block until another thread commits or aborts the reservation. The
209 * caller owns the reference to the returned object. Use g_object_unref ()
210 * to unreference it.
211 *
212 * Returns: (transfer full) (nullable): the object corresponding to @key, or
213 * %NULL if not found
214 **/
215 gpointer
camel_object_bag_get(CamelObjectBag * bag,gconstpointer key)216 camel_object_bag_get (CamelObjectBag *bag,
217 gconstpointer key)
218 {
219 KeyReservation *reservation;
220 ObjRef *ref;
221 gpointer object = NULL;
222
223 g_return_val_if_fail (bag != NULL, NULL);
224 g_return_val_if_fail (key != NULL, NULL);
225
226 g_mutex_lock (&bag->mutex);
227
228 /* Look for the key in the bag. */
229 ref = g_hash_table_lookup (bag->object_table, key);
230 if (ref != NULL) {
231 object = g_weak_ref_get (&ref->ref);
232 if (object != NULL) {
233 g_mutex_unlock (&bag->mutex);
234 return object;
235 }
236
237 /* Remove stale reference to dead object. */
238 g_hash_table_remove (bag->key_table, ref->obj);
239 g_hash_table_remove (bag->object_table, key);
240 }
241
242 /* Check if the key has been reserved. */
243 reservation = key_reservation_lookup (bag, key);
244 if (reservation == NULL) {
245 /* No such key, so return NULL. */
246 g_mutex_unlock (&bag->mutex);
247 return NULL;
248 }
249
250 /* Wait for the key to be unreserved. */
251 reservation->waiters++;
252 while (reservation->owner != NULL)
253 g_cond_wait (&reservation->cond, &bag->mutex);
254 reservation->waiters--;
255
256 /* Check if an object was added by another thread. */
257 ref = g_hash_table_lookup (bag->object_table, key);
258 if (ref != NULL) {
259 object = g_weak_ref_get (&ref->ref);
260 if (object == NULL) {
261 /* Remove stale reference to dead object. */
262 g_hash_table_remove (bag->key_table, ref->obj);
263 g_hash_table_remove (bag->object_table, key);
264 }
265 }
266
267 /* We're not reserving it. */
268 reservation->owner = g_thread_self ();
269 object_bag_unreserve (bag, key);
270
271 g_mutex_unlock (&bag->mutex);
272
273 return object;
274 }
275
276 /**
277 * camel_object_bag_peek:
278 * @bag: a #CamelObjectBag
279 * @key: an unreserved key
280 *
281 * Returns the object for @key in @bag, ignoring any reservations. If it
282 * isn't committed, then it isn't considered. This should only be used
283 * where reliable transactional-based state is not required.
284 *
285 * Unlink other "peek" operations, the caller owns the returned object
286 * reference. Use g_object_unref () to unreference it.
287 *
288 * Returns: (transfer full) (nullable): the object for @key, or %NULL if @key
289 * is reserved or not found
290 **/
291 gpointer
camel_object_bag_peek(CamelObjectBag * bag,gconstpointer key)292 camel_object_bag_peek (CamelObjectBag *bag,
293 gconstpointer key)
294 {
295 gpointer object = NULL;
296 ObjRef *ref;
297
298 g_return_val_if_fail (bag != NULL, NULL);
299 g_return_val_if_fail (key != NULL, NULL);
300
301 g_mutex_lock (&bag->mutex);
302
303 ref = g_hash_table_lookup (bag->object_table, key);
304 if (ref != NULL)
305 object = g_weak_ref_get (&ref->ref);
306
307 g_mutex_unlock (&bag->mutex);
308
309 return object;
310 }
311
312 /**
313 * camel_object_bag_reserve:
314 * @bag: a #CamelObjectBag
315 * @key: the key to reserve
316 *
317 * Reserves @key in @bag. If @key is already reserved in another thread,
318 * then wait until the reservation has been committed.
319 *
320 * After reserving @key, you either get a reference to the object
321 * corresponding to @key (similar to camel_object_bag_get()) or you get
322 * %NULL, signifying that you MUST call either camel_object_bag_add() or
323 * camel_object_bag_abort().
324 *
325 * Returns: (transfer full) (nullable): the object for @key, or %NULL if @key
326 * is not found
327 **/
328 gpointer
camel_object_bag_reserve(CamelObjectBag * bag,gconstpointer key)329 camel_object_bag_reserve (CamelObjectBag *bag,
330 gconstpointer key)
331 {
332 KeyReservation *reservation;
333 ObjRef *ref;
334 gpointer object = NULL;
335
336 g_return_val_if_fail (bag != NULL, NULL);
337 g_return_val_if_fail (key != NULL, NULL);
338
339 g_mutex_lock (&bag->mutex);
340
341 /* If object for key already exists, return it immediately. */
342 ref = g_hash_table_lookup (bag->object_table, key);
343 if (ref != NULL) {
344 object = g_weak_ref_get (&ref->ref);
345 if (object != NULL) {
346 g_mutex_unlock (&bag->mutex);
347 return object;
348 }
349
350 /* Remove stale reference to dead object. */
351 g_hash_table_remove (bag->key_table, ref->obj);
352 g_hash_table_remove (bag->object_table, key);
353 }
354
355 /* If no such key exists in the bag, create a reservation. */
356 reservation = key_reservation_lookup (bag, key);
357 if (reservation == NULL) {
358 key_reservation_new (bag, key);
359 g_mutex_unlock (&bag->mutex);
360 return NULL;
361 }
362
363 /* Wait for the reservation to be committed or aborted. */
364 reservation->waiters++;
365 while (reservation->owner != NULL)
366 g_cond_wait (&reservation->cond, &bag->mutex);
367 reservation->owner = g_thread_self ();
368 reservation->waiters--;
369
370 /* Check if the object was added by another thread. */
371 ref = g_hash_table_lookup (bag->object_table, key);
372 if (ref != NULL) {
373 object = g_weak_ref_get (&ref->ref);
374 if (object != NULL) {
375 /* We have an object; no need to reserve the key. */
376 object_bag_unreserve (bag, key);
377 } else {
378 /* Remove stale reference to dead object. */
379 g_hash_table_remove (bag->key_table, ref->obj);
380 g_hash_table_remove (bag->object_table, key);
381 }
382 }
383
384 g_mutex_unlock (&bag->mutex);
385
386 return object;
387 }
388
389 static gboolean
object_in_bag(CamelObjectBag * bag,gpointer object)390 object_in_bag (CamelObjectBag *bag,
391 gpointer object)
392 {
393 gconstpointer key;
394 ObjRef *ref;
395 GObject *obj2;
396
397 key = g_hash_table_lookup (bag->key_table, object);
398 if (key == NULL)
399 return FALSE;
400
401 ref = g_hash_table_lookup (bag->object_table, key);
402 if (ref == NULL)
403 return FALSE;
404
405 obj2 = g_weak_ref_get (&ref->ref);
406 if (obj2 == NULL) {
407 /* Remove stale reference to dead object. */
408 g_hash_table_remove (bag->key_table, object);
409 g_hash_table_remove (bag->object_table, key);
410 } else {
411 g_object_unref (obj2);
412 }
413
414 return obj2 == object;
415 }
416
417 /**
418 * camel_object_bag_add:
419 * @bag: a #CamelObjectBag
420 * @key: a reserved key
421 * @object: a #GObject
422 *
423 * Adds @object to @bag. The @key MUST have been previously reserved using
424 * camel_object_bag_reserve().
425 **/
426 void
camel_object_bag_add(CamelObjectBag * bag,gconstpointer key,gpointer object)427 camel_object_bag_add (CamelObjectBag *bag,
428 gconstpointer key,
429 gpointer object)
430 {
431 g_return_if_fail (bag != NULL);
432 g_return_if_fail (key != NULL);
433 g_return_if_fail (G_IS_OBJECT (object));
434
435 g_mutex_lock (&bag->mutex);
436
437 /* Check it's the *same* object, not an old one at the same address */
438 if (!object_in_bag (bag, object)) {
439 ObjRef *ref;
440 gpointer copied_key;
441
442 ref = g_slice_new (ObjRef);
443 ref->bag = bag;
444 /* We need to stash a 'raw' pointer since that's the key we use
445 * in the key_table */
446 ref->obj = object;
447 g_weak_ref_init (&ref->ref, object);
448 copied_key = bag->key_copy_func (key);
449 g_hash_table_insert (bag->key_table, object, copied_key);
450 g_hash_table_insert (bag->object_table, copied_key, ref);
451 object_bag_unreserve (bag, key);
452
453 g_object_weak_ref (
454 G_OBJECT (object),
455 (GWeakNotify) object_bag_notify, bag);
456 }
457
458 g_mutex_unlock (&bag->mutex);
459 }
460
461 /**
462 * camel_object_bag_abort:
463 * @bag: a #CamelObjectBag
464 * @key: a reserved key
465 *
466 * Aborts a key reservation.
467 **/
468 void
camel_object_bag_abort(CamelObjectBag * bag,gconstpointer key)469 camel_object_bag_abort (CamelObjectBag *bag,
470 gconstpointer key)
471 {
472 g_return_if_fail (bag != NULL);
473 g_return_if_fail (key != NULL);
474
475 g_mutex_lock (&bag->mutex);
476
477 object_bag_unreserve (bag, key);
478
479 g_mutex_unlock (&bag->mutex);
480 }
481
482 /**
483 * camel_object_bag_rekey:
484 * @bag: a #CamelObjectBag
485 * @object: a #GObject
486 * @new_key: a new key for @object
487 *
488 * Changes the key for @object to @new_key, atomically.
489 *
490 * It is considered a programming error if @object is not found in @bag.
491 * In such case the function will emit a terminal warning and return.
492 **/
493 void
camel_object_bag_rekey(CamelObjectBag * bag,gpointer object,gconstpointer new_key)494 camel_object_bag_rekey (CamelObjectBag *bag,
495 gpointer object,
496 gconstpointer new_key)
497 {
498 gpointer key;
499 ObjRef *ref;
500
501 g_return_if_fail (bag != NULL);
502 g_return_if_fail (G_IS_OBJECT (object));
503 g_return_if_fail (new_key != NULL);
504
505 g_mutex_lock (&bag->mutex);
506
507 key = g_hash_table_lookup (bag->key_table, object);
508 if (key != NULL) {
509 /* Remove the old key. */
510 ref = g_hash_table_lookup (bag->object_table, key);
511 g_hash_table_steal (bag->object_table, key);
512 g_hash_table_remove (bag->key_table, object);
513
514 /* Insert the new key. */
515 key = bag->key_copy_func (new_key);
516 g_hash_table_insert (bag->object_table, key, ref);
517 g_hash_table_insert (bag->key_table, object, key);
518 } else
519 g_warn_if_reached ();
520
521 g_mutex_unlock (&bag->mutex);
522 }
523
524 /**
525 * camel_object_bag_list:
526 * @bag: a #CamelObjectBag
527 *
528 * Returns a #GPtrArray of all the objects in the bag. The caller owns
529 * both the array and the object references, so to free the array use:
530 *
531 * |[
532 * g_ptr_array_foreach (array, (GFunc) g_object_unref, NULL);
533 * g_ptr_array_free (array, TRUE);
534 * ]|
535 *
536 * Returns: (element-type GObject) (transfer full): an array of objects in @bag
537 **/
538 GPtrArray *
camel_object_bag_list(CamelObjectBag * bag)539 camel_object_bag_list (CamelObjectBag *bag)
540 {
541 GPtrArray *array;
542 GList *values;
543
544 g_return_val_if_fail (bag != NULL, NULL);
545
546 /* XXX Too bad we're not returning a GList; this would be trivial. */
547
548 array = g_ptr_array_new ();
549
550 g_mutex_lock (&bag->mutex);
551
552 values = g_hash_table_get_values (bag->object_table);
553 while (values != NULL) {
554 ObjRef *ref = values->data;
555 GObject *obj = g_weak_ref_get (&ref->ref);
556 if (obj)
557 g_ptr_array_add (array, obj);
558 values = g_list_delete_link (values, values);
559 }
560
561 g_mutex_unlock (&bag->mutex);
562
563 return array;
564 }
565
566 /**
567 * camel_object_bag_remove:
568 * @bag: a #CamelObjectBag
569 * @object: a #GObject
570 *
571 * Removes @object from @bag.
572 **/
573 void
camel_object_bag_remove(CamelObjectBag * bag,gpointer object)574 camel_object_bag_remove (CamelObjectBag *bag,
575 gpointer object)
576 {
577 gpointer key;
578
579 g_return_if_fail (bag != NULL);
580 g_return_if_fail (G_IS_OBJECT (object));
581
582 g_mutex_lock (&bag->mutex);
583
584 key = g_hash_table_lookup (bag->key_table, object);
585 if (key != NULL) {
586 g_hash_table_remove (bag->key_table, object);
587 g_hash_table_remove (bag->object_table, key);
588 }
589
590 g_mutex_unlock (&bag->mutex);
591 }
592
593 /**
594 * camel_object_bag_destroy:
595 * @bag: a #CamelObjectBag
596 *
597 * Frees @bag. As a precaution, the function will emit a warning to standard
598 * error and return without freeing @bag if @bag still has reserved keys.
599 **/
600 void
camel_object_bag_destroy(CamelObjectBag * bag)601 camel_object_bag_destroy (CamelObjectBag *bag)
602 {
603 g_return_if_fail (bag != NULL);
604 g_return_if_fail (bag->reserved == NULL);
605
606 g_hash_table_destroy (bag->key_table);
607 g_hash_table_destroy (bag->object_table);
608 g_mutex_clear (&bag->mutex);
609 g_slice_free (CamelObjectBag, bag);
610 }
611