Hi Robert,
(it might not be a good time for this, so if you want to push this
discussion to after 2.8, it's fine by me)
In light of my recent ordeal with looking for "memory leaks" (i.e.
cycles and other ref counting issues), I was wondering if you would
accept some changes to osg::Referenced which made it a bit easier to
find the source of the problem.
Just to be clear, this is more of a request for comments than a real
submission. All I'm saying is that this was useful to me, so it might be
useful to others, perhaps with some modifications.
See the attached files. It's pretty self-explanatory. Basically, if you
want you can register a callback object with osg::Referenced, which will
be called :
* when a new osg::Referenced is created
* when an osg::Referenced is destroyed
* when ref() is called on an osg::Referenced
* when unref() is called on an osg::Referenced
* when unref_nodelete() is called on an osg::Referenced
each time passing the osg::Referenced object. Then the observer can do
what it wants at any or all of these points.
In my case, I recorded a call stack to keep track of all events, and
then I could dump the events I wanted to see to a file. And since I
recorded the events in maps keyed by osg::Referenced pointer, I could
easily do differences between one state and another.
It won't do all the work for you (in my case, I had to code the part
that records the call stack which is platform- and compiler-specific,
and the parts in my application that dumped the stacks to file, and then
I had to analyse those stacks to see which objects were still live and
why) but it's a good tool I think.
Then, of course, there's the issue of performance. First of all, if
there's no registered observer, there will be one check per operation
(ctor, dtor, ref, unref, unref_nodelete). But this is only if it's
enabled in the osg::Referenced header via two #defines (which are
commented by default), one for allocation/destruction (which was already
there) and one for refs and unrefs. So you can control which events you
want to get. If these are not enabled, then there is no performance
penalty. The difference will be just one static pointer in osg::Referenced.
So, comments? I don't think this is an exaggerated amount of code to
add, and it was certainly useful to me. But I could see how you might
argue that if no one has needed this before, it might not be generally
useful. If you agree with the principle but not the execution, please
comment and I'll try and improve the code.
Thanks,
J-S
--
______________________________________________________
Jean-Sebastien Guay [email protected]
http://www.cm-labs.com/
http://whitestar02.webhop.org/
/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield
*
* This library is open source and may be redistributed and/or modified under
* the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or
* (at your option) any later version. The full license is in LICENSE file
* included with this distribution, and on the openscenegraph.org website.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* OpenSceneGraph Public License for more details.
*/
#ifndef OSG_REFERENCED
#define OSG_REFERENCED 1
// When building OSG with Java need to derive from Noodle::CBridgable class,
#include <osg/Export>
#include <OpenThreads/ScopedLock>
#include <OpenThreads/Mutex>
#include <OpenThreads/Atomic>
#if !defined(_OPENTHREADS_ATOMIC_USE_MUTEX)
# define _OSG_REFERENCED_USE_ATOMIC_OPERATIONS
#endif
#ifdef _DEBUG
// Define this to print referenced object count to stdout, as well as to get
// notified in the AllocationObserver when a Referenced object is created
// and destroyed.
//#define DEBUG_OBJECT_ALLOCATION_DESTRUCTION
// Define this to get notified in the AllocationObserver when a Referenced
// object's refefence count increases or decreases.
//#define DEBUG_OBJECT_REF_UNREF
#endif
namespace osg {
// forward declare, declared after Referenced below.
class DeleteHandler;
class Observer;
/** Base class from providing referencing counted objects.*/
class OSG_EXPORT Referenced
{
public:
Referenced();
explicit Referenced(bool threadSafeRefUnref);
Referenced(const Referenced&);
inline Referenced& operator = (const Referenced&) { return *this; }
/** Set whether to use a mutex to ensure ref() and unref() are thread
safe.*/
virtual void setThreadSafeRefUnref(bool threadSafe);
/** Get whether a mutex is used to ensure ref() and unref() are thread
safe.*/
#if defined(_OSG_REFERENCED_USE_ATOMIC_OPERATIONS)
bool getThreadSafeRefUnref() const { return true; }
#else
bool getThreadSafeRefUnref() const { return _refMutex!=0; }
#endif
/** Get the mutex used to ensure thread safety of ref()/unref(). */
#if defined(_OSG_REFERENCED_USE_ATOMIC_OPERATIONS)
OpenThreads::Mutex* getRefMutex() const { return
getGlobalReferencedMutex(); }
#else
OpenThreads::Mutex* getRefMutex() const { return _refMutex; }
#endif
/** Get the optional global Referenced mutex, this can be shared
between all osg::Referenced.*/
static OpenThreads::Mutex* getGlobalReferencedMutex();
/** Increment the reference count by one, indicating that
this object has another pointer which is referencing it.*/
inline void ref() const;
/** Decrement the reference count by one, indicating that
a pointer to this object is referencing it. If the
reference count goes to zero, it is assumed that this object
is no longer referenced and is automatically deleted.*/
inline void unref() const;
/** Decrement the reference count by one, indicating that
a pointer to this object is referencing it. However, do
not delete it, even if ref count goes to 0. Warning,
unref_nodelete()
should only be called if the user knows exactly who will
be responsible for, one should prefer unref() over unref_nodelete()
as the later can lead to memory leaks.*/
void unref_nodelete() const;
/** Return the number pointers currently referencing this object. */
inline int referenceCount() const { return _refCount; }
/** Add a Observer that is observing this object, notify the Observer
when this object gets deleted.*/
void addObserver(Observer* observer);
/** Add a Observer that is observing this object, notify the Observer
when this object gets deleted.*/
void removeObserver(Observer* observer);
public:
/** Set whether reference counting should be use a mutex to create
thread reference counting.*/
static void setThreadSafeReferenceCounting(bool
enableThreadSafeReferenceCounting);
/** Get whether reference counting is active.*/
static bool getThreadSafeReferenceCounting();
friend class DeleteHandler;
/** Set a DeleteHandler to which deletion of all referenced counted
objects
* will be delegated to.*/
static void setDeleteHandler(DeleteHandler* handler);
/** Get a DeleteHandler.*/
static DeleteHandler* getDeleteHandler();
public:
/** Observer which will get called when an osg::Referenced object is
created or destroyed, and when the reference count changes.
Needs to be enabled when compiling the OSG library. */
class AllocationObserver
{
public:
virtual ~AllocationObserver() {}
virtual void objectCreated(const Referenced* object) {};
virtual void objectDestroyed(const Referenced* object) {};
virtual void ref(const Referenced* object) {};
virtual void unref(const Referenced* object) {};
virtual void unref_nodelete(const Referenced* object) {};
};
/** Set the current AllocationObserver. */
static void setAllocationObserver(AllocationObserver* observer) {
s_allocationObserver = observer; }
protected:
virtual ~Referenced();
void deleteUsingDeleteHandler() const;
#if defined(_OSG_REFERENCED_USE_ATOMIC_OPERATIONS)
struct ObserverSetData;
OpenThreads::AtomicPtr _observerSetDataPtr;
mutable OpenThreads::Atomic _refCount;
#else
mutable OpenThreads::Mutex* _refMutex;
mutable int _refCount;
void* _observers;
#endif
static AllocationObserver* s_allocationObserver;
};
inline void Referenced::ref() const
{
#if defined(_OSG_REFERENCED_USE_ATOMIC_OPERATIONS)
++_refCount;
#else
if (_refMutex)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(*_refMutex);
++_refCount;
}
else
{
++_refCount;
}
#endif
#ifdef DEBUG_OBJECT_REF_UNREF
if (s_allocationObserver)
s_allocationObserver->ref(this);
#endif
}
inline void Referenced::unref() const
{
#if defined(_OSG_REFERENCED_USE_ATOMIC_OPERATIONS)
bool needDelete = (--_refCount == 0);
#else
bool needDelete = false;
if (_refMutex)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(*_refMutex);
--_refCount;
needDelete = _refCount<=0;
}
else
{
--_refCount;
needDelete = _refCount<=0;
}
#endif
#ifdef DEBUG_OBJECT_REF_UNREF
if (s_allocationObserver)
s_allocationObserver->unref(this);
#endif
if (needDelete)
{
if (getDeleteHandler()) deleteUsingDeleteHandler();
else delete this;
}
}
// intrusive_ptr_add_ref and intrusive_ptr_release allow
// use of osg Referenced classes with boost::intrusive_ptr
inline void intrusive_ptr_add_ref(Referenced* p) { p->ref(); }
inline void intrusive_ptr_release(Referenced* p) { p->unref(); }
}
#endif
/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield
*
* This library is open source and may be redistributed and/or modified under
* the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or
* (at your option) any later version. The full license is in LICENSE file
* included with this distribution, and on the openscenegraph.org website.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* OpenSceneGraph Public License for more details.
*/
#include <stdlib.h>
#include <osg/Referenced>
#include <osg/Notify>
#include <osg/ApplicationUsage>
#include <osg/observer_ptr>
#include <typeinfo>
#include <memory>
#include <set>
#include <OpenThreads/ScopedLock>
#include <OpenThreads/Mutex>
#include <osg/DeleteHandler>
namespace osg
{
//#define ENFORCE_THREADSAFE
// specialized smart pointer, used to get round auto_ptr<>'s lack of the
destructor reseting itself to 0.
struct DeleteHandlerPointer
{
DeleteHandlerPointer():
_ptr(0) {}
DeleteHandlerPointer(DeleteHandler* ptr):
_ptr(ptr) {}
~DeleteHandlerPointer()
{
delete _ptr;
_ptr = 0;
}
inline DeleteHandlerPointer& operator = (DeleteHandler* ptr)
{
if (_ptr==ptr) return *this;
delete _ptr;
_ptr = ptr;
return *this;
}
void reset(DeleteHandler* ptr)
{
if (_ptr==ptr) return;
delete _ptr;
_ptr = ptr;
}
inline DeleteHandler& operator*() { return *_ptr; }
inline const DeleteHandler& operator*() const { return *_ptr; }
inline DeleteHandler* operator->() { return _ptr; }
inline const DeleteHandler* operator->() const { return _ptr; }
DeleteHandler* get() { return _ptr; }
const DeleteHandler* get() const { return _ptr; }
DeleteHandler* _ptr;
};
OpenThreads::Mutex* Referenced::getGlobalReferencedMutex()
{
static OpenThreads::Mutex s_ReferencedGlobalMutext;
return &s_ReferencedGlobalMutext;
}
typedef std::set<Observer*> ObserverSet;
#if defined(_OSG_REFERENCED_USE_ATOMIC_OPERATIONS)
struct Referenced::ObserverSetData {
OpenThreads::Mutex _mutex;
ObserverSet _observers;
};
#endif
#if !defined(_OSG_REFERENCED_USE_ATOMIC_OPERATIONS)
static bool s_useThreadSafeReferenceCounting =
getenv("OSG_THREAD_SAFE_REF_UNREF")!=0;
#endif
// static std::auto_ptr<DeleteHandler> s_deleteHandler(0);
static DeleteHandlerPointer s_deleteHandler(0);
static ApplicationUsageProxy
Referenced_e0(ApplicationUsage::ENVIRONMENTAL_VARIABLE,"OSG_THREAD_SAFE_REF_UNREF","");
void Referenced::setThreadSafeReferenceCounting(bool
enableThreadSafeReferenceCounting)
{
#if !defined(_OSG_REFERENCED_USE_ATOMIC_OPERATIONS)
s_useThreadSafeReferenceCounting = enableThreadSafeReferenceCounting;
#endif
}
bool Referenced::getThreadSafeReferenceCounting()
{
#if defined(_OSG_REFERENCED_USE_ATOMIC_OPERATIONS)
return true;
#else
return s_useThreadSafeReferenceCounting;
#endif
}
void Referenced::setDeleteHandler(DeleteHandler* handler)
{
s_deleteHandler.reset(handler);
}
DeleteHandler* Referenced::getDeleteHandler()
{
return s_deleteHandler.get();
}
#ifdef DEBUG_OBJECT_ALLOCATION_DESTRUCTION
OpenThreads::Mutex& getNumObjectMutex()
{
static OpenThreads::Mutex s_numObjectMutex;
return s_numObjectMutex;
}
static int s_numObjects = 0;
Referenced::AllocationObserver* Referenced::s_allocationObserver = 0;
#endif
Referenced::Referenced():
#if defined(_OSG_REFERENCED_USE_ATOMIC_OPERATIONS)
_observerSetDataPtr(0),
_refCount(0)
#else
_refMutex(0),
_refCount(0),
_observers(0)
#endif
{
#if !defined(_OSG_REFERENCED_USE_ATOMIC_OPERATIONS)
#ifndef ENFORCE_THREADSAFE
if (s_useThreadSafeReferenceCounting)
#endif
_refMutex = new OpenThreads::Mutex;
#endif
#ifdef DEBUG_OBJECT_ALLOCATION_DESTRUCTION
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(getNumObjectMutex());
++s_numObjects;
osg::notify(osg::NOTICE)<<"Object created, total
num="<<s_numObjects<<std::endl;
}
if (s_allocationObserver)
s_allocationObserver->objectCreated(this);
#endif
}
Referenced::Referenced(bool threadSafeRefUnref):
#if defined(_OSG_REFERENCED_USE_ATOMIC_OPERATIONS)
_observerSetDataPtr(0),
_refCount(0)
#else
_refMutex(0),
_refCount(0),
_observers(0)
#endif
{
#if !defined(_OSG_REFERENCED_USE_ATOMIC_OPERATIONS)
#ifndef ENFORCE_THREADSAFE
if (threadSafeRefUnref)
#endif
_refMutex = new OpenThreads::Mutex;
#endif
#ifdef DEBUG_OBJECT_ALLOCATION_DESTRUCTION
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(getNumObjectMutex());
++s_numObjects;
osg::notify(osg::NOTICE)<<"Object created, total
num="<<s_numObjects<<std::endl;
}
if (s_allocationObserver)
s_allocationObserver->objectCreated(this);
#endif
}
Referenced::Referenced(const Referenced&):
#if defined(_OSG_REFERENCED_USE_ATOMIC_OPERATIONS)
_observerSetDataPtr(0),
_refCount(0)
#else
_refMutex(0),
_refCount(0),
_observers(0)
#endif
{
#if !defined(_OSG_REFERENCED_USE_ATOMIC_OPERATIONS)
#ifndef ENFORCE_THREADSAFE
if (s_useThreadSafeReferenceCounting)
#endif
_refMutex = new OpenThreads::Mutex;
#endif
#ifdef DEBUG_OBJECT_ALLOCATION_DESTRUCTION
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(getNumObjectMutex());
++s_numObjects;
osg::notify(osg::NOTICE)<<"Object created, total
num="<<s_numObjects<<std::endl;
}
if (s_allocationObserver)
s_allocationObserver->objectCreated(this);
#endif
}
Referenced::~Referenced()
{
#ifdef DEBUG_OBJECT_ALLOCATION_DESTRUCTION
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(getNumObjectMutex());
--s_numObjects;
osg::notify(osg::NOTICE)<<"Object deleted, total
num="<<s_numObjects<<std::endl;
}
if (s_allocationObserver)
s_allocationObserver->objectDestroyed(this);
#endif
if (_refCount>0)
{
notify(WARN)<<"Warning: deleting still referenced object "<<this<<" of
type '"<<typeid(this).name()<<"'"<<std::endl;
notify(WARN)<<" the final reference count was "<<_refCount<<",
memory corruption possible."<<std::endl;
}
#if !defined(_OSG_REFERENCED_USE_ATOMIC_OPERATIONS)
if (_observers)
{
ObserverSet* os = static_cast<ObserverSet*>(_observers);
for(ObserverSet::iterator itr = os->begin();
itr != os->end();
++itr)
{
(*itr)->objectDeleted(this);
}
delete os;
_observers = 0;
}
if (_refMutex)
{
OpenThreads::Mutex* tmpMutexPtr = _refMutex;
_refMutex = 0;
delete tmpMutexPtr;
}
#else
ObserverSetData* observerSetData =
static_cast<ObserverSetData*>(_observerSetDataPtr.get());
if (observerSetData)
{
for(ObserverSet::iterator itr = observerSetData->_observers.begin();
itr != observerSetData->_observers.end();
++itr)
{
(*itr)->objectDeleted(this);
}
_observerSetDataPtr.assign(0, observerSetData);
delete observerSetData;
}
#endif
}
void Referenced::setThreadSafeRefUnref(bool threadSafe)
{
#if !defined(_OSG_REFERENCED_USE_ATOMIC_OPERATIONS)
if (threadSafe)
{
if (!_refMutex)
{
// we want thread safe ref()/unref() so assign a mutex
_refMutex = new OpenThreads::Mutex;
}
}
else
{
if (_refMutex)
{
// we don't want thread safe ref()/unref() so remove any assigned
mutex
OpenThreads::Mutex* tmpMutexPtr = _refMutex;
_refMutex = 0;
delete tmpMutexPtr;
}
}
#endif
}
void Referenced::unref_nodelete() const
{
#if !defined(_OSG_REFERENCED_USE_ATOMIC_OPERATIONS)
if (_refMutex)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(*_refMutex);
--_refCount;
}
else
{
--_refCount;
}
#else
--_refCount;
#endif
#ifdef DEBUG_OBJECT_REF_UNREF
if (s_allocationObserver)
s_allocationObserver->unref_nodelete(this);
#endif
}
void Referenced::addObserver(Observer* observer)
{
#if defined(_OSG_REFERENCED_USE_ATOMIC_OPERATIONS)
ObserverSetData* observerSetData =
static_cast<ObserverSetData*>(_observerSetDataPtr.get());
while (0 == observerSetData) {
ObserverSetData* newObserverSetData = new ObserverSetData;
if (!_observerSetDataPtr.assign(newObserverSetData, 0))
delete newObserverSetData;
observerSetData =
static_cast<ObserverSetData*>(_observerSetDataPtr.get());
}
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(observerSetData->_mutex);
observerSetData->_observers.insert(observer);
#else
if (_refMutex)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(*_refMutex);
if (!_observers) _observers = new ObserverSet;
if (_observers) static_cast<ObserverSet*>(_observers)->insert(observer);
}
else
{
if (!_observers) _observers = new ObserverSet;
if (_observers) static_cast<ObserverSet*>(_observers)->insert(observer);
}
#endif
}
void Referenced::removeObserver(Observer* observer)
{
#if defined(_OSG_REFERENCED_USE_ATOMIC_OPERATIONS)
ObserverSetData* observerSetData =
static_cast<ObserverSetData*>(_observerSetDataPtr.get());
if (observerSetData)
{
OpenThreads::ScopedLock<OpenThreads::Mutex>
lock(observerSetData->_mutex);
observerSetData->_observers.erase(observer);
}
#else
if (_refMutex)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(*_refMutex);
if (_observers) static_cast<ObserverSet*>(_observers)->erase(observer);
}
else
{
if (_observers) static_cast<ObserverSet*>(_observers)->erase(observer);
}
#endif
}
void Referenced::deleteUsingDeleteHandler() const
{
getDeleteHandler()->requestDelete(this);
}
} // end of namespace osg
_______________________________________________
osg-submissions mailing list
[email protected]
http://lists.openscenegraph.org/listinfo.cgi/osg-submissions-openscenegraph.org