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

Reply via email to