updated changelog
[platform/upstream/evolution-data-server.git] / camel / camel-object-bag.c
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 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 #include "camel-object-bag.h"
19
20 #include <glib-object.h>
21
22 typedef struct _KeyReservation KeyReservation;
23
24 struct _KeyReservation {
25         gpointer key;
26         gint waiters;
27         GThread *owner;
28         GCond cond;
29 };
30
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 */
38         GMutex mutex;
39 };
40
41 static KeyReservation *
42 key_reservation_new (CamelObjectBag *bag,
43                      gconstpointer key)
44 {
45         KeyReservation *reservation;
46
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);
51
52         bag->reserved = g_list_prepend (bag->reserved, reservation);
53
54         return reservation;
55 }
56
57 static KeyReservation *
58 key_reservation_lookup (CamelObjectBag *bag,
59                         gconstpointer key)
60 {
61         GList *iter;
62
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))
67                         return reservation;
68         }
69
70         return NULL;
71 }
72
73 static void
74 key_reservation_free (CamelObjectBag *bag,
75                       KeyReservation *reservation)
76 {
77         /* Make sure the reservation is actually in the object bag. */
78         g_return_if_fail (key_reservation_lookup (bag, reservation->key) != NULL);
79
80         bag->reserved = g_list_remove (bag->reserved, reservation);
81
82         bag->key_free_func (reservation->key);
83         g_cond_clear (&reservation->cond);
84         g_slice_free (KeyReservation, reservation);
85 }
86
87 static void
88 object_bag_unreserve (CamelObjectBag *bag,
89                       gconstpointer key)
90 {
91         KeyReservation *reservation;
92
93         reservation = key_reservation_lookup (bag, key);
94         g_return_if_fail (reservation != NULL);
95         g_return_if_fail (reservation->owner == g_thread_self ());
96
97         if (reservation->waiters > 0) {
98                 reservation->owner = NULL;
99                 g_cond_signal (&reservation->cond);
100         } else
101                 key_reservation_free (bag, reservation);
102 }
103
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. */
109 typedef struct {
110         GWeakRef ref;
111         gpointer obj;
112         CamelObjectBag *bag;
113 } ObjRef;
114
115 static void
116 object_bag_notify (CamelObjectBag *bag,
117                    GObject *where_the_object_was)
118 {
119         gconstpointer key;
120
121         g_mutex_lock (&bag->mutex);
122
123         key = g_hash_table_lookup (bag->key_table, where_the_object_was);
124         if (key != NULL) {
125                 g_hash_table_remove (bag->key_table, where_the_object_was);
126                 g_hash_table_remove (bag->object_table, key);
127         }
128
129         g_mutex_unlock (&bag->mutex);
130 }
131
132 /* Properly destroy an ObjRef as it's freed from the hash table */
133 static void
134 wref_free_func (gpointer p)
135 {
136         ObjRef *ref = p;
137         GObject *obj = g_weak_ref_get (&ref->ref);
138
139         if (obj) {
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,
145                         ref->bag);
146                 g_object_unref (obj);
147         }
148         g_weak_ref_clear (&ref->ref);
149         g_slice_free (ObjRef, ref);
150 }
151
152 /**
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
158  *
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.
162  *
163  * Returns: a newly-allocated #CamelObjectBag
164  **/
165 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)
170 {
171         CamelObjectBag *bag;
172         GHashTable *key_table;
173         GHashTable *object_table;
174
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);
179
180         /* Each key is shared between both hash tables, so only one
181          * table needs to be responsible for destroying keys. */
182
183         key_table = g_hash_table_new (g_direct_hash, g_direct_equal);
184
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);
189
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);
197
198         return bag;
199 }
200
201 /**
202  * camel_object_bag_get:
203  * @bag: a #CamelObjectBag
204  * @key: a key
205  *
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 ()
209  * to unreference it.
210  *
211  * Returns: the object corresponding to @key, or %NULL if not found
212  **/
213 gpointer
214 camel_object_bag_get (CamelObjectBag *bag,
215                       gconstpointer key)
216 {
217         KeyReservation *reservation;
218         ObjRef *ref;
219         gpointer object = NULL;
220
221         g_return_val_if_fail (bag != NULL, NULL);
222         g_return_val_if_fail (key != NULL, NULL);
223
224         g_mutex_lock (&bag->mutex);
225
226         /* Look for the key in the bag. */
227         ref = g_hash_table_lookup (bag->object_table, key);
228         if (ref != NULL) {
229                 object = g_weak_ref_get (&ref->ref);
230                 if (object != NULL) {
231                         g_mutex_unlock (&bag->mutex);
232                         return object;
233                 }
234
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);
238         }
239
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);
245                 return NULL;
246         }
247
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--;
253
254         /* Check if an object was added by another thread. */
255         ref = g_hash_table_lookup (bag->object_table, key);
256         if (ref != NULL) {
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);
262                 }
263         }
264
265         /* We're not reserving it. */
266         reservation->owner = g_thread_self ();
267         object_bag_unreserve (bag, key);
268
269         g_mutex_unlock (&bag->mutex);
270
271         return object;
272 }
273
274 /**
275  * camel_object_bag_peek:
276  * @bag: a #CamelObjectBag
277  * @key: an unreserved key
278  *
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.
282  *
283  * Unlink other "peek" operations, the caller owns the returned object
284  * reference.  Use g_object_unref () to unreference it.
285  *
286  * Returns: the object for @key, or %NULL if @key is reserved or not found
287  **/
288 gpointer
289 camel_object_bag_peek (CamelObjectBag *bag,
290                        gconstpointer key)
291 {
292         gpointer object = NULL;
293         ObjRef *ref;
294
295         g_return_val_if_fail (bag != NULL, NULL);
296         g_return_val_if_fail (key != NULL, NULL);
297
298         g_mutex_lock (&bag->mutex);
299
300         ref = g_hash_table_lookup (bag->object_table, key);
301         if (ref != NULL)
302                 object = g_weak_ref_get (&ref->ref);
303
304         g_mutex_unlock (&bag->mutex);
305
306         return object;
307 }
308
309 /**
310  * camel_object_bag_reserve:
311  * @bag: a #CamelObjectBag
312  * @key: the key to reserve
313  *
314  * Reserves @key in @bag.  If @key is already reserved in another thread,
315  * then wait until the reservation has been committed.
316  *
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().
321  *
322  * Returns: the object for @key, or %NULL if @key is not found
323  **/
324 gpointer
325 camel_object_bag_reserve (CamelObjectBag *bag,
326                           gconstpointer key)
327 {
328         KeyReservation *reservation;
329         ObjRef *ref;
330         gpointer object = NULL;
331
332         g_return_val_if_fail (bag != NULL, NULL);
333         g_return_val_if_fail (key != NULL, NULL);
334
335         g_mutex_lock (&bag->mutex);
336
337         /* If object for key already exists, return it immediately. */
338         ref = g_hash_table_lookup (bag->object_table, key);
339         if (ref != NULL) {
340                 object = g_weak_ref_get (&ref->ref);
341                 if (object != NULL) {
342                         g_mutex_unlock (&bag->mutex);
343                         return object;
344                 }
345
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);
349         }
350
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);
356                 return NULL;
357         }
358
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--;
365
366         /* Check if the object was added by another thread. */
367         ref = g_hash_table_lookup (bag->object_table, key);
368         if (ref != NULL) {
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);
373                 } else {
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);
377                 }
378         }
379
380         g_mutex_unlock (&bag->mutex);
381
382         return object;
383 }
384
385 static gboolean
386 object_in_bag (CamelObjectBag *bag,
387                gpointer object)
388 {
389         gconstpointer key;
390         ObjRef *ref;
391         GObject *obj2;
392
393         key = g_hash_table_lookup (bag->key_table, object);
394         if (key == NULL)
395                 return FALSE;
396
397         ref = g_hash_table_lookup (bag->object_table, key);
398         if (ref == NULL)
399                 return FALSE;
400
401         obj2 = g_weak_ref_get (&ref->ref);
402         if (obj2 == NULL) {
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);
406         } else {
407                 g_object_unref (obj2);
408         }
409
410         return obj2 == object;
411 }
412
413 /**
414  * camel_object_bag_add:
415  * @bag: a #CamelObjectBag
416  * @key: a reserved key
417  * @object: a #GObject
418  *
419  * Adds @object to @bag.  The @key MUST have been previously reserved using
420  * camel_object_bag_reserve().
421  **/
422 void
423 camel_object_bag_add (CamelObjectBag *bag,
424                       gconstpointer key,
425                       gpointer object)
426 {
427         g_return_if_fail (bag != NULL);
428         g_return_if_fail (key != NULL);
429         g_return_if_fail (G_IS_OBJECT (object));
430
431         g_mutex_lock (&bag->mutex);
432
433         /* Check it's the *same* object, not an old one at the same address */
434         if (!object_in_bag (bag, object)) {
435                 ObjRef *ref;
436                 gpointer copied_key;
437
438                 ref = g_slice_new (ObjRef);
439                 ref->bag = bag;
440                 /* We need to stash a 'raw' pointer since that's the key we use
441                  * in the key_table */
442                 ref->obj = object;
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);
448
449                 g_object_weak_ref (
450                         G_OBJECT (object),
451                         (GWeakNotify) object_bag_notify, bag);
452         }
453
454         g_mutex_unlock (&bag->mutex);
455 }
456
457 /**
458  * camel_object_bag_abort:
459  * @bag: a #CamelObjectBag
460  * @key: a reserved key
461  *
462  * Aborts a key reservation.
463  **/
464 void
465 camel_object_bag_abort (CamelObjectBag *bag,
466                         gconstpointer key)
467 {
468         g_return_if_fail (bag != NULL);
469         g_return_if_fail (key != NULL);
470
471         g_mutex_lock (&bag->mutex);
472
473         object_bag_unreserve (bag, key);
474
475         g_mutex_unlock (&bag->mutex);
476 }
477
478 /**
479  * camel_object_bag_rekey:
480  * @bag: a #CamelObjectBag
481  * @object: a #GObject
482  * @new_key: a new key for @object
483  *
484  * Changes the key for @object to @new_key, atomically.
485  *
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.
488  **/
489 void
490 camel_object_bag_rekey (CamelObjectBag *bag,
491                         gpointer object,
492                         gconstpointer new_key)
493 {
494         gpointer key;
495         ObjRef *ref;
496
497         g_return_if_fail (bag != NULL);
498         g_return_if_fail (G_IS_OBJECT (object));
499         g_return_if_fail (new_key != NULL);
500
501         g_mutex_lock (&bag->mutex);
502
503         key = g_hash_table_lookup (bag->key_table, object);
504         if (key != NULL) {
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);
509
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);
514         } else
515                 g_warn_if_reached ();
516
517         g_mutex_unlock (&bag->mutex);
518 }
519
520 /**
521  * camel_object_bag_list:
522  * @bag: a #CamelObjectBag
523  *
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:
526  *
527  * <informalexample>
528  *   <programlisting>
529  *     g_ptr_array_foreach (array, g_object_unref, NULL);
530  *     g_ptr_array_free (array, TRUE);
531  *   </programlisting>
532  * </informalexample>
533  *
534  * Returns: an array of objects in @bag
535  **/
536 GPtrArray *
537 camel_object_bag_list (CamelObjectBag *bag)
538 {
539         GPtrArray *array;
540         GList *values;
541
542         g_return_val_if_fail (bag != NULL, NULL);
543
544         /* XXX Too bad we're not returning a GList; this would be trivial. */
545
546         array = g_ptr_array_new ();
547
548         g_mutex_lock (&bag->mutex);
549
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);
554                 if (obj)
555                         g_ptr_array_add (array, obj);
556                 values = g_list_delete_link (values, values);
557         }
558
559         g_mutex_unlock (&bag->mutex);
560
561         return array;
562 }
563
564 /**
565  * camel_object_bag_remove:
566  * @bag: a #CamelObjectBag
567  * @object: a #GObject
568  *
569  * Removes @object from @bag.
570  **/
571 void
572 camel_object_bag_remove (CamelObjectBag *bag,
573                          gpointer object)
574 {
575         gpointer key;
576
577         g_return_if_fail (bag != NULL);
578         g_return_if_fail (G_IS_OBJECT (object));
579
580         g_mutex_lock (&bag->mutex);
581
582         key = g_hash_table_lookup (bag->key_table, object);
583         if (key != NULL) {
584                 g_hash_table_remove (bag->key_table, object);
585                 g_hash_table_remove (bag->object_table, key);
586         }
587
588         g_mutex_unlock (&bag->mutex);
589 }
590
591 /**
592  * camel_object_bag_destroy:
593  * @bag: a #CamelObjectBag
594  *
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.
597  **/
598 void
599 camel_object_bag_destroy (CamelObjectBag *bag)
600 {
601         g_return_if_fail (bag != NULL);
602         g_return_if_fail (bag->reserved == NULL);
603
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);
608 }