#include #include #include #include #include #include #include #include #include #include #include // // New Cache Implementation // template class CacheBase; template class CacheEntry; template class CacheObject { friend class CacheBase; public: CacheObject() : pCache( NULL ), bChanged( false ) { } virtual ~CacheObject() { } typedef CacheBase CacheType; virtual keytype getKey() const=0; virtual int getPersistenceScore() const { return 0; } void lock() { pEntry->lock(); } void unlock() { pEntry->unlock(); } int getRefCount() const { return pEntry->getRefCount(); } bool hasChanged() const { return bChanged; } protected: void changed( bool bChanged=true ) { if( this->bChanged == false && bChanged == true ) pCache->objectChanged( getKey() ); this->bChanged = bChanged; } private: typedef CacheEntry Entry; void setCache( CacheType *pCache, Entry *pEntry ) { this->pCache = pCache; this->pEntry = pEntry; } private: CacheType *pCache; Entry *pEntry; bool bChanged; }; template class CacheBase; template class CacheEntry { friend class CacheBase; private: CacheEntry( obtype *pObject ) : iRefCount( 0 ), pObject( pObject ) { } public: int getRefCount() const { mEntry.lock(); int ret = iRefCount; mEntry.unlock(); return ret; } obtype *getPtr() const { return pObject; } void lock() const { mObject.lock(); } void unlock() const { mObject.unlock(); } Bu::Mutex &getMutex() { return mObject; } void incRef() { mEntry.lock(); iRefCount++; mEntry.unlock(); } bool decRef() { mEntry.lock(); iRefCount--; bool bRet = iRefCount > 0; mEntry.unlock(); return bRet; } private: mutable Bu::Mutex mEntry; mutable Bu::Mutex mObject; int iRefCount; obtype *pObject; }; template class CachePtrInterface { protected: CachePtrInterface() { } virtual ~CachePtrInterface() { } template void checkRef( CacheBase *pCache, const keytype &kId, CacheEntry * &rpEnt, obtype * &rpData ) { if( pCache == NULL ) throw Bu::ExceptionBase("Invalid pointer"); if( !rpData ) { rpEnt = pCache->getRef( kId ); rpEnt->incRef(); rpData = dynamic_cast(rpEnt->getPtr()); if( rpData == NULL ) { rpEnt->decRef(); rpEnt = NULL; throw std::bad_cast(); } } } template void releaseRef( CacheBase *pCache, CacheEntry * &rpEnt, obtype * &rpData ) { if( pCache == NULL ) return; if( rpData == NULL ) return; rpData = NULL; rpEnt->decRef(); pCache->releaseRef( rpEnt ); rpEnt = NULL; } }; template class CachePtr : protected CachePtrInterface { friend class CacheBase; private: typedef CachePtr MyType; CachePtr( CacheBase *pCache, CacheEntry *pEnt, const keytype &kId ) : pCache( pCache ), kId( kId ), pEnt( pEnt ), pData( NULL ) { pEnt->incRef(); pData = dynamic_cast( pEnt->getPtr() ); if( pData == NULL ) { pEnt->decRef(); throw std::bad_cast(); } } CachePtr( CacheBase *pCache, const keytype &kId ) : pCache( pCache ), kId( kId ), pEnt( NULL ), pData( NULL ) { } public: CachePtr() : pCache( NULL ), pEnt( NULL ), pData( NULL ) { } CachePtr( const MyType &rhs ) : pCache( rhs.pCache ), kId( rhs.kId ), pEnt( rhs.pEnt ), pData( rhs.pData ) { pEnt->incRef(); } virtual ~CachePtr() { unbind(); } const keytype &getKey() const { return kId; } obtype &operator*() { bind(); return pData; } const obtype &operator*() const { bind(); return pData; } obtype *operator->() { bind(); return pData; } const obtype *operator->() const { bind(); return pData; } MyType operator=( const MyType &rhs ) { unbind(); pCache = rhs.pCache; kId = rhs.kId; pEnt = rhs.pEnt; pData = rhs.pData; pEnt->incRef(); return *this; } template CachePtr cast() { return pCache->cast( *this ); } bool operator==( const MyType &rhs ) const { return pCache == rhs.pCache && kId == rhs.kId; } bool operator!=( const MyType &rhs ) const { return pCache != rhs.pCache || kId != rhs.kId; } void bind() { CachePtrInterface::checkRef( pCache, kId, pEnt, pData ); } void unbind() { CachePtrInterface::releaseRef( pCache, pEnt, pData ); } void lock() { bind(); if( pEnt ) pEnt->lock(); } void unlock() { bind(); if( pEnt ) pEnt->unlock(); } class Locker { public: Locker( MyType &rPtr ) : rPtr( rPtr ), bLocked( true ) { rPtr.lock(); } ~Locker() { unlock(); } void unlock() { if( !bLocked ) return; rPtr.unlock(); bLocked = false; } private: MyType &rPtr; bool bLocked; }; private: CacheBase *pCache; mutable keytype kId; mutable CacheEntry *pEnt; mutable obtype *pData; }; template class CacheBase { friend class CachePtrInterface; friend class CacheObject; public: CacheBase() { } virtual ~CacheBase() { Bu::ReadWriteMutex::WriteLocker wl( mCacheEntry ); syncChanges(); } typedef CacheEntry Entry; typedef Bu::List KeyList; CachePtr insert( obtype *pObject ) { Entry *pEnt = addEntry( pObject ); return CachePtr( this, pEnt, pObject->getKey() ); } template CachePtr insert( supertype *pObject ) { obtype *pCast = dynamic_cast( pObject ); if( pCast == NULL ) throw std::bad_cast(); Entry *pEnt = addEntry( pCast ); return CachePtr( this, pEnt, pObject->getKey() ); } CachePtr get( const keytype &key ) { Entry *pEnt = getEntry( key ); return CachePtr( this, pEnt, key ); } template CachePtr get( const keytype &key ) { Entry *pEnt = getEntry( key ); return CachePtr( this, pEnt, key ); } CachePtr getLazy( const keytype &key ) { return CachePtr( this, key ); } template CachePtr getLazy( const keytype &key ) { return CachePtr( this, key ); } template CachePtr cast( CachePtr &ptr ) { if( ptr.pEnt ) return CachePtr( this, ptr.pEnt, ptr.kId ); else return CachePtr( this, ptr.kId ); } void erase( const keytype &key ) { Bu::ReadWriteMutex::WriteLocker wl( mCacheEntry ); if( hCacheEntry.has( key ) ) { Entry *pEnt = hCacheEntry.get( key ); pEnt->mEntry.lock(); if( pEnt->iRefCount > 0 ) { int iCount = pEnt->iRefCount; pEnt->mEntry.unlock(); throw Bu::ExceptionBase( Bu::String("Cache entry %1 cannot be erased, there are %2 active references.").arg( key ).arg( iCount ).end().getStr() ); } delete pEnt->pObject; delete pEnt; hCacheEntry.erase( key ); } _erase( key ); } virtual KeyList getKeys() const=0; virtual int getSize() const=0; void sync() { Bu::ReadWriteMutex::WriteLocker wl( mCacheEntry ); Bu::println("Syncing storage layer..."); _sync(); syncChanges(); } void _debug() { Bu::ReadWriteMutex::ReadLocker rl( mCacheEntry ); Bu::println("Cache entries:"); for( typename CacheEntryHash::iterator i = hCacheEntry.begin(); i; i++ ) { Bu::println(" %1: refs=%2"). arg( i.getKey() ). arg( i.getValue()->getRefCount() ); } } protected: Entry *getRef( const keytype &k ) { Entry *pEnt = getEntry( k ); return pEnt; } void releaseRef( Entry * )//pEnt ) { } void objectChanged( const keytype &k ) { Bu::ReadWriteMutex::WriteLocker wl( mCacheEntry ); hChanged.insert( k, true ); } protected: virtual void _create( const obtype *o )=0; virtual void _erase( const keytype &k )=0; virtual obtype *_load( const keytype &k )=0; virtual void _save( const obtype *o )=0; virtual void _sync()=0; private: Entry *addEntry( obtype *pObject ) { Entry *pEnt = new Entry( pObject ); mCacheEntry.lockWrite(); hCacheEntry.insert( pObject->getKey(), pEnt ); mCacheEntry.unlockWrite(); _create( pObject ); pObject->setCache( this, pEnt ); return pEnt; } Entry *getEntry( const keytype &k ) { Entry *pEnt = NULL; try { Bu::ReadWriteMutex::ReadLocker rl( mCacheEntry ); pEnt = hCacheEntry.get( k ); } catch(...) { // try to load the object from the backing store obtype *pObject = _load( k ); pEnt = new Entry( pObject ); pObject->setCache( this, pEnt ); Bu::ReadWriteMutex::WriteLocker wl( mCacheEntry ); hCacheEntry.insert( k, pEnt ); } return pEnt; } void syncChanges() { Bu::println("Syncing: %1 entries changed.").arg( hChanged.getSize() ); if( !hChanged.isEmpty() ) { for( typename CacheKeySet::iterator i = hChanged.begin(); i; i++ ) { Entry *pEnt = hCacheEntry.get( i.getKey() ); Bu::MutexLocker ml( pEnt->getMutex() ); _save( pEnt->getPtr() ); } hChanged.clear(); } } private: typedef Bu::Hash CacheEntryHash; typedef Bu::Hash CacheKeySet; CacheEntryHash hCacheEntry; CacheKeySet hChanged; Bu::ReadWriteMutex mCacheEntry; }; template void _cacheObjectSave( Bu::Stream &s, obtype *pObject ) { Bu::Archive ar( s, Bu::Archive::save ); ar << *pObject; } template obtype *_cacheObjectLoad( Bu::Stream &s ) { Bu::Archive ar( s, Bu::Archive::load ); obtype *ret = new obtype(); ar >> *ret; return ret; } template class MyriadCache : public CacheBase { public: MyriadCache( Bu::Stream &sStore, int iBlockSize=512, int iPreallocate=8 ) : sStore( sStore ), mStore( sStore, iBlockSize, iPreallocate ), bStructureChanged( false ) { try { Bu::ReadWriteMutex::ReadLocker l( rwStore ); Bu::MyriadStream ms = mStore.openStream( 1 ); Bu::Archive ar( ms, Bu::Archive::load ); uint8_t uVer; ar >> uVer; switch( uVer ) { case 0: ar >> hIndex; break; } } catch(...) { if( mStore.createStreamWithId( 1 ) != 1 ) throw Bu::ExceptionBase("Error creating index stream."); _sync(); } } virtual ~MyriadCache() { Bu::println("MyriadCache destroying."); _sync(); } using typename CacheBase::KeyList; virtual KeyList getKeys() const { Bu::ReadWriteMutex::ReadLocker rl( rwStore ); return hIndex.getKeys(); } virtual int getSize() const { Bu::ReadWriteMutex::ReadLocker rl( rwStore ); return hIndex.getSize(); } protected: virtual void _create( const obtype *o ) { Bu::ReadWriteMutex::WriteLocker wl( rwStore ); hIndex.insert( o->getKey(), mStore.createStream() ); _save( o ); bStructureChanged = true; } virtual void _erase( const keytype &k ) { Bu::ReadWriteMutex::WriteLocker wl( rwStore ); mStore.deleteStream( hIndex.get( k ) ); hIndex.erase( k ); bStructureChanged = true; } virtual obtype *_load( const keytype &k ) { Bu::MyriadStream ms = mStore.openStream( hIndex.get( k ) ); return _cacheObjectLoad( ms ); } virtual void _save( const obtype *o ) { Bu::MyriadStream ms = mStore.openStream( hIndex.get( o->getKey() ) ); _cacheObjectSave( ms, o ); ms.setSize( ms.tell() ); } virtual void _sync() { Bu::println("Syncing myriad header."); Bu::ReadWriteMutex::ReadLocker wl( rwStore ); if( !bStructureChanged ) return; Bu::println(" --> Header changed, write required."); Bu::MyriadStream ms = mStore.openStream( 1 ); Bu::Archive ar( ms, Bu::Archive::save ); ar << (uint8_t)0 << hIndex; ar.close(); ms.setSize( ms.tell() ); bStructureChanged = false; } private: Bu::Stream &sStore; Bu::Myriad mStore; Bu::Hash hIndex; mutable Bu::ReadWriteMutex rwStore; bool bStructureChanged; }; // // Test // class Something : public CacheObject { friend Bu::ArchiveBase &operator>>( Bu::ArchiveBase &ar, Something &s ); friend Bu::ArchiveBase &operator<<( Bu::ArchiveBase &ar, const Something &s ); public: Something() { } Something( const Bu::String &sName ) : uId( Bu::Uuid::generate() ), sName( sName ) { } virtual ~Something() { } virtual Bu::Uuid getKey() const { return uId; } Bu::String getName() const { return sName; } void setName( const Bu::String &sNewName ) { sName = sNewName; changed(); } virtual Bu::String toString() const=0; private: Bu::Uuid uId; Bu::String sName; }; class SubSomethingA : public Something { friend Bu::ArchiveBase &operator>>( Bu::ArchiveBase &ar, SubSomethingA &s ); friend Bu::ArchiveBase &operator<<( Bu::ArchiveBase &ar, const SubSomethingA &s ); public: SubSomethingA() { } SubSomethingA( const Bu::String &sName, int iNumber ) : Something( sName ), iNumber( iNumber ) { } virtual Bu::String toString() const { return Bu::String("[typeA] %1 (%2)").arg( getName() ).arg( iNumber ); } private: int iNumber; }; class SubSomethingB : public Something { friend Bu::ArchiveBase &operator>>( Bu::ArchiveBase &ar, SubSomethingB &s ); friend Bu::ArchiveBase &operator<<( Bu::ArchiveBase &ar, const SubSomethingB &s ); public: SubSomethingB() { } SubSomethingB( const Bu::String &sName, const Bu::String &sString ) : Something( sName ), sString( sString ) { } virtual Bu::String toString() const { return Bu::String("[typeB] %1 (%2)").arg( getName() ).arg( sString ); } private: Bu::String sString; }; Bu::ArchiveBase &operator>>( Bu::ArchiveBase &ar, Something &s ) { return ar >> s.uId >> s.sName; } Bu::ArchiveBase &operator<<( Bu::ArchiveBase &ar, const Something &s ) { return ar << s.uId << s.sName; } Bu::ArchiveBase &operator>>( Bu::ArchiveBase &ar, SubSomethingA &s ) { return ar >> (Something &)s >> s.iNumber; } Bu::ArchiveBase &operator<<( Bu::ArchiveBase &ar, const SubSomethingA &s ) { return ar << (Something &)s << s.iNumber; } Bu::ArchiveBase &operator>>( Bu::ArchiveBase &ar, SubSomethingB &s ) { return ar >> (Something &)s >> s.sString; } Bu::ArchiveBase &operator<<( Bu::ArchiveBase &ar, const SubSomethingB &s ) { return ar << (Something &)s << s.sString; } template<> void _cacheObjectSave( Bu::Stream &s, const Something *pObject ) { Bu::Archive ar( s, Bu::Archive::save ); if( typeid(*pObject) == typeid(SubSomethingA) ) { ar << (uint8_t)1 << (SubSomethingA &)*pObject; } else if( typeid(*pObject) == typeid(SubSomethingB) ) { ar << (uint8_t)2 << (SubSomethingB &)*pObject; } else { Bu::println("Not a recognized type!"); throw Bu::ExceptionBase("Not recognized type!"); } } template<> Something *_cacheObjectLoad( Bu::Stream &s ) { Bu::Archive ar( s, Bu::Archive::load ); uint8_t uType; ar >> uType; switch( uType ) { case 1: { SubSomethingA *ret = new SubSomethingA(); ar >> *ret; return ret; } break; case 2: { SubSomethingB *ret = new SubSomethingB(); ar >> *ret; return ret; } break; default: throw Bu::ExceptionBase("Flagrant error! Invalid type!"); } } typedef CachePtr SomethingPtr; typedef CachePtr SomethingAPtr; typedef CachePtr SomethingBPtr; typedef MyriadCache SomethingCache; int main( int argc, char *argv[] ) { Bu::File fStore("test.myr", Bu::File::Create|Bu::File::ReadWrite); SomethingCache c( fStore ); SomethingPtr ptr; if( time(NULL)%2 ) ptr = c.insert( new SubSomethingA("Hello", 55) ).cast(); else ptr = c.insert( new SubSomethingB("Goodbye", "Things") ).cast(); Bu::println("Something[%1]: %2"). arg( ptr.getKey() ). arg( ptr->getName() ); SomethingCache::KeyList lKeys = c.getKeys(); Bu::println("Count: %1").arg( lKeys.getSize() ); int j = 0; for( SomethingCache::KeyList::iterator i = lKeys.begin(); i; i++ ) { Bu::println(" - %1: '%2'").arg( *i ).arg( c.get( *i )->toString() ); } Bu::println("Count: %1").arg( c.getSize() ); SomethingPtr p2 = c.get( ptr.getKey() ); Bu::println("%1 == %2").arg( p2->getName() ).arg( ptr->getName() ); SomethingPtr p3 = ptr.cast(); Bu::println("%1: %2").arg( p3.getKey() ).arg( p3->getName() ); return 0; } /* int main( int argc, char *argv[] ) { Bu::File fStore("test.myr", Bu::File::Create|Bu::File::ReadWrite); SomethingCache c( fStore ); for( int j = 1; j < argc; j++ ) { SomethingPtr ptr = c.insert( new Something(argv[j]) ); Bu::println("Something[%1]: %2"). arg( ptr.getKey() ). arg( ptr->getName() ); } SomethingCache::KeyList lKeys = c.getKeys(); Bu::println("Count: %1").arg( lKeys.getSize() ); int j = 0; for( SomethingCache::KeyList::iterator i = lKeys.begin(); i; i++ ) { Bu::println(" - %1: '%2'").arg( *i ).arg( c.get( *i )->getName() ); if( ((j++)%2) ) c.erase( *i ); } Bu::println("Count: %1").arg( c.getSize() ); c._debug(); SomethingPtr p2; SomethingPtr p1( c.get( lKeys.first() ) ); c._debug(); { SomethingPtr p2( p1 ); c._debug(); } c._debug(); p2 = p1; c._debug(); p1.unbind(); c._debug(); Bu::println("Name: %1").arg( p1->getName() ); p1.unbind(); p1.lock(); p1.unlock(); c._debug(); SomethingPtr p3 = c.getLazy( lKeys.first() ); c._debug(); { SomethingPtr::Locker l( p3 ); Bu::println("Name again: %1").arg( p3->getName() ); p3->setName( p3->getName() + " - again" ); } c.sync(); c._debug(); return 0; } */