cleanup
[platform/upstream/glib.git] / gobject / gatomicarray.c
1 /* GObject - GLib Type, Object, Parameter and Signal Library
2  * Copyright (C) 2009 Benjamin Otte <otte@gnome.org>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library 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  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General
15  * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
16  */
17
18 #include "config.h"
19
20 #include <string.h>
21
22 #include "gatomicarray.h"
23
24 /* A GAtomicArray is a growable, mutable array of data
25  * generally of the form of a header of a specific size and
26  * then a array of items of a fixed size.
27  *
28  * It is possible to do lock-less read transactions from the
29  * array without any protection against other reads or writes,
30  * but such read operation must be aware that the data in the
31  * atomic array can change at any time during the transaction,
32  * and only at the end can we verify if the transaction succeeded
33  * or not. Thus the reading transaction cannot for instance
34  * dereference a pointer in the array inside the transaction.
35  *
36  * The size of an array however cannot change during a read
37  * transaction.
38  *
39  * Writes to the array is done in a copy-update style, but there
40  * is no real protection against multiple writers overwriting each
41  * others updates, so writes must be protected by an external lock.
42  */
43
44 G_LOCK_DEFINE_STATIC (array);
45
46 typedef struct _FreeListNode FreeListNode;
47 struct _FreeListNode {
48   FreeListNode *next;
49 };
50
51 /* This is really a list of array memory blocks, using the
52  * first item as the next pointer to chain them together.
53  * Protected by array lock */
54 static FreeListNode *freelist = NULL;
55
56 /* must hold array lock */
57 static gpointer
58 freelist_alloc (gsize size, gboolean reuse)
59 {
60   gpointer mem;
61   FreeListNode *free, **prev;
62   gsize real_size;
63
64   if (reuse)
65     {
66       for (free = freelist, prev = &freelist; free != NULL; prev = &free->next, free = free->next)
67         {
68           if (G_ATOMIC_ARRAY_DATA_SIZE (free) == size)
69             {
70               *prev = free->next;
71               return (gpointer)free;
72             }
73         }
74     }
75
76   real_size = sizeof (gsize) + MAX (size, sizeof (FreeListNode));
77   mem = g_slice_alloc (real_size);
78   mem = ((char *) mem) + sizeof (gsize);
79   G_ATOMIC_ARRAY_DATA_SIZE (mem) = size;
80   return mem;
81 }
82
83 /* must hold array lock */
84 static void
85 freelist_free (gpointer mem)
86 {
87   FreeListNode *free;
88
89   free = mem;
90   free->next = freelist;
91   freelist = free;
92 }
93
94 void
95 _g_atomic_array_init (GAtomicArray *array)
96 {
97   array->data = NULL;
98 }
99
100 /* Get a copy of the data (if non-NULL) that
101  * can be changed and then re-applied with
102  * g_atomic_array_update().
103  *
104  * If additional_element_size is > 0 then
105  * then the new memory chunk is that much
106  * larger, or there were no data we return
107  * a chunk of header_size + additional_element_size.
108  * This means you can use this to grow the
109  * array part and it handles the first element
110  * being added automatically.
111  *
112  * We don't support shrinking arrays, as if
113  * we then re-grow we may reuse an old pointer
114  * value and confuse the transaction check.
115  */
116 gpointer
117 _g_atomic_array_copy (GAtomicArray *array,
118                       gsize header_size,
119                       gsize additional_element_size)
120 {
121   guint8 *new, *old;
122   gsize old_size, new_size;
123
124   G_LOCK (array);
125   old = g_atomic_pointer_get (&array->data);
126   if (old)
127     {
128       old_size = G_ATOMIC_ARRAY_DATA_SIZE (old);
129       new_size = old_size + additional_element_size;
130       /* Don't reuse if copying to same size, as this may end
131          up reusing the same pointer for the same array thus
132          confusing the transaction check */
133       new = freelist_alloc (new_size, additional_element_size != 0);
134       memcpy (new, old, old_size);
135     }
136   else if (additional_element_size != 0)
137     {
138       new_size = header_size + additional_element_size;
139       new = freelist_alloc (new_size, TRUE);
140     }
141   else
142     new = NULL;
143   G_UNLOCK (array);
144   return new;
145 }
146
147 /* Replace the data in the array with the new data,
148  * freeing the old data (for reuse). The new data may
149  * not be smaller than the current data.
150  */
151 void
152 _g_atomic_array_update (GAtomicArray *array,
153                         gpointer new_data)
154 {
155   guint8 *old;
156
157   G_LOCK (array);
158   old = g_atomic_pointer_get (&array->data);
159
160   g_assert (old == NULL || G_ATOMIC_ARRAY_DATA_SIZE (old) <= G_ATOMIC_ARRAY_DATA_SIZE (new_data));
161
162   g_atomic_pointer_set (&array->data, new_data);
163   if (old)
164     freelist_free (old);
165   G_UNLOCK (array);
166 }