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