1 /*
2 * Copyright (C) 2006 Taybin Rutkin <taybin@taybin.com>
3 * Copyright (C) 2008-2009 David Robillard <d@drobilla.net>
4 * Copyright (C) 2008-2011 Carl Hetherington <carl@carlh.net>
5 * Copyright (C) 1998-2015 Paul Davis <paul@linuxaudiosystems.com>
6 * Copyright (C) 2014-2015 Robin Gareus <robin@gareus.org>
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 along
19 * with this program; if not, write to the Free Software Foundation, Inc.,
20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 */
22
23 #include <cstdlib>
24 #include <vector>
25 #include <cstdlib>
26 #include <cassert>
27
28 #include "pbd/pool.h"
29 #include "pbd/pthread_utils.h"
30 #include "pbd/error.h"
31 #include "pbd/debug.h"
32 #include "pbd/compose.h"
33
34 using namespace std;
35 using namespace PBD;
36
Pool(string n,unsigned long item_size,unsigned long nitems)37 Pool::Pool (string n, unsigned long item_size, unsigned long nitems)
38 : free_list (nitems)
39 , _name (n)
40 #ifndef NDEBUG
41 , max_usage (0)
42 #endif
43 {
44 _name = n;
45
46 /* since some overloaded ::operator new() might use this,
47 its important that we use a "lower level" allocator to
48 get more space.
49 */
50
51 block = malloc (nitems * item_size);
52
53 void **ptrlist = (void **) malloc (sizeof (void *) * nitems);
54
55 for (unsigned long i = 0; i < nitems; i++) {
56 ptrlist[i] = static_cast<void *> (static_cast<char*>(block) + (i * item_size));
57 }
58
59 free_list.write (ptrlist, nitems);
60 free (ptrlist);
61 }
62
~Pool()63 Pool::~Pool ()
64 {
65 DEBUG_TRACE (DEBUG::Pool, string_compose ("Pool: '%1' max: %2 / %3\n", name(), max_usage, total()));
66 free (block);
67 }
68
69 /** Allocate an item's worth of memory in the Pool by taking one from the free list.
70 * @return Pointer to free item.
71 */
72 void *
alloc()73 Pool::alloc ()
74 {
75 void *ptr;
76
77 #ifndef NDEBUG
78 if (used () > max_usage) {
79 max_usage = used () + 1;
80 }
81 #endif
82
83 if (free_list.read (&ptr, 1) < 1) {
84 fatal << "CRITICAL: " << _name << " POOL OUT OF MEMORY - RECOMPILE WITH LARGER SIZE!!" << endmsg;
85 abort(); /*NOTREACHED*/
86 return 0;
87 } else {
88 return ptr;
89 }
90 }
91
92 /** Release an item's memory by writing its location to the free list */
93 void
release(void * ptr)94 Pool::release (void *ptr)
95 {
96 free_list.write (&ptr, 1);
97 }
98
99 /*---------------------------------------------*/
100
MultiAllocSingleReleasePool(string n,unsigned long isize,unsigned long nitems)101 MultiAllocSingleReleasePool::MultiAllocSingleReleasePool (string n, unsigned long isize, unsigned long nitems)
102 : Pool (n, isize, nitems)
103 {
104 }
105
~MultiAllocSingleReleasePool()106 MultiAllocSingleReleasePool::~MultiAllocSingleReleasePool ()
107 {
108 }
109
SingleAllocMultiReleasePool(string n,unsigned long isize,unsigned long nitems)110 SingleAllocMultiReleasePool::SingleAllocMultiReleasePool (string n, unsigned long isize, unsigned long nitems)
111 : Pool (n, isize, nitems)
112 {
113 }
114
~SingleAllocMultiReleasePool()115 SingleAllocMultiReleasePool::~SingleAllocMultiReleasePool ()
116 {
117 }
118
119 void*
alloc()120 MultiAllocSingleReleasePool::alloc ()
121 {
122 void *ptr;
123 Glib::Threads::Mutex::Lock guard (m_lock);
124 ptr = Pool::alloc ();
125 return ptr;
126 }
127
128 void
release(void * ptr)129 MultiAllocSingleReleasePool::release (void* ptr)
130 {
131 Pool::release (ptr);
132 }
133
134 void*
alloc()135 SingleAllocMultiReleasePool::alloc ()
136 {
137 return Pool::alloc ();
138 }
139
140 void
release(void * ptr)141 SingleAllocMultiReleasePool::release (void* ptr)
142 {
143 Glib::Threads::Mutex::Lock guard (m_lock);
144 Pool::release (ptr);
145 }
146
147 /*-------------------------------------------------------*/
148
149 static void
free_per_thread_pool(void * ptr)150 free_per_thread_pool (void* ptr)
151 {
152 /* Rather than deleting the CrossThreadPool now, we add it to our trash buffer.
153 * This prevents problems if other threads still require access to this CrossThreadPool.
154 * We assume that some other agent will clean out the trash buffer as required.
155 */
156 CrossThreadPool* cp = static_cast<CrossThreadPool*> (ptr);
157 assert (cp);
158
159 if (cp->empty()) {
160 /* This CrossThreadPool is already empty, and the thread is finishing so nothing
161 * more can be added to it. We can just delete the pool.
162 */
163 delete cp;
164 } else {
165 /* This CrossThreadPool is not empty, meaning that there's some Events in it
166 * which another thread may yet read, so we can't delete the pool just yet.
167 * Put it in the trash and hope someone deals with it at some stage.
168 */
169 cp->parent()->add_to_trash (cp);
170 }
171 }
172
PerThreadPool()173 PerThreadPool::PerThreadPool ()
174 : _key (free_per_thread_pool)
175 , _trash (0)
176 {
177 }
178
179 /** Create a new CrossThreadPool and set the current thread's private _key to point to it.
180 * @param n Name.
181 * @param isize Size of each item in the pool.
182 * @param nitems Number of items in the pool.
183 */
184 void
create_per_thread_pool(string n,unsigned long isize,unsigned long nitems)185 PerThreadPool::create_per_thread_pool (string n, unsigned long isize, unsigned long nitems)
186 {
187 _key.set (new CrossThreadPool (n, isize, nitems, this));
188 }
189
190 /** @return True if CrossThreadPool for the current thread exists,
191 * False otherwise
192 */
193 bool
has_per_thread_pool()194 PerThreadPool::has_per_thread_pool ()
195 {
196 CrossThreadPool* p = _key.get();
197 if (p) {
198 return true;
199 }
200 return false;
201 }
202
203
204 /** @return CrossThreadPool for the current thread, which must previously have been created by
205 * calling create_per_thread_pool in the current thread.
206 */
207 CrossThreadPool*
per_thread_pool(bool must_exist)208 PerThreadPool::per_thread_pool (bool must_exist)
209 {
210 CrossThreadPool* p = _key.get();
211 if (!p && must_exist) {
212 fatal << "programming error: no per-thread pool \"" << _name << "\" for thread " << pthread_name() << endmsg;
213 abort(); /*NOTREACHED*/
214 }
215 return p;
216 }
217
218 void
set_trash(RingBuffer<CrossThreadPool * > * t)219 PerThreadPool::set_trash (RingBuffer<CrossThreadPool*>* t)
220 {
221 Glib::Threads::Mutex::Lock lm (_trash_mutex);
222 _trash = t;
223 }
224
225 /** Add a CrossThreadPool to our trash, if we have one. If not, a warning is emitted. */
226 void
add_to_trash(CrossThreadPool * p)227 PerThreadPool::add_to_trash (CrossThreadPool* p)
228 {
229 Glib::Threads::Mutex::Lock lm (_trash_mutex);
230
231 if (!_trash) {
232 warning << "Pool " << p->name() << " has no trash collector; a memory leak has therefore occurred" << endmsg;
233 return;
234 }
235
236 /* we have a lock here so that multiple threads can safely call add_to_trash (even though there
237 can only be one writer to the _trash RingBuffer)
238 */
239
240 _trash->write (&p, 1);
241 }
242
CrossThreadPool(string n,unsigned long isize,unsigned long nitems,PerThreadPool * p)243 CrossThreadPool::CrossThreadPool (string n, unsigned long isize, unsigned long nitems, PerThreadPool* p)
244 : Pool (n, isize, nitems)
245 , pending (nitems)
246 , _parent (p)
247 {
248
249 }
250
251 void
flush_pending_with_ev(void * ptr)252 CrossThreadPool::flush_pending_with_ev (void *ptr)
253 {
254 push (ptr);
255 flush_pending ();
256 }
257
258 void
flush_pending()259 CrossThreadPool::flush_pending ()
260 {
261 void* ptr;
262 bool did_release = false;
263
264 DEBUG_TRACE (DEBUG::Pool, string_compose ("%1 %2 has %3 pending free entries waiting, status size %4 free %5 used %6\n", pthread_name(), name(), pending.read_space(),
265 total(), available(), used()));
266
267 while (pending.read (&ptr, 1) == 1) {
268 DEBUG_TRACE (DEBUG::Pool, string_compose ("%1 %2 pushes back a pending free list entry before allocating\n", pthread_name(), name()));
269 free_list.write (&ptr, 1);
270 did_release = true;
271 }
272
273 if (did_release) {
274 DEBUG_TRACE (DEBUG::Pool, string_compose ("Pool size: %1 free %2 used %3 pending now %4\n", total(), available(), used(), pending_size()));
275 }
276 }
277
278 void*
alloc()279 CrossThreadPool::alloc ()
280 {
281 /* process anything waiting to be deleted (i.e. moved back to the free list) */
282 flush_pending ();
283 /* now allocate from the potentially larger free list */
284 return Pool::alloc ();
285 }
286
287 void
push(void * t)288 CrossThreadPool::push (void* t)
289 {
290 pending.write (&t, 1);
291 }
292
293 /** @return true if there is nothing in this pool */
294 bool
empty()295 CrossThreadPool::empty ()
296 {
297 return (free_list.write_space() == pending.read_space());
298 }
299
300