#include <glibmm/thread.h>
+#include "pbd/ringbuffer.h"
+#include "pbd/pool.h"
#include "ardour/types.h"
#include "ardour/session_handle.h"
namespace ARDOUR {
+/**
+ * One of the Butler's functions is to clean up (ie delete) unused CrossThreadPools.
+ * When a thread with a CrossThreadPool terminates, its CTP is added to pool_trash.
+ * When the Butler thread wakes up, we check this trash buffer for CTPs, and if they
+ * are empty they are deleted.
+ */
+
class Butler : public SessionHandleRef
{
public:
int request_pipe[2];
uint32_t audio_dstream_buffer_size;
uint32_t midi_dstream_buffer_size;
+ RingBuffer<CrossThreadPool*> pool_trash;
+
+private:
+ void empty_pool_trash ();
};
} // namespace ARDOUR
, thread(0)
, audio_dstream_buffer_size(0)
, midi_dstream_buffer_size(0)
+ , pool_trash(16)
{
g_atomic_int_set(&should_do_transport_work, 0);
+ SessionEvent::pool->set_trash (&pool_trash);
}
Butler::~Butler()
paused.signal();
}
+
+ empty_pool_trash ();
}
pthread_exit_pbd (0);
return _write_data_rate > 10485.7600000f ? 0.0f : _write_data_rate;
}
+void
+Butler::empty_pool_trash ()
+{
+ /* look in the trash, deleting empty pools until we come to one that is not empty */
+
+ RingBuffer<CrossThreadPool*>::rw_vector vec;
+ pool_trash.get_read_vector (&vec);
+
+ guint deleted = 0;
+
+ for (int i = 0; i < 2; ++i) {
+ for (guint j = 0; j < vec.len[i]; ++j) {
+ if (vec.buf[i][j]->empty()) {
+ delete vec.buf[i][j];
+ ++deleted;
+ } else {
+ /* found a non-empty pool, so stop deleting */
+ if (deleted) {
+ pool_trash.increment_read_idx (deleted);
+ }
+ return;
+ }
+ }
+ }
+
+ if (deleted) {
+ pool_trash.increment_read_idx (deleted);
+ }
+}
+
} // namespace ARDOUR
+
#include "pbd/ringbuffer.h"
+/** A pool of data items that can be allocated, read from and written to
+ * without system memory allocation or locking.
+ */
class Pool
{
public:
std::string name() const { return _name; }
protected:
- RingBuffer<void*> free_list;
+ RingBuffer<void*> free_list; ///< a list of pointers to free items within block
std::string _name;
private:
- void *block;
+ void *block; ///< data storage area
};
class SingleAllocMultiReleasePool : public Pool
Glib::Mutex* m_lock;
};
+class PerThreadPool;
+
+/** A per-thread pool of data */
class CrossThreadPool : public Pool
{
public:
- CrossThreadPool (std::string n, unsigned long isize, unsigned long nitems);
+ CrossThreadPool (std::string n, unsigned long isize, unsigned long nitems, PerThreadPool *);
void* alloc ();
void push (void *);
+
+ PerThreadPool* parent () const {
+ return _parent;
+ }
+
+ bool empty ();
private:
RingBuffer<void*> pending;
+ PerThreadPool* _parent;
};
+/** A class to manage per-thread pools of memory. One object of this class is instantiated,
+ * and then it is used to create per-thread pools as required.
+ */
class PerThreadPool
{
public:
void create_per_thread_pool (std::string name, unsigned long item_size, unsigned long nitems);
CrossThreadPool* per_thread_pool ();
-
+
+ void set_trash (RingBuffer<CrossThreadPool*>* t) {
+ _trash = t;
+ }
+
+ void add_to_trash (CrossThreadPool *);
+
private:
GPrivate* _key;
std::string _name;
unsigned long _item_size;
unsigned long _nitems;
+ RingBuffer<CrossThreadPool*>* _trash;
+ Glib::Mutex _trash_write_mutex;
};
#endif // __qm_pool_h__
#include <iostream>
#include <vector>
#include <cstdlib>
+#include <cassert>
#include "pbd/pool.h"
#include "pbd/error.h"
free (block);
}
+/** Allocate an item's worth of memory in the Pool by taking one from the free list.
+ * @return Pointer to free item.
+ */
void *
Pool::alloc ()
{
void *ptr;
-// cerr << _name << " pool " << " alloc, thread = " << pthread_name() << " space = " << free_list->read_space() << endl;
-
if (free_list.read (&ptr, 1) < 1) {
fatal << "CRITICAL: " << _name << " POOL OUT OF MEMORY - RECOMPILE WITH LARGER SIZE!!" << endmsg;
/*NOTREACHED*/
}
}
+/** Release an item's memory by writing its location to the free list */
void
Pool::release (void *ptr)
{
free_list.write (&ptr, 1);
-// cerr << _name << ": release, now has " << free_list->read_space() << endl;
}
/*---------------------------------------------*/
MultiAllocSingleReleasePool::MultiAllocSingleReleasePool (string n, unsigned long isize, unsigned long nitems)
- : Pool (n, isize, nitems),
- m_lock(0)
+ : Pool (n, isize, nitems)
+ , m_lock(0)
{
}
}
SingleAllocMultiReleasePool::SingleAllocMultiReleasePool (string n, unsigned long isize, unsigned long nitems)
- : Pool (n, isize, nitems),
- m_lock(0)
+ : Pool (n, isize, nitems)
+ , m_lock(0)
{
}
static void
free_per_thread_pool (void* ptr)
{
- Pool* pptr = static_cast<Pool*>(ptr);
- delete pptr;
+ /* Rather than deleting the CrossThreadPool now, we add it to our trash buffer.
+ * This prevents problems if other threads still require access to this CrossThreadPool.
+ * We assume that some other agent will clean out the trash buffer as required.
+ */
+ CrossThreadPool* cp = static_cast<CrossThreadPool*> (ptr);
+ assert (cp);
+
+ cp->parent()->add_to_trash (cp);
}
PerThreadPool::PerThreadPool ()
+ : _trash (0)
{
{
/* for some reason this appears necessary to get glib's thread private stuff to work */
_key = g_private_new (free_per_thread_pool);
}
+/** Create a new CrossThreadPool and set the current thread's private _key to point to it.
+ * @param n Name.
+ * @param isize Size of each item in the pool.
+ * @param nitems Number of items in the pool.
+ */
void
PerThreadPool::create_per_thread_pool (string n, unsigned long isize, unsigned long nitems)
{
- Pool* p = new CrossThreadPool (n, isize, nitems);
+ CrossThreadPool* p = new CrossThreadPool (n, isize, nitems, this);
g_private_set (_key, p);
}
+/** @return CrossThreadPool for the current thread, which must previously have been created by
+ * calling create_per_thread_pool in the current thread.
+ */
CrossThreadPool*
PerThreadPool::per_thread_pool ()
{
return p;
}
-CrossThreadPool::CrossThreadPool (string n, unsigned long isize, unsigned long nitems)
+/** Add a CrossThreadPool to our trash, if we have one. If not, a warning is emitted. */
+void
+PerThreadPool::add_to_trash (CrossThreadPool* p)
+{
+ if (!_trash) {
+ warning << "Pool " << p->name() << " has no trash collector; a memory leak has therefore occurred" << endmsg;
+ return;
+ }
+
+ /* we have a lock here so that multiple threads can safely call add_to_trash (even though there
+ can only be one writer to the _trash RingBuffer)
+ */
+
+ Glib::Mutex::Lock lm (_trash_write_mutex);
+ _trash->write (&p, 1);
+}
+
+CrossThreadPool::CrossThreadPool (string n, unsigned long isize, unsigned long nitems, PerThreadPool* p)
: Pool (n, isize, nitems)
, pending (nitems)
+ , _parent (p)
{
}
{
pending.write (&t, 1);
}
+
+/** @return true if there is nothing in this pool */
+bool
+CrossThreadPool::empty ()
+{
+ return (free_list.write_space() == pending.read_space());
+}
+