Suspend deletion of cross-thread pools until they are empty. Prevents crashes when the freeze thread completes.
git-svn-id: svn://localhost/ardour2/branches/3.0@6893 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
parent
72f8544b24
commit
8783fc35f2
@ -22,11 +22,20 @@
|
|||||||
|
|
||||||
#include <glibmm/thread.h>
|
#include <glibmm/thread.h>
|
||||||
|
|
||||||
|
#include "pbd/ringbuffer.h"
|
||||||
|
#include "pbd/pool.h"
|
||||||
#include "ardour/types.h"
|
#include "ardour/types.h"
|
||||||
#include "ardour/session_handle.h"
|
#include "ardour/session_handle.h"
|
||||||
|
|
||||||
namespace ARDOUR {
|
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
|
class Butler : public SessionHandleRef
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -67,6 +76,10 @@ class Butler : public SessionHandleRef
|
|||||||
int request_pipe[2];
|
int request_pipe[2];
|
||||||
uint32_t audio_dstream_buffer_size;
|
uint32_t audio_dstream_buffer_size;
|
||||||
uint32_t midi_dstream_buffer_size;
|
uint32_t midi_dstream_buffer_size;
|
||||||
|
RingBuffer<CrossThreadPool*> pool_trash;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void empty_pool_trash ();
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ARDOUR
|
} // namespace ARDOUR
|
||||||
|
@ -117,6 +117,8 @@ struct SessionEvent {
|
|||||||
private:
|
private:
|
||||||
static PerThreadPool* pool;
|
static PerThreadPool* pool;
|
||||||
CrossThreadPool* own_pool;
|
CrossThreadPool* own_pool;
|
||||||
|
|
||||||
|
friend class Butler;
|
||||||
};
|
};
|
||||||
|
|
||||||
class SessionEventManager {
|
class SessionEventManager {
|
||||||
|
@ -43,8 +43,10 @@ Butler::Butler(Session& s)
|
|||||||
, thread(0)
|
, thread(0)
|
||||||
, audio_dstream_buffer_size(0)
|
, audio_dstream_buffer_size(0)
|
||||||
, midi_dstream_buffer_size(0)
|
, midi_dstream_buffer_size(0)
|
||||||
|
, pool_trash(16)
|
||||||
{
|
{
|
||||||
g_atomic_int_set(&should_do_transport_work, 0);
|
g_atomic_int_set(&should_do_transport_work, 0);
|
||||||
|
SessionEvent::pool->set_trash (&pool_trash);
|
||||||
}
|
}
|
||||||
|
|
||||||
Butler::~Butler()
|
Butler::~Butler()
|
||||||
@ -331,6 +333,8 @@ Butler::thread_work ()
|
|||||||
|
|
||||||
paused.signal();
|
paused.signal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
empty_pool_trash ();
|
||||||
}
|
}
|
||||||
|
|
||||||
pthread_exit_pbd (0);
|
pthread_exit_pbd (0);
|
||||||
@ -394,4 +398,35 @@ Butler::write_data_rate () const
|
|||||||
return _write_data_rate > 10485.7600000f ? 0.0f : _write_data_rate;
|
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
|
} // namespace ARDOUR
|
||||||
|
|
||||||
|
@ -27,6 +27,9 @@
|
|||||||
|
|
||||||
#include "pbd/ringbuffer.h"
|
#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
|
class Pool
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -39,11 +42,11 @@ class Pool
|
|||||||
std::string name() const { return _name; }
|
std::string name() const { return _name; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
RingBuffer<void*> free_list;
|
RingBuffer<void*> free_list; ///< a list of pointers to free items within block
|
||||||
std::string _name;
|
std::string _name;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void *block;
|
void *block; ///< data storage area
|
||||||
};
|
};
|
||||||
|
|
||||||
class SingleAllocMultiReleasePool : public Pool
|
class SingleAllocMultiReleasePool : public Pool
|
||||||
@ -73,18 +76,31 @@ class MultiAllocSingleReleasePool : public Pool
|
|||||||
Glib::Mutex* m_lock;
|
Glib::Mutex* m_lock;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class PerThreadPool;
|
||||||
|
|
||||||
|
/** A per-thread pool of data */
|
||||||
class CrossThreadPool : public Pool
|
class CrossThreadPool : public Pool
|
||||||
{
|
{
|
||||||
public:
|
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* alloc ();
|
||||||
void push (void *);
|
void push (void *);
|
||||||
|
|
||||||
|
PerThreadPool* parent () const {
|
||||||
|
return _parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool empty ();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
RingBuffer<void*> pending;
|
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
|
class PerThreadPool
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -94,12 +110,20 @@ class PerThreadPool
|
|||||||
|
|
||||||
void create_per_thread_pool (std::string name, unsigned long item_size, unsigned long nitems);
|
void create_per_thread_pool (std::string name, unsigned long item_size, unsigned long nitems);
|
||||||
CrossThreadPool* per_thread_pool ();
|
CrossThreadPool* per_thread_pool ();
|
||||||
|
|
||||||
|
void set_trash (RingBuffer<CrossThreadPool*>* t) {
|
||||||
|
_trash = t;
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_to_trash (CrossThreadPool *);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
GPrivate* _key;
|
GPrivate* _key;
|
||||||
std::string _name;
|
std::string _name;
|
||||||
unsigned long _item_size;
|
unsigned long _item_size;
|
||||||
unsigned long _nitems;
|
unsigned long _nitems;
|
||||||
|
RingBuffer<CrossThreadPool*>* _trash;
|
||||||
|
Glib::Mutex _trash_write_mutex;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // __qm_pool_h__
|
#endif // __qm_pool_h__
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
#include "pbd/pool.h"
|
#include "pbd/pool.h"
|
||||||
#include "pbd/error.h"
|
#include "pbd/error.h"
|
||||||
@ -57,13 +58,14 @@ Pool::~Pool ()
|
|||||||
free (block);
|
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 *
|
void *
|
||||||
Pool::alloc ()
|
Pool::alloc ()
|
||||||
{
|
{
|
||||||
void *ptr;
|
void *ptr;
|
||||||
|
|
||||||
// cerr << _name << " pool " << " alloc, thread = " << pthread_name() << " space = " << free_list->read_space() << endl;
|
|
||||||
|
|
||||||
if (free_list.read (&ptr, 1) < 1) {
|
if (free_list.read (&ptr, 1) < 1) {
|
||||||
fatal << "CRITICAL: " << _name << " POOL OUT OF MEMORY - RECOMPILE WITH LARGER SIZE!!" << endmsg;
|
fatal << "CRITICAL: " << _name << " POOL OUT OF MEMORY - RECOMPILE WITH LARGER SIZE!!" << endmsg;
|
||||||
/*NOTREACHED*/
|
/*NOTREACHED*/
|
||||||
@ -73,18 +75,18 @@ Pool::alloc ()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Release an item's memory by writing its location to the free list */
|
||||||
void
|
void
|
||||||
Pool::release (void *ptr)
|
Pool::release (void *ptr)
|
||||||
{
|
{
|
||||||
free_list.write (&ptr, 1);
|
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)
|
MultiAllocSingleReleasePool::MultiAllocSingleReleasePool (string n, unsigned long isize, unsigned long nitems)
|
||||||
: Pool (n, isize, nitems),
|
: Pool (n, isize, nitems)
|
||||||
m_lock(0)
|
, m_lock(0)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,8 +96,8 @@ MultiAllocSingleReleasePool::~MultiAllocSingleReleasePool ()
|
|||||||
}
|
}
|
||||||
|
|
||||||
SingleAllocMultiReleasePool::SingleAllocMultiReleasePool (string n, unsigned long isize, unsigned long nitems)
|
SingleAllocMultiReleasePool::SingleAllocMultiReleasePool (string n, unsigned long isize, unsigned long nitems)
|
||||||
: Pool (n, isize, nitems),
|
: Pool (n, isize, nitems)
|
||||||
m_lock(0)
|
, m_lock(0)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,11 +150,18 @@ SingleAllocMultiReleasePool::release (void* ptr)
|
|||||||
static void
|
static void
|
||||||
free_per_thread_pool (void* ptr)
|
free_per_thread_pool (void* ptr)
|
||||||
{
|
{
|
||||||
Pool* pptr = static_cast<Pool*>(ptr);
|
/* Rather than deleting the CrossThreadPool now, we add it to our trash buffer.
|
||||||
delete pptr;
|
* 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 ()
|
PerThreadPool::PerThreadPool ()
|
||||||
|
: _trash (0)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
/* for some reason this appears necessary to get glib's thread private stuff to work */
|
/* for some reason this appears necessary to get glib's thread private stuff to work */
|
||||||
@ -163,13 +172,21 @@ PerThreadPool::PerThreadPool ()
|
|||||||
_key = g_private_new (free_per_thread_pool);
|
_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
|
void
|
||||||
PerThreadPool::create_per_thread_pool (string n, unsigned long isize, unsigned long nitems)
|
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);
|
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*
|
CrossThreadPool*
|
||||||
PerThreadPool::per_thread_pool ()
|
PerThreadPool::per_thread_pool ()
|
||||||
{
|
{
|
||||||
@ -181,9 +198,27 @@ PerThreadPool::per_thread_pool ()
|
|||||||
return p;
|
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)
|
: Pool (n, isize, nitems)
|
||||||
, pending (nitems)
|
, pending (nitems)
|
||||||
|
, _parent (p)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -203,3 +238,11 @@ CrossThreadPool::push (void* t)
|
|||||||
{
|
{
|
||||||
pending.write (&t, 1);
|
pending.write (&t, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @return true if there is nothing in this pool */
|
||||||
|
bool
|
||||||
|
CrossThreadPool::empty ()
|
||||||
|
{
|
||||||
|
return (free_list.write_space() == pending.read_space());
|
||||||
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user