#include "Cache.h" #include "Config.h" #include "Downloader.h" #include #include #include #include #include #include #include using namespace std; using namespace boost::filesystem; namespace storagemanager { Cache::Cache() : currentCacheSize(0) { Config *conf = Config::get(); logger = SMLogging::get(); sync = Synchronizer::get(); string stmp = conf->getValue("Cache", "cache_size"); if (stmp.empty()) { logger->log(LOG_CRIT, "Cache/cache_size is not set"); throw runtime_error("Please set Cache/cache_size in the storagemanager.cnf file"); } try { maxCacheSize = stoul(stmp); } catch (invalid_argument &) { logger->log(LOG_CRIT, "Cache/cache_size is not a number"); throw runtime_error("Please set Cache/cache_size to a number"); } //cout << "Cache got cache size " << maxCacheSize << endl; stmp = conf->getValue("ObjectStorage", "object_size"); if (stmp.empty()) { logger->log(LOG_CRIT, "ObjectStorage/object_size is not set"); throw runtime_error("Please set ObjectStorage/object_size in the storagemanager.cnf file"); } try { objectSize = stoul(stmp); } catch (invalid_argument &) { logger->log(LOG_CRIT, "ObjectStorage/object_size is not a number"); throw runtime_error("Please set ObjectStorage/object_size to a number"); } prefix = conf->getValue("Cache", "path"); if (prefix.empty()) { logger->log(LOG_CRIT, "Cache/path is not set"); throw runtime_error("Please set Cache/path in the storagemanager.cnf file"); } try { boost::filesystem::create_directories(prefix); } catch (exception &e) { syslog(LOG_CRIT, "Failed to create %s, got: %s", prefix.string().c_str(), e.what()); throw e; } //cout << "Cache got prefix " << prefix << endl; downloader.setDownloadPath(prefix.string()); /* todo: populate structures with existing files in the cache path */ } Cache::~Cache() { } void Cache::read(const vector &keys) { /* move existing keys to a do-not-evict map fetch keys that do not exist after fetching, move all keys from do-not-evict to the back of the LRU */ boost::unique_lock s(lru_mutex); vector keysToFetch; uint i; M_LRU_t::iterator mit; for (const string &key : keys) { mit = m_lru.find(key); if (mit != m_lru.end()) // it's in the cache, add the entry to the do-not-evict set addToDNE(mit->lit); else // not in the cache, put it in the list to download keysToFetch.push_back(&key); } // TODO: get the sizes of the objects to download and make space // For now using an estimate makeSpace(keys.size() * objectSize); s.unlock(); // start downloading the keys to fetch int dl_err; vector dl_errnos; if (!keysToFetch.empty()) dl_err = downloader.download(keysToFetch, &dl_errnos); s.lock(); // move all keys to the back of the LRU for (i = 0; i < keys.size(); i++) { mit = m_lru.find(keys[i]); if (mit != m_lru.end()) { lru.splice(lru.end(), lru, mit->lit); removeFromDNE(lru.end()); } else if (dl_errnos[i] == 0) // successful download { lru.push_back(keys[i]); m_lru.insert(M_LRU_element_t(&(lru.back()), lru.end()--)); } else { // Downloader already logged it, anything to do here? /* brainstorming options for handling it. 1) Should it be handled? The caller will log a file-not-found error, and there will be a download failure in the log already. 2) Can't really DO anything can it? */ } } } Cache::DNEElement::DNEElement(const LRU_t::iterator &k) : key(k), refCount(1) { } void Cache::addToDNE(const LRU_t::iterator &key) { DNEElement e(key); DNE_t::iterator it = doNotEvict.find(e); if (it != doNotEvict.end()) { DNEElement &dnee = const_cast(*it); ++dnee.refCount; } else doNotEvict.insert(e); } void Cache::removeFromDNE(const LRU_t::iterator &key) { DNEElement e(key); DNE_t::iterator it = doNotEvict.find(e); if (it == doNotEvict.end()) return; DNEElement &dnee = const_cast(*it); if (--dnee.refCount == 0) doNotEvict.erase(it); } const boost::filesystem::path & Cache::getCachePath() { return prefix; } void Cache::exists(const vector &keys, vector *out) { out->resize(keys.size()); boost::unique_lock s(lru_mutex); for (int i = 0; i < keys.size(); i++) (*out)[i] = (m_lru.find(keys[i]) == m_lru.end()); } void Cache::newObject(const string &key, size_t size) { } void Cache::deletedObject(const string &key, size_t size) { } void Cache::setMaxCacheSize(size_t size) { } // call this holding lru_mutex void Cache::makeSpace(size_t size) { ssize_t thisMuch = currentCacheSize + size - maxCacheSize; if (thisMuch <= 0) return; struct stat statbuf; LRU_t::iterator it = lru.begin(); while (it != lru.end() && thisMuch > 0) { if (doNotEvict.find(it) != doNotEvict.end()) { ++it; continue; // it's in the do-not-evict list } boost::filesystem::path cachedFile = prefix / *it; int err = stat(cachedFile.string().c_str(), &statbuf); if (err) { logger->log(LOG_WARNING, "Downloader: There seems to be a cached file that couldn't be stat'ed: %s", cachedFile.string().c_str()); ++it; continue; } /* TODO: tell Synchronizer that this key will be evicted delete the file remove it from our structs update current size */ assert(currentCacheSize >= statbuf.st_size); currentCacheSize -= statbuf.st_size; thisMuch -= statbuf.st_size; sync->flushObject(*it); boost::filesystem::remove(cachedFile); LRU_t::iterator toRemove = it++; lru.erase(toRemove); m_lru.erase(*toRemove); } } /* The helper classes */ Cache::M_LRU_element_t::M_LRU_element_t(const string *k) : key(k) {} Cache::M_LRU_element_t::M_LRU_element_t(const string &k) : key(&k) {} Cache::M_LRU_element_t::M_LRU_element_t(const string *k, const LRU_t::iterator &i) : key(k), lit(i) {} inline size_t Cache::KeyHasher::operator()(const M_LRU_element_t &l) const { return hash()(*(l.key)); } inline bool Cache::KeyEquals::operator()(const M_LRU_element_t &l1, const M_LRU_element_t &l2) const { return (*(l1.key) == *(l2.key)); } inline size_t Cache::DNEHasher::operator()(const DNEElement &l) const { return hash()(*(l.key)); } inline bool Cache::DNEEquals::operator()(const DNEElement &l1, const DNEElement &l2) const { return (*(l1.key) == *(l2.key)); } }