diff --git a/doc/man/s3fs.1 b/doc/man/s3fs.1 index 0650574..502ce01 100644 --- a/doc/man/s3fs.1 +++ b/doc/man/s3fs.1 @@ -156,13 +156,13 @@ time to wait between read/write activity before giving up. specify the maximum number of keys returned by S3 list object API. The default is 1000. you can set this value to 1000 or more. .TP \fB\-o\fR max_stat_cache_size (default="100,000" entries (about 40MB)) -maximum number of entries in the stat cache +maximum number of entries in the stat cache and symbolic link cache. .TP \fB\-o\fR stat_cache_expire (default is no expire) -specify expire time (seconds) for entries in the stat cache. This expire time indicates the time since stat cached. +specify expire time (seconds) for entries in the stat cache and symbolic link cache. This expire time indicates the time since cached. .TP \fB\-o\fR stat_cache_interval_expire (default is no expire) -specify expire time (seconds) for entries in the stat cache. This expire time is based on the time from the last access time of the stat cache. +specify expire time (seconds) for entries in the stat cache and symbolic link cache. This expire time is based on the time from the last access time of those cache. This option is exclusive with stat_cache_expire, and is left for compatibility with older versions. .TP \fB\-o\fR enable_noobj_cache (default is disable) diff --git a/src/cache.cpp b/src/cache.cpp index 6bb7a70..2fa0681 100644 --- a/src/cache.cpp +++ b/src/cache.cpp @@ -114,7 +114,7 @@ inline bool IsExpireStatCacheTime(const struct timespec& ts, const time_t& expir } // -// For cache out +// For stats cache out // typedef std::vector statiterlist_t; @@ -132,6 +132,25 @@ struct sort_statiterlist{ } }; +// +// For symbolic link cache out +// +typedef std::vector symlinkiterlist_t; + +struct sort_symlinkiterlist{ + // ascending order + bool operator()(const symlink_cache_t::iterator& src1, const symlink_cache_t::iterator& src2) const + { + int result = CompareStatCacheTime(src1->second->cache_date, src2->second->cache_date); // use the same as Stats + if(0 == result){ + if(src1->second->hit_count < src2->second->hit_count){ + result = -1; + } + } + return (result < 0); + } +}; + //------------------------------------------------------------------- // Static //------------------------------------------------------------------- @@ -344,7 +363,7 @@ bool StatCache::IsNoObjectCache(const string& key, bool overcheck) return false; } -bool StatCache::AddStat(std::string& key, headers_t& meta, bool forcedir, bool no_truncate) +bool StatCache::AddStat(const std::string& key, headers_t& meta, bool forcedir, bool no_truncate) { if(!no_truncate && CacheSize< 1){ return true; @@ -408,10 +427,18 @@ bool StatCache::AddStat(std::string& key, headers_t& meta, bool forcedir, bool n } stat_cache[key] = ent; + // check symbolic link cache + if(!S_ISLNK(ent->stbuf.st_mode)){ + if(symlink_cache.end() != symlink_cache.find(key)){ + // if symbolic link cache has key, thus remove it. + DelSymlink(key.c_str(), true); + } + } + return true; } -bool StatCache::AddNoObjectCache(string& key) +bool StatCache::AddNoObjectCache(const string& key) { if(!IsCacheNoObject){ return true; // pretend successful @@ -459,6 +486,12 @@ bool StatCache::AddNoObjectCache(string& key) } stat_cache[key] = ent; + // check symbolic link cache + if(symlink_cache.end() != symlink_cache.find(key)){ + // if symbolic link cache has key, thus remove it. + DelSymlink(key.c_str(), true); + } + return true; } @@ -571,6 +604,151 @@ bool StatCache::DelStat(const char* key, bool lock_already_held) return true; } +bool StatCache::GetSymlink(const string& key, string& value) +{ + bool is_delete_cache = false; + string strpath = key; + + AutoLock lock(&StatCache::stat_cache_lock); + + symlink_cache_t::iterator iter = symlink_cache.find(strpath); + if(iter != symlink_cache.end() && iter->second){ + symlink_cache_entry* ent = iter->second; + if(!IsExpireTime || !IsExpireStatCacheTime(ent->cache_date, ExpireTime)){ // use the same as Stats + // found + S3FS_PRN_DBG("symbolic link cache hit [path=%s][time=%lld.%09ld][hit count=%lu]", + strpath.c_str(), static_cast(ent->cache_date.tv_sec), ent->cache_date.tv_nsec, ent->hit_count); + + value = ent->link; + + ent->hit_count++; + if(IsExpireIntervalType){ + SetStatCacheTime(ent->cache_date); + } + return true; + }else{ + // timeout + is_delete_cache = true; + } + } + + if(is_delete_cache){ + DelSymlink(strpath.c_str(), /*lock_already_held=*/ true); + } + return false; +} + +bool StatCache::AddSymlink(const string& key, const string& value) +{ + if(CacheSize< 1){ + return true; + } + S3FS_PRN_INFO3("add symbolic link cache entry[path=%s, value=%s]", key.c_str(), value.c_str()); + + bool found; + bool do_truncate; + { + AutoLock lock(&StatCache::stat_cache_lock); + found = symlink_cache.end() != symlink_cache.find(key); + do_truncate = symlink_cache.size() > CacheSize; + } + + if(found){ + DelSymlink(key.c_str()); + }else{ + if(do_truncate){ + if(!TruncateSymlink()){ + return false; + } + } + } + + // make new + symlink_cache_entry* ent = new symlink_cache_entry(); + ent->link = value; + ent->hit_count = 0; + SetStatCacheTime(ent->cache_date); // Set time(use the same as Stats). + + // add + AutoLock lock(&StatCache::stat_cache_lock); + + symlink_cache_t::iterator iter = symlink_cache.find(key); // recheck for same key exists + if(symlink_cache.end() != iter){ + delete iter->second; + symlink_cache.erase(iter); + } + symlink_cache[key] = ent; + + return true; +} + +bool StatCache::TruncateSymlink() +{ + AutoLock lock(&StatCache::stat_cache_lock); + + if(symlink_cache.empty()){ + return true; + } + + // 1) erase over expire time + if(IsExpireTime){ + for(symlink_cache_t::iterator iter = symlink_cache.begin(); iter != symlink_cache.end(); ){ + symlink_cache_entry* entry = iter->second; + if(!entry || IsExpireStatCacheTime(entry->cache_date, ExpireTime)){ // use the same as Stats + delete entry; + symlink_cache.erase(iter++); + }else{ + ++iter; + } + } + } + + // 2) check stat cache count + if(symlink_cache.size() < CacheSize){ + return true; + } + + // 3) erase from the old cache in order + size_t erase_count= symlink_cache.size() - CacheSize + 1; + symlinkiterlist_t erase_iters; + for(symlink_cache_t::iterator iter = symlink_cache.begin(); iter != symlink_cache.end(); ++iter){ + erase_iters.push_back(iter); + sort(erase_iters.begin(), erase_iters.end(), sort_symlinkiterlist()); + if(erase_count < erase_iters.size()){ + erase_iters.pop_back(); + } + } + for(symlinkiterlist_t::iterator iiter = erase_iters.begin(); iiter != erase_iters.end(); ++iiter){ + symlink_cache_t::iterator siter = *iiter; + + S3FS_PRN_DBG("truncate symbolic link cache[path=%s]", siter->first.c_str()); + delete siter->second; + symlink_cache.erase(siter); + } + S3FS_MALLOCTRIM(0); + + return true; +} + +bool StatCache::DelSymlink(const char* key, bool lock_already_held) +{ + if(!key){ + return false; + } + S3FS_PRN_INFO3("delete symbolic link cache entry[path=%s]", key); + + AutoLock lock(&StatCache::stat_cache_lock, lock_already_held ? AutoLock::ALREADY_LOCKED : AutoLock::NONE); + + symlink_cache_t::iterator iter; + if(symlink_cache.end() != (iter = symlink_cache.find(string(key)))){ + delete iter->second; + symlink_cache.erase(iter); + } + S3FS_MALLOCTRIM(0); + + return true; +} + //------------------------------------------------------------------- // Functions //------------------------------------------------------------------- diff --git a/src/cache.h b/src/cache.h index f210080..4d948e8 100644 --- a/src/cache.h +++ b/src/cache.h @@ -24,7 +24,7 @@ #include "common.h" // -// Struct +// Struct for stats cache // struct stat_cache_entry { struct stat stbuf; @@ -45,20 +45,46 @@ struct stat_cache_entry { typedef std::map stat_cache_t; // key=path +// +// Struct for symbolic link cache +// +struct symlink_cache_entry { + std::string link; + unsigned long hit_count; + struct timespec cache_date; // The function that operates timespec uses the same as Stats + + symlink_cache_entry() : link(""), hit_count(0) { + cache_date.tv_sec = 0; + cache_date.tv_nsec = 0; + } +}; + +typedef std::map symlink_cache_t; + // // Class // +// [NOTE] About Symbolic link cache +// The Stats cache class now also has a symbolic link cache. +// It is possible to take out the Symbolic link cache in another class, +// but the cache out etc. should be synchronized with the Stats cache +// and implemented in this class. +// Symbolic link cache size and timeout use the same settings as Stats +// cache. This simplifies user configuration, and from a user perspective, +// the symbolic link cache appears to be included in the Stats cache. +// class StatCache { private: static StatCache singleton; static pthread_mutex_t stat_cache_lock; - stat_cache_t stat_cache; - bool IsExpireTime; - bool IsExpireIntervalType; // if this flag is true, cache data is updated at last access time. - time_t ExpireTime; - unsigned long CacheSize; - bool IsCacheNoObject; + stat_cache_t stat_cache; + bool IsExpireTime; + bool IsExpireIntervalType; // if this flag is true, cache data is updated at last access time. + time_t ExpireTime; + unsigned long CacheSize; + bool IsCacheNoObject; + symlink_cache_t symlink_cache; private: StatCache(); @@ -68,6 +94,8 @@ class StatCache bool GetStat(const std::string& key, struct stat* pst, headers_t* meta, bool overcheck, const char* petag, bool* pisforce); // Truncate stat cache bool TruncateCache(void); + // Truncate symbolic link cache + bool TruncateSymlink(void); public: // Reference singleton @@ -111,10 +139,10 @@ class StatCache // Cache For no object bool IsNoObjectCache(const std::string& key, bool overcheck = true); - bool AddNoObjectCache(std::string& key); + bool AddNoObjectCache(const std::string& key); // Add stat cache - bool AddStat(std::string& key, headers_t& meta, bool forcedir = false, bool no_truncate = false); + bool AddStat(const std::string& key, headers_t& meta, bool forcedir = false, bool no_truncate = false); // Change no truncate flag void ChangeNoTruncateFlag(const std::string& key, bool no_truncate); @@ -124,6 +152,11 @@ class StatCache bool DelStat(std::string& key, bool lock_already_held = false) { return DelStat(key.c_str(), lock_already_held); } + + // Cache for symbolic link + bool GetSymlink(const std::string& key, std::string& value); + bool AddSymlink(const std::string& key, const std::string& value); + bool DelSymlink(const char* key, bool lock_already_held = false); }; // diff --git a/src/s3fs.cpp b/src/s3fs.cpp index 0f7c2a9..1a787b3 100644 --- a/src/s3fs.cpp +++ b/src/s3fs.cpp @@ -910,40 +910,54 @@ static int s3fs_readlink(const char* _path, char* buf, size_t size) return 0; } WTF8_ENCODE(path) - // Open - FdEntity* ent; - if(NULL == (ent = get_local_fent(path))){ - S3FS_PRN_ERR("could not get fent(file=%s)", path); - return -EIO; - } - // Get size - off_t readsize; - if(!ent->GetSize(readsize)){ - S3FS_PRN_ERR("could not get file size(file=%s)", path); - FdManager::get()->Close(ent); - return -EIO; - } - if(static_cast(size) <= readsize){ - readsize = size - 1; - } - // Read - ssize_t ressize; - if(0 > (ressize = ent->Read(buf, 0, readsize))){ - S3FS_PRN_ERR("could not read file(file=%s, ressize=%jd)", path, (intmax_t)ressize); - FdManager::get()->Close(ent); - return static_cast(ressize); - } - buf[ressize] = '\0'; + string strValue; - // check buf if it has space words. - string strTmp = trim(string(buf)); - // decode wtf8. This will always be shorter - if(use_wtf8){ - strTmp = s3fs_wtf8_decode(strTmp); - } - strncpy(buf, strTmp.c_str(), size); + // check symblic link cache + if(!StatCache::getStatCacheData()->GetSymlink(string(path), strValue)){ + // not found in cache, then open the path + FdEntity* ent; + if(NULL == (ent = get_local_fent(path))){ + S3FS_PRN_ERR("could not get fent(file=%s)", path); + return -EIO; + } + // Get size + off_t readsize; + if(!ent->GetSize(readsize)){ + S3FS_PRN_ERR("could not get file size(file=%s)", path); + FdManager::get()->Close(ent); + return -EIO; + } + if(static_cast(size) <= readsize){ + readsize = size - 1; + } + // Read + ssize_t ressize; + if(0 > (ressize = ent->Read(buf, 0, readsize))){ + S3FS_PRN_ERR("could not read file(file=%s, ressize=%jd)", path, (intmax_t)ressize); + FdManager::get()->Close(ent); + return static_cast(ressize); + } + buf[ressize] = '\0'; + + // close + FdManager::get()->Close(ent); + + // check buf if it has space words. + strValue = trim(string(buf)); + + // decode wtf8. This will always be shorter + if(use_wtf8){ + strValue = s3fs_wtf8_decode(strValue); + } + + // add symblic link cache + if(!StatCache::getStatCacheData()->AddSymlink(string(path), strValue)){ + S3FS_PRN_ERR("failed to add symbolic link cache for %s", path); + } + } + // copy result + strncpy(buf, strValue.c_str(), size); - FdManager::get()->Close(ent); S3FS_MALLOCTRIM(0); return 0; @@ -1150,6 +1164,7 @@ static int s3fs_unlink(const char* _path) result = s3fscurl.DeleteRequest(path); FdManager::DeleteCacheFile(path); StatCache::getStatCacheData()->DelStat(path); + StatCache::getStatCacheData()->DelSymlink(path); S3FS_MALLOCTRIM(0); return result; @@ -1279,6 +1294,9 @@ static int s3fs_symlink(const char* _from, const char* _to) FdManager::get()->Close(ent); StatCache::getStatCacheData()->DelStat(to); + if(!StatCache::getStatCacheData()->AddSymlink(string(to), strFrom)){ + S3FS_PRN_ERR("failed to add symbolic link cache for %s", to); + } S3FS_MALLOCTRIM(0); return result; diff --git a/src/s3fs_util.cpp b/src/s3fs_util.cpp index 842706b..1e5b3bf 100644 --- a/src/s3fs_util.cpp +++ b/src/s3fs_util.cpp @@ -1199,18 +1199,20 @@ void show_help () " API. The default is 1000. you can set this value to 1000 or more.\n" "\n" " max_stat_cache_size (default=\"100,000\" entries (about 40MB))\n" - " - maximum number of entries in the stat cache\n" + " - maximum number of entries in the stat cache, and this maximum is\n" + " also treated as the number of symbolic link cache.\n" "\n" " stat_cache_expire (default is no expire)\n" " - specify expire time (seconds) for entries in the stat cache.\n" - " This expire time indicates the time since stat cached.\n" + " This expire time indicates the time since stat cached. and this\n" + " is also set to the expire time of the symbolic link cache.\n" "\n" " stat_cache_interval_expire (default is no expire)\n" - " - specify expire time (seconds) for entries in the stat cache.\n" + " - specify expire time (seconds) for entries in the stat cache(and\n" + " symbolic link cache).\n" " This expire time is based on the time from the last access time\n" - " of the stat cache. This option is exclusive with\n" - " stat_cache_expire, and is left for compatibility with older\n" - " versions.\n" + " of the stat cache. This option is exclusive with stat_cache_expire,\n" + " and is left for compatibility with older versions.\n" "\n" " enable_noobj_cache (default is disable)\n" " - enable cache entries for the object which does not exist.\n"