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