Replace individual global-lock variables with an array of DBusRMutex *
[platform/upstream/dbus.git] / dbus / dbus-dataslot.c
1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2 /* dbus-dataslot.c  storing data on objects
3  *
4  * Copyright (C) 2003 Red Hat, Inc.
5  *
6  * Licensed under the Academic Free License version 2.1
7  * 
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  * 
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
21  *
22  */
23
24 #include <config.h>
25 #include "dbus-dataslot.h"
26 #include "dbus-threads-internal.h"
27
28 /**
29  * @defgroup DBusDataSlot Data slots
30  * @ingroup  DBusInternals
31  * @brief Storing data by ID
32  *
33  * Types and functions related to storing data by an
34  * allocated ID. This is used for dbus_connection_set_data(),
35  * dbus_server_set_data(), etc. 
36  * @{
37  */
38
39 /**
40  * Initializes a data slot allocator object, used to assign
41  * integer IDs for data slots.
42  *
43  * @param allocator the allocator to initialize
44  */
45 dbus_bool_t
46 _dbus_data_slot_allocator_init (DBusDataSlotAllocator *allocator,
47                                 DBusGlobalLock         lock)
48 {
49   allocator->allocated_slots = NULL;
50   allocator->n_allocated_slots = 0;
51   allocator->n_used_slots = 0;
52   allocator->lock = lock;
53
54   return TRUE;
55 }
56
57 /**
58  * Allocates an integer ID to be used for storing data
59  * in a #DBusDataSlotList. If the value at *slot_id_p is
60  * not -1, this function just increments the refcount for
61  * the existing slot ID. If the value is -1, a new slot ID
62  * is allocated and stored at *slot_id_p.
63  * 
64  * @param allocator the allocator
65  * @param slot_id_p address to fill with the slot ID
66  * @returns #TRUE on success
67  */
68 dbus_bool_t
69 _dbus_data_slot_allocator_alloc (DBusDataSlotAllocator *allocator,
70                                  dbus_int32_t          *slot_id_p)
71 {
72   dbus_int32_t slot;
73
74   _dbus_lock (allocator->lock);
75
76   if (*slot_id_p >= 0)
77     {
78       slot = *slot_id_p;
79       
80       _dbus_assert (slot < allocator->n_allocated_slots);
81       _dbus_assert (allocator->allocated_slots[slot].slot_id == slot);
82       
83       allocator->allocated_slots[slot].refcount += 1;
84
85       goto out;
86     }
87
88   _dbus_assert (*slot_id_p < 0);
89   
90   if (allocator->n_used_slots < allocator->n_allocated_slots)
91     {
92       slot = 0;
93       while (slot < allocator->n_allocated_slots)
94         {
95           if (allocator->allocated_slots[slot].slot_id < 0)
96             {
97               allocator->allocated_slots[slot].slot_id = slot;
98               allocator->allocated_slots[slot].refcount = 1;
99               allocator->n_used_slots += 1;
100               break;
101             }
102           ++slot;
103         }
104
105       _dbus_assert (slot < allocator->n_allocated_slots);
106     }
107   else
108     {
109       DBusAllocatedSlot *tmp;
110       
111       slot = -1;
112       tmp = dbus_realloc (allocator->allocated_slots,
113                           sizeof (DBusAllocatedSlot) * (allocator->n_allocated_slots + 1));
114       if (tmp == NULL)
115         goto out;
116
117       allocator->allocated_slots = tmp;
118       slot = allocator->n_allocated_slots;
119       allocator->n_allocated_slots += 1;
120       allocator->n_used_slots += 1;
121       allocator->allocated_slots[slot].slot_id = slot;
122       allocator->allocated_slots[slot].refcount = 1;
123     }
124
125   _dbus_assert (slot >= 0);
126   _dbus_assert (slot < allocator->n_allocated_slots);
127   _dbus_assert (*slot_id_p < 0);
128   _dbus_assert (allocator->allocated_slots[slot].slot_id == slot);
129   _dbus_assert (allocator->allocated_slots[slot].refcount == 1);
130   
131   *slot_id_p = slot;
132   
133   _dbus_verbose ("Allocated slot %d on allocator %p total %d slots allocated %d used\n",
134                  slot, allocator, allocator->n_allocated_slots, allocator->n_used_slots);
135   
136  out:
137   _dbus_unlock (allocator->lock);
138   return slot >= 0;
139 }
140
141 /**
142  * Deallocates an ID previously allocated with
143  * _dbus_data_slot_allocator_alloc().  Existing data stored on
144  * existing #DBusDataSlotList objects with this ID will be freed when the
145  * data list is finalized, but may not be retrieved (and may only be
146  * replaced if someone else reallocates the slot).
147  * The slot value is reset to -1 if this is the last unref.
148  *
149  * @param allocator the allocator
150  * @param slot_id_p address where we store the slot
151  */
152 void
153 _dbus_data_slot_allocator_free (DBusDataSlotAllocator *allocator,
154                                 dbus_int32_t          *slot_id_p)
155 {
156   _dbus_lock (allocator->lock);
157   
158   _dbus_assert (*slot_id_p < allocator->n_allocated_slots);
159   _dbus_assert (allocator->allocated_slots[*slot_id_p].slot_id == *slot_id_p);
160   _dbus_assert (allocator->allocated_slots[*slot_id_p].refcount > 0);
161
162   allocator->allocated_slots[*slot_id_p].refcount -= 1;
163
164   if (allocator->allocated_slots[*slot_id_p].refcount > 0)
165     {
166       _dbus_unlock (allocator->lock);
167       return;
168     }
169
170   /* refcount is 0, free the slot */
171   _dbus_verbose ("Freeing slot %d on allocator %p total %d allocated %d used\n",
172                  *slot_id_p, allocator, allocator->n_allocated_slots, allocator->n_used_slots);
173   
174   allocator->allocated_slots[*slot_id_p].slot_id = -1;
175   *slot_id_p = -1;
176   
177   allocator->n_used_slots -= 1;
178   
179   if (allocator->n_used_slots == 0)
180     {
181       dbus_free (allocator->allocated_slots);
182       allocator->allocated_slots = NULL;
183       allocator->n_allocated_slots = 0;
184     }
185
186   _dbus_unlock (allocator->lock);
187 }
188
189 /**
190  * Initializes a slot list.
191  * @param list the list to initialize.
192  */
193 void
194 _dbus_data_slot_list_init (DBusDataSlotList *list)
195 {
196   list->slots = NULL;
197   list->n_slots = 0;
198 }
199
200 /**
201  * Stores a pointer in the data slot list, along with an optional
202  * function to be used for freeing the data when the data is set
203  * again, or when the slot list is finalized. The slot number must
204  * have been allocated with _dbus_data_slot_allocator_alloc() for the
205  * same allocator passed in here. The same allocator has to be used
206  * with the slot list every time.
207  *
208  * @param allocator the allocator to use
209  * @param list the data slot list
210  * @param slot the slot number
211  * @param data the data to store
212  * @param free_data_func finalizer function for the data
213  * @param old_free_func free function for any previously-existing data
214  * @param old_data previously-existing data, should be freed with old_free_func
215  * @returns #TRUE if there was enough memory to store the data
216  */
217 dbus_bool_t
218 _dbus_data_slot_list_set  (DBusDataSlotAllocator *allocator,
219                            DBusDataSlotList      *list,
220                            int                    slot,
221                            void                  *data,
222                            DBusFreeFunction       free_data_func,
223                            DBusFreeFunction      *old_free_func,
224                            void                 **old_data)
225 {
226 #ifndef DBUS_DISABLE_ASSERT
227   /* We need to take the allocator lock here, because the allocator could
228    * be e.g. realloc()ing allocated_slots. We avoid doing this if asserts
229    * are disabled, since then the asserts are empty.
230    */
231   _dbus_lock (allocator->lock);
232   _dbus_assert (slot < allocator->n_allocated_slots);
233   _dbus_assert (allocator->allocated_slots[slot].slot_id == slot);
234   _dbus_unlock (allocator->lock);
235 #endif
236   
237   if (slot >= list->n_slots)
238     {
239       DBusDataSlot *tmp;
240       int i;
241       
242       tmp = dbus_realloc (list->slots,
243                           sizeof (DBusDataSlot) * (slot + 1));
244       if (tmp == NULL)
245         return FALSE;
246       
247       list->slots = tmp;
248       i = list->n_slots;
249       list->n_slots = slot + 1;
250       while (i < list->n_slots)
251         {
252           list->slots[i].data = NULL;
253           list->slots[i].free_data_func = NULL;
254           ++i;
255         }
256     }
257
258   _dbus_assert (slot < list->n_slots);
259
260   *old_data = list->slots[slot].data;
261   *old_free_func = list->slots[slot].free_data_func;
262
263   list->slots[slot].data = data;
264   list->slots[slot].free_data_func = free_data_func;
265
266   return TRUE;
267 }
268
269 /**
270  * Retrieves data previously set with _dbus_data_slot_list_set_data().
271  * The slot must still be allocated (must not have been freed).
272  *
273  * @param allocator the allocator slot was allocated from
274  * @param list the data slot list
275  * @param slot the slot to get data from
276  * @returns the data, or #NULL if not found
277  */
278 void*
279 _dbus_data_slot_list_get  (DBusDataSlotAllocator *allocator,
280                            DBusDataSlotList      *list,
281                            int                    slot)
282 {
283 #ifndef DBUS_DISABLE_ASSERT
284   /* We need to take the allocator lock here, because the allocator could
285    * be e.g. realloc()ing allocated_slots. We avoid doing this if asserts
286    * are disabled, since then the asserts are empty.
287    */
288   _dbus_lock (allocator->lock);
289   _dbus_assert (slot >= 0);
290   _dbus_assert (slot < allocator->n_allocated_slots);
291   _dbus_assert (allocator->allocated_slots[slot].slot_id == slot);
292   _dbus_unlock (allocator->lock);
293 #endif
294
295   if (slot >= list->n_slots)
296     return NULL;
297   else
298     return list->slots[slot].data;
299 }
300
301 /**
302  * Frees all data slots contained in the list, calling
303  * application-provided free functions if they exist.
304  *
305  * @param list the list to clear
306  */
307 void
308 _dbus_data_slot_list_clear (DBusDataSlotList *list)
309 {
310   int i;
311
312   i = 0;
313   while (i < list->n_slots)
314     {
315       if (list->slots[i].free_data_func)
316         (* list->slots[i].free_data_func) (list->slots[i].data);
317       list->slots[i].data = NULL;
318       list->slots[i].free_data_func = NULL;
319       ++i;
320     }
321 }
322
323 /**
324  * Frees the data slot list and all data slots contained
325  * in it, calling application-provided free functions
326  * if they exist.
327  *
328  * @param list the list to free
329  */
330 void
331 _dbus_data_slot_list_free (DBusDataSlotList *list)
332 {
333   _dbus_data_slot_list_clear (list);
334   
335   dbus_free (list->slots);
336   list->slots = NULL;
337   list->n_slots = 0;
338 }
339
340 /** @} */
341
342 #ifdef DBUS_BUILD_TESTS
343 #include "dbus-test.h"
344 #include <stdio.h>
345
346 static int free_counter;
347
348 static void
349 test_free_slot_data_func (void *data)
350 {
351   int i = _DBUS_POINTER_TO_INT (data);
352
353   _dbus_assert (free_counter == i);
354   ++free_counter;
355 }
356
357 /**
358  * Test function for data slots
359  */
360 dbus_bool_t
361 _dbus_data_slot_test (void)
362 {
363   DBusDataSlotAllocator allocator;
364   DBusDataSlotList list;
365   int i;
366   DBusFreeFunction old_free_func;
367   void *old_data;
368
369   if (!_dbus_data_slot_allocator_init (&allocator, _DBUS_LOCK_server_slots))
370     _dbus_assert_not_reached ("no memory for allocator");
371
372   _dbus_data_slot_list_init (&list);
373
374 #define N_SLOTS 100
375
376   i = 0;
377   while (i < N_SLOTS)
378     {
379       /* we don't really want apps to rely on this ordered
380        * allocation, but it simplifies things to rely on it
381        * here.
382        */
383       dbus_int32_t tmp = -1;
384
385       _dbus_data_slot_allocator_alloc (&allocator, &tmp);
386
387       if (tmp != i)
388         _dbus_assert_not_reached ("did not allocate slots in numeric order\n");
389
390       ++i;
391     }
392
393   i = 0;
394   while (i < N_SLOTS)
395     {
396       if (!_dbus_data_slot_list_set (&allocator, &list,
397                                      i,
398                                      _DBUS_INT_TO_POINTER (i), 
399                                      test_free_slot_data_func,
400                                      &old_free_func, &old_data))
401         _dbus_assert_not_reached ("no memory to set data");
402
403       _dbus_assert (old_free_func == NULL);
404       _dbus_assert (old_data == NULL);
405
406       _dbus_assert (_dbus_data_slot_list_get (&allocator, &list, i) ==
407                     _DBUS_INT_TO_POINTER (i));
408       
409       ++i;
410     }
411
412   free_counter = 0;
413   i = 0;
414   while (i < N_SLOTS)
415     {
416       if (!_dbus_data_slot_list_set (&allocator, &list,
417                                      i,
418                                      _DBUS_INT_TO_POINTER (i), 
419                                      test_free_slot_data_func,
420                                      &old_free_func, &old_data))
421         _dbus_assert_not_reached ("no memory to set data");
422
423       _dbus_assert (old_free_func == test_free_slot_data_func);
424       _dbus_assert (_DBUS_POINTER_TO_INT (old_data) == i);
425
426       (* old_free_func) (old_data);
427       _dbus_assert (i == (free_counter - 1));
428
429       _dbus_assert (_dbus_data_slot_list_get (&allocator, &list, i) ==
430                     _DBUS_INT_TO_POINTER (i));
431       
432       ++i;
433     }
434
435   free_counter = 0;
436   _dbus_data_slot_list_free (&list);
437
438   _dbus_assert (N_SLOTS == free_counter);
439
440   i = 0;
441   while (i < N_SLOTS)
442     {
443       dbus_int32_t tmp = i;
444       
445       _dbus_data_slot_allocator_free (&allocator, &tmp);
446       _dbus_assert (tmp == -1);
447       ++i;
448     }
449
450   return TRUE;
451 }
452
453 #endif /* DBUS_BUILD_TESTS */