1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2008 Novell, Inc.
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.
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 General Public License
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/>.
18 #include "camel-object-bag.h"
20 #include <glib-object.h>
22 typedef struct _KeyReservation KeyReservation;
24 struct _KeyReservation {
31 struct _CamelObjectBag {
32 GHashTable *key_table;
33 GHashTable *object_table;
34 GEqualFunc key_equal_func;
35 CamelCopyFunc key_copy_func;
36 GFreeFunc key_free_func;
37 GList *reserved; /* list of KeyReservations */
41 static KeyReservation *
42 key_reservation_new (CamelObjectBag *bag,
45 KeyReservation *reservation;
47 reservation = g_slice_new0 (KeyReservation);
48 reservation->key = bag->key_copy_func (key);
49 reservation->owner = g_thread_self ();
50 g_cond_init (&reservation->cond);
52 bag->reserved = g_list_prepend (bag->reserved, reservation);
57 static KeyReservation *
58 key_reservation_lookup (CamelObjectBag *bag,
63 /* XXX Might be easier to use a GHashTable for reservations. */
64 for (iter = bag->reserved; iter != NULL; iter = iter->next) {
65 KeyReservation *reservation = iter->data;
66 if (bag->key_equal_func (reservation->key, key))
74 key_reservation_free (CamelObjectBag *bag,
75 KeyReservation *reservation)
77 /* Make sure the reservation is actually in the object bag. */
78 g_return_if_fail (key_reservation_lookup (bag, reservation->key) != NULL);
80 bag->reserved = g_list_remove (bag->reserved, reservation);
82 bag->key_free_func (reservation->key);
83 g_cond_clear (&reservation->cond);
84 g_slice_free (KeyReservation, reservation);
88 object_bag_unreserve (CamelObjectBag *bag,
91 KeyReservation *reservation;
93 reservation = key_reservation_lookup (bag, key);
94 g_return_if_fail (reservation != NULL);
95 g_return_if_fail (reservation->owner == g_thread_self ());
97 if (reservation->waiters > 0) {
98 reservation->owner = NULL;
99 g_cond_signal (&reservation->cond);
101 key_reservation_free (bag, reservation);
104 /* Ick. We need to store the original gobject pointer too, since that's
105 * used as the key in one of the hash tables. So to clean up after the
106 * object dies and the GWeakRef starts returning NULL, we'll need to
107 * know where it *was*. This is safe because we'll always check and
108 * avoid adding a duplicate. But still, ick. */
116 object_bag_notify (CamelObjectBag *bag,
117 GObject *where_the_object_was)
121 g_mutex_lock (&bag->mutex);
123 key = g_hash_table_lookup (bag->key_table, where_the_object_was);
125 g_hash_table_remove (bag->key_table, where_the_object_was);
126 g_hash_table_remove (bag->object_table, key);
129 g_mutex_unlock (&bag->mutex);
132 /* Properly destroy an ObjRef as it's freed from the hash table */
134 wref_free_func (gpointer p)
137 GObject *obj = g_weak_ref_get (&ref->ref);
140 /* The object is being removed from the bag while it's
141 * still alive, e.g. by camel_object_bag_remove()
142 * or camel_object_bag_destroy(). Drop the weak_ref. */
143 g_object_weak_unref (
144 obj, (GWeakNotify) object_bag_notify,
146 g_object_unref (obj);
148 g_weak_ref_clear (&ref->ref);
149 g_slice_free (ObjRef, ref);
153 * camel_object_bag_new:
154 * @key_hash_func: a hashing function for keys
155 * @key_equal_func: a comparison function for keys
156 * @key_copy_func: a function to copy keys
157 * @key_free_func: a function to free keys
159 * Returns a new object bag. Object bags are keyed hash tables of objects
160 * that can be updated atomically using transaction semantics. Use
161 * camel_object_bag_destroy() to free the object bag.
163 * Returns: a newly-allocated #CamelObjectBag
166 camel_object_bag_new (GHashFunc key_hash_func,
167 GEqualFunc key_equal_func,
168 CamelCopyFunc key_copy_func,
169 GFreeFunc key_free_func)
172 GHashTable *key_table;
173 GHashTable *object_table;
175 g_return_val_if_fail (key_hash_func != NULL, NULL);
176 g_return_val_if_fail (key_equal_func != NULL, NULL);
177 g_return_val_if_fail (key_copy_func != NULL, NULL);
178 g_return_val_if_fail (key_free_func != NULL, NULL);
180 /* Each key is shared between both hash tables, so only one
181 * table needs to be responsible for destroying keys. */
183 key_table = g_hash_table_new (g_direct_hash, g_direct_equal);
185 object_table = g_hash_table_new_full (
186 key_hash_func, key_equal_func,
187 (GDestroyNotify) key_free_func,
188 (GDestroyNotify) wref_free_func);
190 bag = g_slice_new0 (CamelObjectBag);
191 bag->key_table = key_table;
192 bag->object_table = object_table;
193 bag->key_equal_func = key_equal_func;
194 bag->key_copy_func = key_copy_func;
195 bag->key_free_func = key_free_func;
196 g_mutex_init (&bag->mutex);
202 * camel_object_bag_get:
203 * @bag: a #CamelObjectBag
206 * Lookup an object by @key. If the key is currently reserved, the function
207 * will block until another thread commits or aborts the reservation. The
208 * caller owns the reference to the returned object. Use g_object_unref ()
211 * Returns: the object corresponding to @key, or %NULL if not found
214 camel_object_bag_get (CamelObjectBag *bag,
217 KeyReservation *reservation;
219 gpointer object = NULL;
221 g_return_val_if_fail (bag != NULL, NULL);
222 g_return_val_if_fail (key != NULL, NULL);
224 g_mutex_lock (&bag->mutex);
226 /* Look for the key in the bag. */
227 ref = g_hash_table_lookup (bag->object_table, key);
229 object = g_weak_ref_get (&ref->ref);
230 if (object != NULL) {
231 g_mutex_unlock (&bag->mutex);
235 /* Remove stale reference to dead object. */
236 g_hash_table_remove (bag->key_table, ref->obj);
237 g_hash_table_remove (bag->object_table, key);
240 /* Check if the key has been reserved. */
241 reservation = key_reservation_lookup (bag, key);
242 if (reservation == NULL) {
243 /* No such key, so return NULL. */
244 g_mutex_unlock (&bag->mutex);
248 /* Wait for the key to be unreserved. */
249 reservation->waiters++;
250 while (reservation->owner != NULL)
251 g_cond_wait (&reservation->cond, &bag->mutex);
252 reservation->waiters--;
254 /* Check if an object was added by another thread. */
255 ref = g_hash_table_lookup (bag->object_table, key);
257 object = g_weak_ref_get (&ref->ref);
258 if (object == NULL) {
259 /* Remove stale reference to dead object. */
260 g_hash_table_remove (bag->key_table, ref->obj);
261 g_hash_table_remove (bag->object_table, key);
265 /* We're not reserving it. */
266 reservation->owner = g_thread_self ();
267 object_bag_unreserve (bag, key);
269 g_mutex_unlock (&bag->mutex);
275 * camel_object_bag_peek:
276 * @bag: a #CamelObjectBag
277 * @key: an unreserved key
279 * Returns the object for @key in @bag, ignoring any reservations. If it
280 * isn't committed, then it isn't considered. This should only be used
281 * where reliable transactional-based state is not required.
283 * Unlink other "peek" operations, the caller owns the returned object
284 * reference. Use g_object_unref () to unreference it.
286 * Returns: the object for @key, or %NULL if @key is reserved or not found
289 camel_object_bag_peek (CamelObjectBag *bag,
292 gpointer object = NULL;
295 g_return_val_if_fail (bag != NULL, NULL);
296 g_return_val_if_fail (key != NULL, NULL);
298 g_mutex_lock (&bag->mutex);
300 ref = g_hash_table_lookup (bag->object_table, key);
302 object = g_weak_ref_get (&ref->ref);
304 g_mutex_unlock (&bag->mutex);
310 * camel_object_bag_reserve:
311 * @bag: a #CamelObjectBag
312 * @key: the key to reserve
314 * Reserves @key in @bag. If @key is already reserved in another thread,
315 * then wait until the reservation has been committed.
317 * After reserving @key, you either get a reference to the object
318 * corresponding to @key (similar to camel_object_bag_get()) or you get
319 * %NULL, signifying that you MUST call either camel_object_bag_add() or
320 * camel_object_bag_abort().
322 * Returns: the object for @key, or %NULL if @key is not found
325 camel_object_bag_reserve (CamelObjectBag *bag,
328 KeyReservation *reservation;
330 gpointer object = NULL;
332 g_return_val_if_fail (bag != NULL, NULL);
333 g_return_val_if_fail (key != NULL, NULL);
335 g_mutex_lock (&bag->mutex);
337 /* If object for key already exists, return it immediately. */
338 ref = g_hash_table_lookup (bag->object_table, key);
340 object = g_weak_ref_get (&ref->ref);
341 if (object != NULL) {
342 g_mutex_unlock (&bag->mutex);
346 /* Remove stale reference to dead object. */
347 g_hash_table_remove (bag->key_table, ref->obj);
348 g_hash_table_remove (bag->object_table, key);
351 /* If no such key exists in the bag, create a reservation. */
352 reservation = key_reservation_lookup (bag, key);
353 if (reservation == NULL) {
354 key_reservation_new (bag, key);
355 g_mutex_unlock (&bag->mutex);
359 /* Wait for the reservation to be committed or aborted. */
360 reservation->waiters++;
361 while (reservation->owner != NULL)
362 g_cond_wait (&reservation->cond, &bag->mutex);
363 reservation->owner = g_thread_self ();
364 reservation->waiters--;
366 /* Check if the object was added by another thread. */
367 ref = g_hash_table_lookup (bag->object_table, key);
369 object = g_weak_ref_get (&ref->ref);
370 if (object != NULL) {
371 /* We have an object; no need to reserve the key. */
372 object_bag_unreserve (bag, key);
374 /* Remove stale reference to dead object. */
375 g_hash_table_remove (bag->key_table, ref->obj);
376 g_hash_table_remove (bag->object_table, key);
380 g_mutex_unlock (&bag->mutex);
386 object_in_bag (CamelObjectBag *bag,
393 key = g_hash_table_lookup (bag->key_table, object);
397 ref = g_hash_table_lookup (bag->object_table, key);
401 obj2 = g_weak_ref_get (&ref->ref);
403 /* Remove stale reference to dead object. */
404 g_hash_table_remove (bag->key_table, object);
405 g_hash_table_remove (bag->object_table, key);
407 g_object_unref (obj2);
410 return obj2 == object;
414 * camel_object_bag_add:
415 * @bag: a #CamelObjectBag
416 * @key: a reserved key
417 * @object: a #GObject
419 * Adds @object to @bag. The @key MUST have been previously reserved using
420 * camel_object_bag_reserve().
423 camel_object_bag_add (CamelObjectBag *bag,
427 g_return_if_fail (bag != NULL);
428 g_return_if_fail (key != NULL);
429 g_return_if_fail (G_IS_OBJECT (object));
431 g_mutex_lock (&bag->mutex);
433 /* Check it's the *same* object, not an old one at the same address */
434 if (!object_in_bag (bag, object)) {
438 ref = g_slice_new (ObjRef);
440 /* We need to stash a 'raw' pointer since that's the key we use
441 * in the key_table */
443 g_weak_ref_init (&ref->ref, object);
444 copied_key = bag->key_copy_func (key);
445 g_hash_table_insert (bag->key_table, object, copied_key);
446 g_hash_table_insert (bag->object_table, copied_key, ref);
447 object_bag_unreserve (bag, key);
451 (GWeakNotify) object_bag_notify, bag);
454 g_mutex_unlock (&bag->mutex);
458 * camel_object_bag_abort:
459 * @bag: a #CamelObjectBag
460 * @key: a reserved key
462 * Aborts a key reservation.
465 camel_object_bag_abort (CamelObjectBag *bag,
468 g_return_if_fail (bag != NULL);
469 g_return_if_fail (key != NULL);
471 g_mutex_lock (&bag->mutex);
473 object_bag_unreserve (bag, key);
475 g_mutex_unlock (&bag->mutex);
479 * camel_object_bag_rekey:
480 * @bag: a #CamelObjectBag
481 * @object: a #GObject
482 * @new_key: a new key for @object
484 * Changes the key for @object to @new_key, atomically.
486 * It is considered a programming error if @object is not found in @bag.
487 * In such case the function will emit a terminal warning and return.
490 camel_object_bag_rekey (CamelObjectBag *bag,
492 gconstpointer new_key)
497 g_return_if_fail (bag != NULL);
498 g_return_if_fail (G_IS_OBJECT (object));
499 g_return_if_fail (new_key != NULL);
501 g_mutex_lock (&bag->mutex);
503 key = g_hash_table_lookup (bag->key_table, object);
505 /* Remove the old key. */
506 ref = g_hash_table_lookup (bag->object_table, key);
507 g_hash_table_steal (bag->object_table, key);
508 g_hash_table_remove (bag->key_table, object);
510 /* Insert the new key. */
511 key = bag->key_copy_func (new_key);
512 g_hash_table_insert (bag->object_table, key, ref);
513 g_hash_table_insert (bag->key_table, object, key);
515 g_warn_if_reached ();
517 g_mutex_unlock (&bag->mutex);
521 * camel_object_bag_list:
522 * @bag: a #CamelObjectBag
524 * Returns a #GPtrArray of all the objects in the bag. The caller owns
525 * both the array and the object references, so to free the array use:
529 * g_ptr_array_foreach (array, g_object_unref, NULL);
530 * g_ptr_array_free (array, TRUE);
534 * Returns: an array of objects in @bag
537 camel_object_bag_list (CamelObjectBag *bag)
542 g_return_val_if_fail (bag != NULL, NULL);
544 /* XXX Too bad we're not returning a GList; this would be trivial. */
546 array = g_ptr_array_new ();
548 g_mutex_lock (&bag->mutex);
550 values = g_hash_table_get_values (bag->object_table);
551 while (values != NULL) {
552 ObjRef *ref = values->data;
553 GObject *obj = g_weak_ref_get (&ref->ref);
555 g_ptr_array_add (array, obj);
556 values = g_list_delete_link (values, values);
559 g_mutex_unlock (&bag->mutex);
565 * camel_object_bag_remove:
566 * @bag: a #CamelObjectBag
567 * @object: a #GObject
569 * Removes @object from @bag.
572 camel_object_bag_remove (CamelObjectBag *bag,
577 g_return_if_fail (bag != NULL);
578 g_return_if_fail (G_IS_OBJECT (object));
580 g_mutex_lock (&bag->mutex);
582 key = g_hash_table_lookup (bag->key_table, object);
584 g_hash_table_remove (bag->key_table, object);
585 g_hash_table_remove (bag->object_table, key);
588 g_mutex_unlock (&bag->mutex);
592 * camel_object_bag_destroy:
593 * @bag: a #CamelObjectBag
595 * Frees @bag. As a precaution, the function will emit a warning to standard
596 * error and return without freeing @bag if @bag still has reserved keys.
599 camel_object_bag_destroy (CamelObjectBag *bag)
601 g_return_if_fail (bag != NULL);
602 g_return_if_fail (bag->reserved == NULL);
604 g_hash_table_destroy (bag->key_table);
605 g_hash_table_destroy (bag->object_table);
606 g_mutex_clear (&bag->mutex);
607 g_slice_free (CamelObjectBag, bag);