Robert,

I just wanted to respond with my investigations and observations.


> 
> I sounds like the lack of a readRefFontFile() is a potential problem.
> In the case of include/osgDB/ReadFile there are readRefNodeFile() etc.
> functions that pass back a ref_ptr<> rather than a C pointer, and are
> all safer in multi-thread situations where object cache is in play.
> Could you try implementing a readRefFontFile() and convert osgText
> across to use this to see if it helps.
> 


So, I am mistaken and there is a readRefFontFile already implemented.  All I 
needed to do was look a few lines down.  I've changed my code to use this 
function instead and it seems to have solved the issue at hand.  I will go into 
more detail about this, as I've done some really deep digging into this

There are a few places that should more than likely be changed within OSG.  
TXPArchive.cpp and ScalarBar.cpp both use the unsafe version.  It will more 
than like be quicker for you to fix this issue than for me to submit source to 
the submission.  I'll just leave that one up to you.

Investigation:
So, I dug into this problem almost all day on Saturday.  I think I have a good 
understanding of what this problem is.  I also believe that there is another 
problem that needs to be addressed.  More to come on that.

In our application, we have a database pager thread that is requesting node 
files in a non-cached manner.  On our main thread, when requesting a 
osgText::Font object, the underlying function is requesting these objects to be 
cached.  I'm posting the Registry::readImplementation below for quick reference.

So, lets say that our database pager has some requests.  No remember that these 
requests are not going to be cached.  Once the Registry::readImplementation 
function is reached, the else portion of the if/else statement is executed.  
This portion of the code takes all objects in the _objectCache and swaps it 
with a temporary local object cache.

Now, lets assume that the database pager is busy at work bringing in a new node 
of data.  The temporary data is all setup and the database pager is happily in 
the readFunctor.  Now, the main thread decides to request a font object.  With 
the database pager still in the readFunctor, the font object is able to get 
inside of the if portion of the if/else statement.  The object is inserted into 
the _objectCache and happily moves on.  As the main thread continues back down 
the stack, one of the function reached first before getting to 
osgText::readFontFile is osgDB::readObjectFile.  This function basically calls 
takeObject on the read results object, which basically adds then subtracts a 
reference from the returning object.  The point I want to make is that the 
reference is 1.  At this point, the stack is unwound a bit further to the 
original calling function.  It has a pure pointer and passes that along to the 
osgText::setFont function.

Lets say that the database pager was finally able to return from doing its read 
somewhere between the main threads execution of Registry::readImplementation 
and osgText::setFont.  Now, the database pager had done a swap of data on the 
_objectCache and the temporary object cache.  Now this means that the data in 
_objectCache belongs to whatever was swapped in from the temporary object cache 
of the database pager thread.  The main thread decided to load some data and 
cache the results.  This data is now in the _objectCache, which belongs to the 
temporary cache of the database pager.  When the database pager returns, it 
swaps again the two pointers.  Now, the temporary cache contains the newly 
cached object and the _objectCache contains nothing.  We can see where this is 
going to lead.  As the temporary cache is destructed, the stored objects, our 
font object, gets unreferenced.  This makes for a sad story when trying to 
access this data later.


Code:

ReaderWriter::ReadResult Registry::readImplementation(const ReadFunctor& 
readFunctor, bool useObjectCache)
{
    std::string file(readFunctor._filename);

    if (useObjectCache)
    {
        // search for entry in the object cache.
        {
            OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
            ObjectCache::iterator oitr=_objectCache.find(file);
            if (oitr!=_objectCache.end())
            {
                notify(INFO)<<"returning cached instanced of "<<file<<std::endl;
                if (readFunctor.isValid(oitr->second.first.get())) return 
ReaderWriter::ReadResult(oitr->second.first.get(), 
ReaderWriter::ReadResult::FILE_LOADED_FROM_CACHE);
                else return ReaderWriter::ReadResult("Error file does not 
contain an osg::Object");
            }
        }
        
        ReaderWriter::ReadResult rr = read(readFunctor);
        if (rr.validObject()) 
        {
            // update cache with new entry.
            notify(INFO)<<"Adding to object cache "<<file<<std::endl;
            addEntryToObjectCache(file,rr.getObject());
        }
        else
        {
            notify(INFO)<<"No valid object found for "<<file<<std::endl;
        }

        return rr;

    }
    else
    {
        ObjectCache tmpObjectCache;
        
        {
            OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
            tmpObjectCache.swap(_objectCache);
        }
        
        ReaderWriter::ReadResult rr = read(readFunctor);

        {
            OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_objectCacheMutex);
            tmpObjectCache.swap(_objectCache);
        }
        
        return rr;
    }
}




Observation 1:
Using the unsafe versions is bad.  Now we know and knowing is half the battler, 
but should this be take further.  It might be a good idea to tag the unsafe 
versions as deprecated, this way a compiler warning can be generated.  This can 
be rolled into the 2.10 release of OSG.  I would then say that a version of two 
later, they can be removed all together.

Observation 2:
It looks like if you mix cached and uncached operations on different threads, 
this is the chance that cached objects will be lost by the registry.  I'm not 
sure how to overcome this issue, and this issue might need a more trained eye 
and someone with greater knowledge of OSGs internal workings.  I'll leave that 
up to you and the community to decide this one.

Observation 3:
I did try to read osgText::Font objects using a noncached call, but this was a 
very bad thing to do.  It basically made our entire terrain page in bad texture 
data.  I'm not sure what the problem was there, but our terrain was paging in 
with some pink textures.  In any case, I did notice something quite strange or 
even bad.  When caching of the font objects was off, calling to read the font 
object internally was caching this data in FreeTypeLibrary::getFont.  The 
_fontImplementationSet continually was growing and growing.  With caching 
turned on, the _fontImplementationSet averaged around nine objects, with some 
of them being the same.  I can only surmise that this was due to the object 
cache problem I described above and would fall to the same fate as using the 
noncached version.  In any case, I wanted to point this out, as there might be 
some optimizations that can be placed here.

Well, I think I have exhausted my time here.  Sorry for the quite long post.  
Thanks and have a good day.

Ryan

------------------
Read this topic online here:
http://forum.openscenegraph.org/viewtopic.php?p=21401#21401





_______________________________________________
osg-users mailing list
[email protected]
http://lists.openscenegraph.org/listinfo.cgi/osg-users-openscenegraph.org

Reply via email to