#ifndef __TAGGEDCACHE__ #define __TAGGEDCACHE__ #include #include #include #include #include // This class implemented a cache and a map. The cache keeps objects alive // in the map. The map allows multiple code paths that reference objects // with the same tag to get the same actual object. // So long as data is in the cache, it will stay in memory. // If it stays in memory even after it is ejected from the cache, // the map will track it. // CAUTION: Callers must not modify data objects that are stored in the cache! template class TaggedCache { public: typedef c_Key key_type; typedef c_Data data_type; typedef boost::weak_ptr weak_data_ptr; typedef boost::shared_ptr data_ptr; typedef std::pair cache_entry; protected: mutable boost::recursive_mutex mLock; int mTargetSize, mTargetAge; boost::unordered_map mCache; // Hold strong reference to recent objects time_t mLastSweep; boost::unordered_map mMap; // Track stored objects public: TaggedCache(int size, int age) : mTargetSize(size), mTargetAge(age), mLastSweep(time(NULL)) { ; } int getTargetSize() const; int getTargetAge() const; int getCacheSize(); int getSweepAge(); void setTargetSize(int size); void setTargetAge(int age); void sweep(); bool touch(const key_type& key); bool del(const key_type& key); bool canonicalize(const key_type& key, boost::shared_ptr& data); bool store(const key_type& key, const c_Data& data); boost::shared_ptr fetch(const key_type& key); bool retrieve(const key_type& key, c_Data& data); boost::recursive_mutex& peekMutex() { return mLock; } }; template int TaggedCache::getTargetSize() const { boost::recursive_mutex::scoped_lock sl(mLock); return mTargetSize; } template int TaggedCache::getTargetAge() const { boost::recursive_mutex::scoped_lock sl(mLock); return mTargetAge; } template int TaggedCache::getCacheSize() { boost::recursive_mutex::scoped_lock sl(mLock); return mCache.size(); } template void TaggedCache::sweep() { boost::recursive_mutex::scoped_lock sl(mLock); if (mCache.size() < mTargetSize) return; time_t now = time(NULL); if ((mLastSweep + 10) < now) return; mLastSweep = now; time_t target = now - mTargetAge; // Pass 1, remove old objects from cache typename boost::unordered_map::iterator cit = mCache.begin(); while (cit != mCache.end()) { if (cit->second->second.first < target) { typename boost::unordered_map::iterator tmp = cit++; mCache.erase(tmp); } else ++cit; } // Pass 2, remove dead objects from map typename boost::unordered_map::iterator mit = mMap.begin(); while (mit != mMap.end()) { if (mit->second->expired()) { typename boost::unordered_map::iterator tmp = mit++; mMap.erase(mit); } else ++mit; } } template bool TaggedCache::touch(const key_type& key) { // If present, make current in cache boost::recursive_mutex::scoped_lock sl(mLock); // Is the object in the map? typename boost::unordered_map::iterator mit = mMap.find(key); if (mit == mMap.end()) return false; if (mit->second.expired()) { // in map, but expired mMap.erase(mit); return false; } // Is the object in the cache? typename boost::unordered_map::iterator cit = mCache.find(key); if (cit != mCache.end()) { // in both map and cache cit->second.first = time(NULL); return true; } // In map but not cache, put in cache mCache.insert(std::make_pair(key, std::make_pair(time(NULL), weak_data_ptr(cit->second.second)))); return true; } template bool TaggedCache::del(const key_type& key) { // Remove from cache, map unaffected boost::recursive_mutex::scoped_lock sl(mLock); typename boost::unordered_map::iterator cit = mCache.find(key); if (cit == mCache.end()) return false; mCache.erase(cit); return true; } template bool TaggedCache::canonicalize(const key_type& key, boost::shared_ptr& data) { // Return canonical value, store if needed, refresh in cache // Return values: true=we had the data already boost::recursive_mutex::scoped_lock sl(mLock); typename boost::unordered_map::iterator mit = mMap.find(key); if (mit == mMap.end()) { // not in map mCache.insert(std::make_pair(key, std::make_pair(time(NULL), data))); mMap.insert(std::make_pair(key, data)); return false; } boost::shared_ptr cachedData = mit->second.lock(); if (!cachedData) { // in map, but expired. Update in map, insert in cache mit->second = data; mCache.insert(std::make_pair(key, std::make_pair(time(NULL), data))); return false; } data = cachedData; // Valid in map, is it in cache? typename boost::unordered_map::iterator cit = mCache.find(key); if (cit != mCache.end()) cit->second.first = time(NULL); // Yes, refesh else // no, add to cache mCache.insert(std::make_pair(key, std::make_pair(time(NULL), data))); return true; } template boost::shared_ptr TaggedCache::fetch(const key_type& key) { // fetch us a shared pointer to the stored data object boost::recursive_mutex::scoped_lock sl(mLock); // Is it in the map? typename boost::unordered_map::iterator mit = mMap.find(key); if (mit == mMap.end()) return data_ptr(); // No, we're done boost::shared_ptr cachedData = mit->second.lock(); if (!cachedData) { // in map, but expired. Sorry, we don't have it mMap.erase(mit); return cachedData; } // Valid in map, is it in the cache? typename boost::unordered_map::iterator cit = mCache.find(key); if (cit != mCache.end()) cit->second.first = time(NULL); // Yes, refresh else // No, add to cache mCache.insert(std::make_pair(key, std::make_pair(time(NULL), cachedData))); return cachedData; } template bool TaggedCache::store(const key_type& key, const c_Data& data) { boost::shared_ptr d = boost::make_shared(boost::ref(data)); return canonicalize(key, d); } template bool TaggedCache::retrieve(const key_type& key, c_Data& data) { // retrieve the value of the stored data boost::shared_ptr dataPtr = fetch(key); if (!dataPtr) return false; data = *dataPtr; return true; } #endif