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
6 * modify it under the terms of version 2 of the GNU Lesser General Public
7 * License as published by the Free Software Foundation.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
20 #include "camel-object-bag.h"
22 #include <glib-object.h>
24 typedef struct _KeyReservation KeyReservation;
26 struct _KeyReservation {
33 struct _CamelObjectBag {
34 GHashTable *key_table;
35 GHashTable *object_table;
36 GEqualFunc key_equal_func;
37 CamelCopyFunc key_copy_func;
38 GFreeFunc key_free_func;
39 GSList *reserved; /* list of KeyReservations */
43 static KeyReservation *
44 key_reservation_new (CamelObjectBag *bag,
47 KeyReservation *reservation;
49 reservation = g_slice_new0 (KeyReservation);
50 reservation->key = bag->key_copy_func (key);
51 reservation->owner = g_thread_self ();
52 reservation->cond = g_cond_new ();
54 bag->reserved = g_slist_prepend (bag->reserved, reservation);
59 static KeyReservation *
60 key_reservation_lookup (CamelObjectBag *bag,
65 /* XXX Might be easier to use a GHashTable for reservations. */
66 for (iter = bag->reserved; iter != NULL; iter = iter->next) {
67 KeyReservation *reservation = iter->data;
68 if (bag->key_equal_func (reservation->key, key))
76 key_reservation_free (CamelObjectBag *bag,
77 KeyReservation *reservation)
79 /* Make sure the reservation is actually in the object bag. */
80 g_return_if_fail (key_reservation_lookup (bag, reservation->key) != NULL);
82 bag->reserved = g_slist_remove (bag->reserved, reservation);
84 bag->key_free_func (reservation->key);
85 g_cond_free (reservation->cond);
86 g_slice_free (KeyReservation, reservation);
90 object_bag_notify (CamelObjectBag *bag,
91 GObject *where_the_object_was)
95 g_mutex_lock (bag->mutex);
97 key = g_hash_table_lookup (bag->key_table, where_the_object_was);
99 g_hash_table_remove (bag->key_table, where_the_object_was);
100 g_hash_table_remove (bag->object_table, key);
103 g_mutex_unlock (bag->mutex);
107 object_bag_weak_unref (gpointer key,
111 g_object_weak_unref (object, (GWeakNotify) object_bag_notify, bag);
115 object_bag_unreserve (CamelObjectBag *bag,
118 KeyReservation *reservation;
120 reservation = key_reservation_lookup (bag, key);
121 g_return_if_fail (reservation != NULL);
122 g_return_if_fail (reservation->owner == g_thread_self ());
124 if (reservation->waiters > 0) {
125 reservation->owner = NULL;
126 g_cond_signal (reservation->cond);
128 key_reservation_free (bag, reservation);
132 * camel_object_bag_new:
133 * @key_hash_func: a hashing function for keys
134 * @key_equal_func: a comparison function for keys
135 * @key_copy_func: a function to copy keys
136 * @key_free_func: a function to free keys
138 * Returns a new object bag. Object bags are keyed hash tables of objects
139 * that can be updated atomically using transaction semantics. Use
140 * camel_object_bag_destroy() to free the object bag.
142 * Returns: a newly-allocated #CamelObjectBag
145 camel_object_bag_new (GHashFunc key_hash_func,
146 GEqualFunc key_equal_func,
147 CamelCopyFunc key_copy_func,
148 GFreeFunc key_free_func)
151 GHashTable *key_table;
152 GHashTable *object_table;
154 g_return_val_if_fail (key_hash_func != NULL, NULL);
155 g_return_val_if_fail (key_equal_func != NULL, NULL);
156 g_return_val_if_fail (key_copy_func != NULL, NULL);
157 g_return_val_if_fail (key_free_func != NULL, NULL);
159 /* Each key is shared between both hash tables, so only one
160 * table needs to be responsible for destroying keys. */
162 key_table = g_hash_table_new (g_direct_hash, g_direct_equal);
164 object_table = g_hash_table_new_full (
165 key_hash_func, key_equal_func,
166 (GDestroyNotify) key_free_func,
167 (GDestroyNotify) NULL);
169 bag = g_slice_new0 (CamelObjectBag);
170 bag->key_table = key_table;
171 bag->object_table = object_table;
172 bag->key_equal_func = key_equal_func;
173 bag->key_copy_func = key_copy_func;
174 bag->key_free_func = key_free_func;
175 bag->mutex = g_mutex_new ();
181 * camel_object_bag_get:
182 * @bag: a #CamelObjectBag
185 * Lookup an object by @key. If the key is currently reserved, the function
186 * will block until another thread commits or aborts the reservation. The
187 * caller owns the reference to the returned object. Use g_object_unref ()
190 * Returns: the object corresponding to @key, or %NULL if not found
193 camel_object_bag_get (CamelObjectBag *bag,
196 KeyReservation *reservation;
199 g_return_val_if_fail (bag != NULL, NULL);
200 g_return_val_if_fail (key != NULL, NULL);
202 g_mutex_lock (bag->mutex);
204 /* Look for the key in the bag. */
205 object = g_hash_table_lookup (bag->object_table, key);
206 if (object != NULL) {
207 g_object_ref (object);
208 g_mutex_unlock (bag->mutex);
212 /* Check if the key has been reserved. */
213 reservation = key_reservation_lookup (bag, key);
214 if (reservation == NULL) {
215 /* No such key, so return NULL. */
216 g_mutex_unlock (bag->mutex);
220 /* Wait for the key to be unreserved. */
221 reservation->waiters++;
222 while (reservation->owner != NULL)
223 g_cond_wait (reservation->cond, bag->mutex);
224 reservation->waiters--;
226 /* Check if an object was added by another thread. */
227 object = g_hash_table_lookup (bag->object_table, key);
229 g_object_ref (object);
231 /* We're not reserving it. */
232 reservation->owner = g_thread_self ();
233 object_bag_unreserve (bag, key);
235 g_mutex_unlock (bag->mutex);
241 * camel_object_bag_peek:
242 * @bag: a #CamelObjectBag
243 * @key: an unreserved key
245 * Returns the object for @key in @bag, ignoring any reservations. If it
246 * isn't committed, then it isn't considered. This should only be used
247 * where reliable transactional-based state is not required.
249 * Unlink other "peek" operations, the caller owns the returned object
250 * reference. Use g_object_unref () to unreference it.
252 * Returns: the object for @key, or %NULL if @key is reserved or not found
255 camel_object_bag_peek (CamelObjectBag *bag,
260 g_return_val_if_fail (bag != NULL, NULL);
261 g_return_val_if_fail (key != NULL, NULL);
263 g_mutex_lock (bag->mutex);
265 object = g_hash_table_lookup (bag->object_table, key);
267 g_object_ref (object);
269 g_mutex_unlock (bag->mutex);
275 * camel_object_bag_reserve:
276 * @bag: a #CamelObjectBag
277 * @key: the key to reserve
279 * Reserves @key in @bag. If @key is already reserved in another thread,
280 * then wait until the reservation has been committed.
282 * After reserving @key, you either get a reference to the object
283 * corresponding to @key (similar to camel_object_bag_get()) or you get
284 * %NULL, signifying that you MUST call either camel_object_bag_add() or
285 * camel_object_bag_abort().
287 * Returns: the object for @key, or %NULL if @key is not found
290 camel_object_bag_reserve (CamelObjectBag *bag,
293 KeyReservation *reservation;
296 g_return_val_if_fail (bag != NULL, NULL);
297 g_return_val_if_fail (key != NULL, NULL);
299 g_mutex_lock (bag->mutex);
301 /* If object for key already exists, return it immediately. */
302 object = g_hash_table_lookup (bag->object_table, key);
303 if (object != NULL) {
304 g_object_ref (object);
305 g_mutex_unlock (bag->mutex);
309 /* If no such key exists in the bag, create a reservation. */
310 reservation = key_reservation_lookup (bag, key);
311 if (reservation == NULL) {
312 reservation = key_reservation_new (bag, key);
313 g_mutex_unlock (bag->mutex);
317 /* Wait for the reservation to be committed or aborted. */
318 reservation->waiters++;
319 while (reservation->owner != NULL)
320 g_cond_wait (reservation->cond, bag->mutex);
321 reservation->owner = g_thread_self ();
322 reservation->waiters--;
324 /* Check if the object was added by another thread. */
325 object = g_hash_table_lookup (bag->object_table, key);
326 if (object != NULL) {
327 /* We have an object; no need to reserve the key. */
328 object_bag_unreserve (bag, key);
329 g_object_ref (object);
332 g_mutex_unlock (bag->mutex);
338 * camel_object_bag_add:
339 * @bag: a #CamelObjectBag
340 * @key: a reserved key
341 * @object: a #GObject
343 * Adds @object to @bag. The @key MUST have been previously reserved using
344 * camel_object_bag_reserve().
347 camel_object_bag_add (CamelObjectBag *bag,
351 g_return_if_fail (bag != NULL);
352 g_return_if_fail (key != NULL);
353 g_return_if_fail (G_IS_OBJECT (object));
355 g_mutex_lock (bag->mutex);
357 if (g_hash_table_lookup (bag->key_table, object) == NULL) {
360 copied_key = bag->key_copy_func (key);
361 g_hash_table_insert (bag->key_table, object, copied_key);
362 g_hash_table_insert (bag->object_table, copied_key, object);
363 object_bag_unreserve (bag, key);
366 G_OBJECT (object), (GWeakNotify)
367 object_bag_notify, bag);
370 g_mutex_unlock (bag->mutex);
374 * camel_object_bag_abort:
375 * @bag: a #CamelObjectBag
376 * @key: a reserved key
378 * Aborts a key reservation.
381 camel_object_bag_abort (CamelObjectBag *bag,
384 g_return_if_fail (bag != NULL);
385 g_return_if_fail (key != NULL);
387 g_mutex_lock (bag->mutex);
389 object_bag_unreserve (bag, key);
391 g_mutex_unlock (bag->mutex);
395 * camel_object_bag_rekey:
396 * @bag: a #CamelObjectBag
397 * @object: a #GObject
398 * @new_key: a new key for @object
400 * Changes the key for @object to @new_key, atomically.
402 * It is considered a programming error if @object is not found in @bag.
403 * In such case the function will emit a terminal warning and return.
406 camel_object_bag_rekey (CamelObjectBag *bag,
408 gconstpointer new_key)
412 g_return_if_fail (bag != NULL);
413 g_return_if_fail (G_IS_OBJECT (object));
414 g_return_if_fail (new_key != NULL);
416 g_mutex_lock (bag->mutex);
418 key = g_hash_table_lookup (bag->key_table, object);
420 /* Remove the old key. */
421 g_hash_table_remove (bag->object_table, key);
422 g_hash_table_remove (bag->key_table, object);
424 /* Insert the new key. */
425 key = bag->key_copy_func (new_key);
426 g_hash_table_insert (bag->object_table, key, object);
427 g_hash_table_insert (bag->key_table, object, key);
429 g_warn_if_reached ();
431 g_mutex_unlock (bag->mutex);
435 * camel_object_bag_list:
436 * @bag: a #CamelObjectBag
438 * Returns a #GPtrArray of all the objects in the bag. The caller owns
439 * both the array and the object references, so to free the array use:
443 * g_ptr_array_foreach (array, g_object_unref, NULL);
444 * g_ptr_array_free (array, TRUE);
448 * Returns: an array of objects in @bag
451 camel_object_bag_list (CamelObjectBag *bag)
456 g_return_val_if_fail (bag != NULL, NULL);
458 /* XXX Too bad we're not returning a GList; this would be trivial. */
460 array = g_ptr_array_new ();
462 g_mutex_lock (bag->mutex);
464 values = g_hash_table_get_values (bag->object_table);
465 while (values != NULL) {
466 g_ptr_array_add (array, g_object_ref (values->data));
467 values = g_list_delete_link (values, values);
470 g_mutex_unlock (bag->mutex);
476 * camel_object_bag_remove:
477 * @bag: a #CamelObjectBag
478 * @object: a #GObject
480 * Removes @object from @bag.
483 camel_object_bag_remove (CamelObjectBag *bag,
488 g_return_if_fail (bag != NULL);
489 g_return_if_fail (G_IS_OBJECT (object));
491 g_mutex_lock (bag->mutex);
493 key = g_hash_table_lookup (bag->key_table, object);
495 object_bag_weak_unref (key, object, bag);
496 g_hash_table_remove (bag->key_table, object);
497 g_hash_table_remove (bag->object_table, key);
500 g_mutex_unlock (bag->mutex);
504 * camel_object_bag_destroy:
505 * @bag: a #CamelObjectBag
507 * Frees @bag. As a precaution, the function will emit a warning to standard
508 * error and return without freeing @bag if @bag still has reserved keys.
511 camel_object_bag_destroy (CamelObjectBag *bag)
513 g_return_if_fail (bag != NULL);
514 g_return_if_fail (bag->reserved == NULL);
516 /* Drop remaining weak references. */
517 g_hash_table_foreach (
518 bag->object_table, (GHFunc)
519 object_bag_weak_unref, bag);
521 g_hash_table_destroy (bag->key_table);
522 g_hash_table_destroy (bag->object_table);
523 g_mutex_free (bag->mutex);
524 g_slice_free (CamelObjectBag, bag);