First THREADED backport attempt, focusing on adding locks and making sure the API...
[platform/upstream/gstreamer.git] / gst / gstmemchunk.c
1 /* GStreamer
2  * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
3  *               <2005> Wim Taymans <wim@fluendo.com>
4  *
5  * gstmemchunk.c: implementation of lockfree allocation of fixed
6  *                size memory chunks.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library 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 GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public
19  * License along with this library; if not, write to the
20  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21  * Boston, MA 02111-1307, USA.
22  */
23 #include "gst_private.h"
24
25 #include <string.h>             /* memset */
26
27 #include "gstutils.h"
28 #include "gstmemchunk.h"
29 #include "gsttrashstack.h"
30 #ifdef HAVE_VALGRIND
31 #include <sys/mman.h>
32 #include <valgrind/valgrind.h>
33 #endif
34
35 #define GST_MEM_CHUNK_AREA(chunk)       (((GstMemChunkElement*)(chunk))->area)
36 #define GST_MEM_CHUNK_DATA(chunk)       ((gpointer)(((GstMemChunkElement*)(chunk)) + 1))
37 #define GST_MEM_CHUNK_LINK(mem)         ((GstMemChunkElement*)((guint8*)(mem) - sizeof (GstMemChunkElement)))
38
39 typedef struct _GstMemChunkElement GstMemChunkElement;
40
41 struct _GstMemChunkElement
42 {
43   GstTrashStackElement elem;    /* make sure we can safely push it on the trashstack */
44   gpointer area;                /* pointer to data areas */
45 };
46
47 struct _GstMemChunk
48 {
49   GstTrashStack stack;
50
51   gchar *name;
52   gulong area_size;
53   gulong chunk_size;
54   gulong atom_size;
55   gboolean cleanup;
56 };
57
58 /*******************************************************
59  *         area size
60  * +-------------------------------------------------------+
61  *   chunk size
62  * +-----------------+
63  *
64  * !next!area|data... !next!area!data.... !next!area!data...
65  *  !                  ^ !                 ^ !
66  *  +------------------+ +-----------------+ +--------> NULL
67  *
68  */
69 static gboolean
70 populate (GstMemChunk * mem_chunk)
71 {
72   guint8 *area;
73   gint i;
74
75   if (mem_chunk->cleanup)
76     return FALSE;
77
78   /* FIXME: if we don't do this here and use g_malloc, valgrind crashes */
79 #if HAVE_VALGRIND
80   if (__gst_in_valgrind ()) {
81     /* copied from valgrind example */
82     area =
83         (guint8 *) mmap (0, mem_chunk->area_size,
84         PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANON, -1, 0);
85   } else
86 #endif
87   {
88     area = g_malloc0 (mem_chunk->area_size);
89   }
90
91   for (i = 0; i < mem_chunk->area_size; i += mem_chunk->chunk_size) {
92     GST_MEM_CHUNK_AREA (area + i) = area;
93     gst_trash_stack_push (&mem_chunk->stack, area + i);
94   }
95
96   return TRUE;
97 }
98
99 /**
100  * gst_mem_chunk_new:
101  * @name: the name of the chunk
102  * @atom_size: the size of the allocated atoms
103  * @area_size: the initial size of the memory area
104  * @type: the allocation strategy to use
105  *
106  * Creates a new memchunk that will allocate atom_sized memchunks.
107  * The initial area is set to area_size and will grow automatically 
108  * when it is too small (with a small overhead when that happens)
109  *
110  * Returns: a new #GstMemChunk
111  *
112  * MT safe.
113  */
114 GstMemChunk *
115 gst_mem_chunk_new (gchar * name, gint atom_size, gulong area_size, gint type)
116 {
117   GstMemChunk *mem_chunk;
118
119   g_return_val_if_fail (atom_size > 0, NULL);
120   g_return_val_if_fail (area_size >= atom_size, NULL);
121
122   mem_chunk = g_malloc (sizeof (GstMemChunk));
123
124   mem_chunk->chunk_size = atom_size + sizeof (GstMemChunkElement);
125   area_size = (area_size / atom_size) * mem_chunk->chunk_size;
126
127   mem_chunk->name = g_strdup (name);
128   mem_chunk->atom_size = atom_size;
129   mem_chunk->area_size = area_size;
130   mem_chunk->cleanup = FALSE;
131   gst_trash_stack_init (&mem_chunk->stack);
132
133   populate (mem_chunk);
134
135   return mem_chunk;
136 }
137
138 static gboolean
139 free_area (gpointer key, gpointer value, gpointer user_data)
140 {
141 #if HAVE_VALGRIND
142   GstMemChunk *chunk = (GstMemChunk *) user_data;
143
144   if (__gst_in_valgrind ()) {
145     /* copied from valgrind example */
146     munmap (key, chunk->area_size);
147   } else
148 #endif
149   {
150     g_free (key);
151   }
152
153   return TRUE;
154 }
155
156 /**
157  * gst_mem_chunk_destroy:
158  * @mem_chunk: the GstMemChunk to destroy
159  *
160  * Free the memory allocated by the memchunk. This function
161  * is not Threadsafe as it does not wait for all outstanding
162  * allocations to be freed.
163  */
164 void
165 gst_mem_chunk_destroy (GstMemChunk * mem_chunk)
166 {
167   GHashTable *elements = g_hash_table_new (NULL, NULL);
168   gpointer data;
169
170   mem_chunk->cleanup = TRUE;
171
172   data = gst_mem_chunk_alloc (mem_chunk);
173   while (data) {
174     GstMemChunkElement *elem = GST_MEM_CHUNK_LINK (data);
175
176     g_hash_table_insert (elements, GST_MEM_CHUNK_AREA (elem), NULL);
177
178     data = gst_mem_chunk_alloc (mem_chunk);
179   }
180   g_hash_table_foreach_remove (elements, free_area, mem_chunk);
181
182   g_hash_table_destroy (elements);
183   g_free (mem_chunk->name);
184   g_free (mem_chunk);
185 }
186
187 /**
188  * gst_mem_chunk_alloc:
189  * @mem_chunk: the mem chunk to use
190  *
191  * Allocate a new memory region from the chunk. The size
192  * of the allocated memory was specified when the memchunk
193  * was created.
194  *
195  * Returns: a pointer to the allocated memory region.
196  *
197  * MT safe.
198  */
199 gpointer
200 gst_mem_chunk_alloc (GstMemChunk * mem_chunk)
201 {
202   GstMemChunkElement *chunk;
203
204   g_return_val_if_fail (mem_chunk != NULL, NULL);
205
206 again:
207   chunk = gst_trash_stack_pop (&mem_chunk->stack);
208   /* chunk is empty, try to refill */
209   if (G_UNLIKELY (!chunk)) {
210     if (G_LIKELY (populate (mem_chunk))) {
211       goto again;
212     } else {
213       /* this happens when we are in cleanup mode and we
214        * allocate all remaining chunks for cleanup */
215       return NULL;
216     }
217   }
218 #ifdef HAVE_VALGRIND
219   if (G_UNLIKELY (__gst_in_valgrind ())) {
220     VALGRIND_MALLOCLIKE_BLOCK (GST_MEM_CHUNK_DATA (chunk), mem_chunk->atom_size,
221         0, 0);
222   }
223 #endif
224   return GST_MEM_CHUNK_DATA (chunk);
225 }
226
227 /**
228  * gst_mem_chunk_alloc0:
229  * @mem_chunk: the mem chunk to use
230  *
231  * Allocate a new memory region from the chunk. The size
232  * of the allocated memory was specified when the memchunk
233  * was created. The memory will be set to all zeroes.
234  *
235  * Returns: a pointer to the allocated memory region.
236  *
237  * MT safe.
238  */
239 gpointer
240 gst_mem_chunk_alloc0 (GstMemChunk * mem_chunk)
241 {
242   gpointer mem = gst_mem_chunk_alloc (mem_chunk);
243
244   if (G_LIKELY (mem))
245     memset (mem, 0, mem_chunk->atom_size);
246
247   return mem;
248 }
249
250 /**
251  * gst_mem_chunk_free:
252  * @mem_chunk: the mem chunk to use
253  * @mem: the memory region to hand back to the chunk
254  *
255  * Free the memeory region allocated from the chunk.
256  *
257  * MT safe.
258  */
259 void
260 gst_mem_chunk_free (GstMemChunk * mem_chunk, gpointer mem)
261 {
262   GstMemChunkElement *chunk;
263
264   g_return_if_fail (mem_chunk != NULL);
265   g_return_if_fail (mem != NULL);
266
267   chunk = GST_MEM_CHUNK_LINK (mem);
268
269 #ifdef HAVE_VALGRIND
270   if (G_UNLIKELY (__gst_in_valgrind ())) {
271     VALGRIND_FREELIKE_BLOCK (mem, 0);
272   }
273 #endif
274   gst_trash_stack_push (&mem_chunk->stack, chunk);
275 }