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

Reply via email to