Hi JS, I would put this type of functionality in a catagory of optional compile debug support, as the performance implication of checking for a callback would be prohibitive. CMake is our friend here of course :-)
This close to a stable release I want to keep the code base a stable as possible, and just focus on fixing bugs without introducing any new functionality. Once OSG 2.8 is out the door I can open the door to new functionality like this proposal. As I have plenty of other work to do on just resolve all the build/bug fixes tasks I'll refrain from diving in on discussions about this particular proposal. Cheers, Robert. On Mon, Jan 26, 2009 at 8:04 PM, Jean-Sébastien Guay <[email protected]> wrote: > 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 > > _______________________________________________ osg-submissions mailing list [email protected] http://lists.openscenegraph.org/listinfo.cgi/osg-submissions-openscenegraph.org
