there was a reference count race for hooks during invocation loops. since
[platform/upstream/glib.git] / glib / ghook.c
1 /* GLIB - Library of useful routines for C programming
2  * Copyright (C) 1995-1997  Peter Mattis, Spencer Kimball and Josh MacDonald
3  *
4  * GHook: Callback maintenance functions
5  * Copyright (C) 1998 Tim Janik
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public
18  * License along with this library; if not, write to the
19  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20  * Boston, MA 02111-1307, USA.
21  */
22
23 /* 
24  * MT safe
25  */
26
27 #include        "glib.h"
28
29
30 /* --- defines --- */
31 #define G_HOOKS_PREALLOC        (16)
32
33
34 /* --- functions --- */
35 void
36 g_hook_list_init (GHookList *hook_list,
37                   guint      hook_size)
38 {
39   g_return_if_fail (hook_list != NULL);
40   g_return_if_fail (hook_size >= sizeof (GHook));
41   
42   hook_list->seq_id = 1;
43   hook_list->hook_size = hook_size;
44   hook_list->is_setup = TRUE;
45   hook_list->hooks = NULL;
46   hook_list->hook_memchunk = g_mem_chunk_new ("GHook Memchunk",
47                                               hook_size,
48                                               hook_size * G_HOOKS_PREALLOC,
49                                               G_ALLOC_AND_FREE);
50   hook_list->hook_free = NULL;
51 }
52
53 void
54 g_hook_list_clear (GHookList *hook_list)
55 {
56   g_return_if_fail (hook_list != NULL);
57   
58   if (hook_list->is_setup)
59     {
60       GHook *hook;
61       
62       hook_list->is_setup = FALSE;
63       
64       hook = hook_list->hooks;
65       if (!hook)
66         {
67           g_mem_chunk_destroy (hook_list->hook_memchunk);
68           hook_list->hook_memchunk = NULL;
69         }
70       else
71         do
72           {
73             GHook *tmp;
74             
75             g_hook_ref (hook_list, hook);
76             g_hook_destroy_link (hook_list, hook);
77             tmp = hook->next;
78             g_hook_unref (hook_list, hook);
79             hook = tmp;
80           }
81         while (hook);
82     }
83 }
84
85 GHook*
86 g_hook_alloc (GHookList *hook_list)
87 {
88   GHook *hook;
89   
90   g_return_val_if_fail (hook_list != NULL, NULL);
91   g_return_val_if_fail (hook_list->is_setup, NULL);
92   
93   hook = g_chunk_new0 (GHook, hook_list->hook_memchunk);
94   hook->data = NULL;
95   hook->next = NULL;
96   hook->prev = NULL;
97   hook->flags = G_HOOK_FLAG_ACTIVE;
98   hook->ref_count = 0;
99   hook->hook_id = 0;
100   hook->func = NULL;
101   hook->destroy = NULL;
102   
103   return hook;
104 }
105
106 void
107 g_hook_free (GHookList *hook_list,
108              GHook     *hook)
109 {
110   g_return_if_fail (hook_list != NULL);
111   g_return_if_fail (hook_list->is_setup);
112   g_return_if_fail (hook != NULL);
113   g_return_if_fail (G_HOOK_IS_UNLINKED (hook));
114
115   if (hook_list->hook_free)
116     hook_list->hook_free (hook_list, hook);
117   
118   g_chunk_free (hook, hook_list->hook_memchunk);
119 }
120
121 void
122 g_hook_destroy_link (GHookList *hook_list,
123                      GHook     *hook)
124 {
125   g_return_if_fail (hook_list != NULL);
126   g_return_if_fail (hook != NULL);
127   
128   if (hook->hook_id)
129     {
130       hook->hook_id = 0;
131       hook->flags &= ~G_HOOK_FLAG_ACTIVE;
132       if (hook->destroy)
133         {
134           GDestroyNotify destroy;
135           
136           destroy = hook->destroy;
137           hook->destroy = NULL;
138           destroy (hook->data);
139         }
140       g_hook_unref (hook_list, hook); /* counterpart to g_hook_insert_before */
141     }
142 }
143
144 gboolean
145 g_hook_destroy (GHookList   *hook_list,
146                 guint        hook_id)
147 {
148   GHook *hook;
149   
150   g_return_val_if_fail (hook_list != NULL, FALSE);
151   g_return_val_if_fail (hook_id > 0, FALSE);
152   
153   hook = g_hook_get (hook_list, hook_id);
154   if (hook)
155     {
156       g_hook_destroy_link (hook_list, hook);
157       return TRUE;
158     }
159   
160   return FALSE;
161 }
162
163 void
164 g_hook_unref (GHookList *hook_list,
165               GHook     *hook)
166 {
167   g_return_if_fail (hook_list != NULL);
168   g_return_if_fail (hook != NULL);
169   g_return_if_fail (hook->ref_count > 0);
170   
171   hook->ref_count--;
172   if (!hook->ref_count)
173     {
174       g_return_if_fail (hook->hook_id == 0);
175       g_return_if_fail (!G_HOOK_IN_CALL (hook));
176       
177       if (hook->prev)
178         hook->prev->next = hook->next;
179       else
180         hook_list->hooks = hook->next;
181       if (hook->next)
182         {
183           hook->next->prev = hook->prev;
184           hook->next = NULL;
185         }
186       hook->prev = NULL;
187       
188       g_hook_free (hook_list, hook);
189       
190       if (!hook_list->hooks &&
191           !hook_list->is_setup)
192         {
193           g_mem_chunk_destroy (hook_list->hook_memchunk);
194           hook_list->hook_memchunk = NULL;
195         }
196     }
197 }
198
199 void
200 g_hook_ref (GHookList *hook_list,
201             GHook     *hook)
202 {
203   g_return_if_fail (hook_list != NULL);
204   g_return_if_fail (hook != NULL);
205   g_return_if_fail (hook->ref_count > 0);
206   
207   hook->ref_count++;
208 }
209
210 void
211 g_hook_prepend (GHookList *hook_list,
212                 GHook     *hook)
213 {
214   g_return_if_fail (hook_list != NULL);
215   
216   g_hook_insert_before (hook_list, hook_list->hooks, hook);
217 }
218
219 void
220 g_hook_insert_before (GHookList *hook_list,
221                       GHook     *sibling,
222                       GHook     *hook)
223 {
224   g_return_if_fail (hook_list != NULL);
225   g_return_if_fail (hook_list->is_setup);
226   g_return_if_fail (hook != NULL);
227   g_return_if_fail (G_HOOK_IS_UNLINKED (hook));
228   g_return_if_fail (hook->func != NULL);
229   
230   hook->hook_id = hook_list->seq_id++;
231   hook->ref_count = 1; /* counterpart to g_hook_destroy_link */
232   
233   if (sibling)
234     {
235       if (sibling->prev)
236         {
237           hook->prev = sibling->prev;
238           hook->prev->next = hook;
239           hook->next = sibling;
240           sibling->prev = hook;
241         }
242       else
243         {
244           hook_list->hooks = hook;
245           hook->next = sibling;
246           sibling->prev = hook;
247         }
248     }
249   else
250     {
251       if (hook_list->hooks)
252         {
253           sibling = hook_list->hooks;
254           while (sibling->next)
255             sibling = sibling->next;
256           hook->prev = sibling;
257           sibling->next = hook;
258         }
259       else
260         hook_list->hooks = hook;
261     }
262 }
263
264 void
265 g_hook_list_invoke (GHookList *hook_list,
266                     gboolean   may_recurse)
267 {
268   GHook *hook;
269   
270   g_return_if_fail (hook_list != NULL);
271   g_return_if_fail (hook_list->is_setup);
272
273   hook = g_hook_first_valid (hook_list, may_recurse);
274   while (hook)
275     {
276       GHook *tmp;
277       GHookFunc func;
278       gboolean was_in_call;
279       
280       g_hook_ref (hook_list, hook);
281       func = (GHookFunc) hook->func;
282       
283       was_in_call = G_HOOK_IN_CALL (hook);
284       hook->flags |= G_HOOK_FLAG_IN_CALL;
285       func (hook->data);
286       if (!was_in_call)
287         hook->flags &= ~G_HOOK_FLAG_IN_CALL;
288       
289       tmp = g_hook_next_valid (hook_list, hook, may_recurse);
290       
291       g_hook_unref (hook_list, hook);
292       hook = tmp;
293     }
294 }
295
296 void
297 g_hook_list_invoke_check (GHookList *hook_list,
298                           gboolean   may_recurse)
299 {
300   GHook *hook;
301   
302   g_return_if_fail (hook_list != NULL);
303   g_return_if_fail (hook_list->is_setup);
304   
305   hook = g_hook_first_valid (hook_list, may_recurse);
306   while (hook)
307     {
308       GHook *tmp;
309       GHookCheckFunc func;
310       gboolean was_in_call;
311       gboolean need_destroy;
312       
313       g_hook_ref (hook_list, hook);
314       func = (GHookCheckFunc) hook->func;
315       
316       was_in_call = G_HOOK_IN_CALL (hook);
317       hook->flags |= G_HOOK_FLAG_IN_CALL;
318       need_destroy = !func (hook->data);
319       if (!was_in_call)
320         hook->flags &= ~G_HOOK_FLAG_IN_CALL;
321       if (need_destroy)
322         g_hook_destroy_link (hook_list, hook);
323       
324       tmp = g_hook_next_valid (hook_list, hook, may_recurse);
325       
326       g_hook_unref (hook_list, hook);
327       hook = tmp;
328     }
329 }
330
331 void
332 g_hook_list_marshal_check (GHookList           *hook_list,
333                            gboolean             may_recurse,
334                            GHookCheckMarshaller marshaller,
335                            gpointer             data)
336 {
337   GHook *hook;
338   
339   g_return_if_fail (hook_list != NULL);
340   g_return_if_fail (hook_list->is_setup);
341   g_return_if_fail (marshaller != NULL);
342   
343   hook = g_hook_first_valid (hook_list, may_recurse);
344   while (hook)
345     {
346       GHook *tmp;
347       gboolean was_in_call;
348       gboolean need_destroy;
349       
350       g_hook_ref (hook_list, hook);
351       
352       was_in_call = G_HOOK_IN_CALL (hook);
353       hook->flags |= G_HOOK_FLAG_IN_CALL;
354       need_destroy = !marshaller (hook, data);
355       if (!was_in_call)
356         hook->flags &= ~G_HOOK_FLAG_IN_CALL;
357       if (need_destroy)
358         g_hook_destroy_link (hook_list, hook);
359       
360       tmp = g_hook_next_valid (hook_list, hook, may_recurse);
361       
362       g_hook_unref (hook_list, hook);
363       hook = tmp;
364     }
365 }
366
367 void
368 g_hook_list_marshal (GHookList               *hook_list,
369                      gboolean                 may_recurse,
370                      GHookMarshaller          marshaller,
371                      gpointer                 data)
372 {
373   GHook *hook;
374   
375   g_return_if_fail (hook_list != NULL);
376   g_return_if_fail (hook_list->is_setup);
377   g_return_if_fail (marshaller != NULL);
378   
379   hook = g_hook_first_valid (hook_list, may_recurse);
380   while (hook)
381     {
382       GHook *tmp;
383       gboolean was_in_call;
384       
385       g_hook_ref (hook_list, hook);
386       
387       was_in_call = G_HOOK_IN_CALL (hook);
388       hook->flags |= G_HOOK_FLAG_IN_CALL;
389       marshaller (hook, data);
390       if (!was_in_call)
391         hook->flags &= ~G_HOOK_FLAG_IN_CALL;
392       
393       tmp = g_hook_next_valid (hook_list, hook, may_recurse);
394       
395       g_hook_unref (hook_list, hook);
396       hook = tmp;
397     }
398 }
399
400 GHook*
401 g_hook_first_valid (GHookList *hook_list,
402                     gboolean   may_be_in_call)
403 {
404   g_return_val_if_fail (hook_list != NULL, NULL);
405   
406   if (hook_list->is_setup)
407     {
408       GHook *hook;
409       
410       hook = hook_list->hooks;
411       g_hook_ref (hook_list, hook);
412       if (hook)
413         {
414           if (G_HOOK_IS_VALID (hook) && (may_be_in_call || !G_HOOK_IN_CALL (hook)))
415             return hook;
416           else
417             return g_hook_next_valid (hook_list, hook, may_be_in_call);
418         }
419     }
420   
421   return NULL;
422 }
423
424 GHook*
425 g_hook_next_valid (GHookList *hook_list,
426                    GHook     *hook,
427                    gboolean   may_be_in_call)
428 {
429   GHook *ohook = hook;
430
431   g_return_val_if_fail (hook_list != NULL, NULL);
432
433   if (!hook)
434     return NULL;
435   
436   hook = hook->next;
437   while (hook)
438     {
439       if (G_HOOK_IS_VALID (hook) && (may_be_in_call || !G_HOOK_IN_CALL (hook)))
440         {
441           g_hook_ref (hook_list, hook);
442           g_hook_unref (hook_list, ohook);
443           
444           return hook;
445         }
446       hook = hook->next;
447     }
448   g_hook_unref (hook_list, ohook);
449
450   return NULL;
451 }
452
453 GHook*
454 g_hook_get (GHookList *hook_list,
455             guint      hook_id)
456 {
457   GHook *hook;
458   
459   g_return_val_if_fail (hook_list != NULL, NULL);
460   g_return_val_if_fail (hook_id > 0, NULL);
461   
462   hook = hook_list->hooks;
463   while (hook)
464     {
465       if (hook->hook_id == hook_id)
466         return hook;
467       hook = hook->next;
468     }
469   
470   return NULL;
471 }
472
473 GHook*
474 g_hook_find (GHookList    *hook_list,
475              gboolean      need_valids,
476              GHookFindFunc func,
477              gpointer      data)
478 {
479   GHook *hook;
480   
481   g_return_val_if_fail (hook_list != NULL, NULL);
482   g_return_val_if_fail (func != NULL, NULL);
483   
484   hook = hook_list->hooks;
485   while (hook)
486     {
487       GHook *tmp;
488
489       /* test only non-destroyed hooks */
490       if (!hook->hook_id)
491         {
492           hook = hook->next;
493           continue;
494         }
495       
496       g_hook_ref (hook_list, hook);
497       
498       if (func (hook, data) && hook->hook_id && (!need_valids || G_HOOK_ACTIVE (hook)))
499         {
500           g_hook_unref (hook_list, hook);
501           
502           return hook;
503         }
504
505       tmp = hook->next;
506       g_hook_unref (hook_list, hook);
507       hook = tmp;
508     }
509   
510   return NULL;
511 }
512
513 GHook*
514 g_hook_find_data (GHookList *hook_list,
515                   gboolean   need_valids,
516                   gpointer   data)
517 {
518   GHook *hook;
519   
520   g_return_val_if_fail (hook_list != NULL, NULL);
521   
522   hook = hook_list->hooks;
523   while (hook)
524     {
525       /* test only non-destroyed hooks */
526       if (hook->data == data &&
527           hook->hook_id &&
528           (!need_valids || G_HOOK_ACTIVE (hook)))
529         return hook;
530
531       hook = hook->next;
532     }
533   
534   return NULL;
535 }
536
537 GHook*
538 g_hook_find_func (GHookList *hook_list,
539                   gboolean   need_valids,
540                   gpointer   func)
541 {
542   GHook *hook;
543   
544   g_return_val_if_fail (hook_list != NULL, NULL);
545   g_return_val_if_fail (func != NULL, NULL);
546   
547   hook = hook_list->hooks;
548   while (hook)
549     {
550       /* test only non-destroyed hooks */
551       if (hook->func == func &&
552           hook->hook_id &&
553           (!need_valids || G_HOOK_ACTIVE (hook)))
554         return hook;
555
556       hook = hook->next;
557     }
558   
559   return NULL;
560 }
561
562 GHook*
563 g_hook_find_func_data (GHookList *hook_list,
564                        gboolean   need_valids,
565                        gpointer   func,
566                        gpointer   data)
567 {
568   GHook *hook;
569   
570   g_return_val_if_fail (hook_list != NULL, NULL);
571   g_return_val_if_fail (func != NULL, NULL);
572   
573   hook = hook_list->hooks;
574   while (hook)
575     {
576       /* test only non-destroyed hooks */
577       if (hook->data == data &&
578           hook->func == func &&
579           hook->hook_id &&
580           (!need_valids || G_HOOK_ACTIVE (hook)))
581         return hook;
582
583       hook = hook->next;
584     }
585   
586   return NULL;
587 }
588
589 void
590 g_hook_insert_sorted (GHookList       *hook_list,
591                       GHook           *hook,
592                       GHookCompareFunc func)
593 {
594   GHook *sibling;
595   
596   g_return_if_fail (hook_list != NULL);
597   g_return_if_fail (hook_list->is_setup);
598   g_return_if_fail (hook != NULL);
599   g_return_if_fail (G_HOOK_IS_UNLINKED (hook));
600   g_return_if_fail (hook->func != NULL);
601   g_return_if_fail (func != NULL);
602
603   /* first non-destroyed hook */
604   sibling = hook_list->hooks;
605   while (sibling && !sibling->hook_id)
606     sibling = sibling->next;
607   
608   while (sibling)
609     {
610       GHook *tmp;
611       
612       g_hook_ref (hook_list, sibling);
613       if (func (hook, sibling) <= 0 && sibling->hook_id)
614         {
615           g_hook_unref (hook_list, sibling);
616           break;
617         }
618
619       /* next non-destroyed hook */
620       tmp = sibling->next;
621       while (tmp && !tmp->hook_id)
622         tmp = tmp->next;
623
624       g_hook_unref (hook_list, sibling);
625       sibling = tmp;
626     }
627   
628   g_hook_insert_before (hook_list, sibling, hook);
629 }
630
631 gint
632 g_hook_compare_ids (GHook *new_hook,
633                     GHook *sibling)
634 {
635   return ((glong) new_hook->hook_id) - ((glong) sibling->hook_id);
636 }