1 /* 2 Copyright 2013-2014 EditShare, 2013-2015 Skytechnology sp. z o.o. 3 4 This file is part of LizardFS. 5 6 LizardFS is free software: you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published by 8 the Free Software Foundation, version 3. 9 10 LizardFS is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public License 16 along with LizardFS. If not, see <http://www.gnu.org/licenses/>. 17 */ 18 19 #pragma once 20 21 #include "common/platform.h" 22 23 #include <atomic> 24 #include <functional> 25 #include <map> 26 #include <mutex> 27 #include <set> 28 #include <tuple> 29 #include <type_traits> 30 #include <unordered_map> 31 32 #include "common/hashfn.h" 33 #include "common/massert.h" 34 #include "common/time_utils.h" 35 36 /** 37 * Options for LruCache. 38 */ 39 struct LruCacheOption { 40 // Map type used by the cache 41 typedef std::true_type UseHashMap; 42 typedef std::false_type UseTreeMap; 43 44 // Reentrant map will use a mutex internally to work with multiple threads 45 struct Reentrant { 46 typedef std::mutex Mutex; 47 }; 48 49 // Not-reentrant map doesn't use any locking 50 struct NotReentrant { 51 struct Mutex { lockLruCacheOption::NotReentrant::Mutex52 void lock() {} unlockLruCacheOption::NotReentrant::Mutex53 void unlock() {} 54 }; 55 }; 56 }; 57 58 /** 59 * A map caching 'Values' for 'Keys' in LRU manner. 60 * 61 * 'KeysTupleToTimeAndValue' is an internal map type used by the cache. For details have a look 62 * at example usages. If possible use 'LruCache' typedef instead. 63 */ 64 template <class HashMapType, class ReentrancyType, class Value, class... Keys> 65 class LruCache { 66 public: 67 typedef std::function<Value(Keys...)> ValueObtainer; 68 69 /** 70 * \param maxTime period after which every cache entry is discarded 71 * \param maxElements capacity of the cache, after exceeding which values added least recently 72 * are removed 73 * \param valueObtainer a function that is used for obtaining a value for a given key, if 74 * cached value was not available. 75 */ LruCache(SteadyDuration maxTime,uint64_t maxElements,ValueObtainer valueObtainer)76 LruCache(SteadyDuration maxTime, uint64_t maxElements, ValueObtainer valueObtainer) 77 : cacheHit(0), 78 cacheExpired(0), 79 cacheMiss(0), 80 maxTime_ms(std::chrono::duration_cast<std::chrono::milliseconds>(maxTime).count()), 81 maxTime_(maxTime), 82 maxElements_(maxElements), 83 valueObtainer_(valueObtainer) { 84 } 85 86 /** 87 * If available return value from cache. Otherwise obtain it with use of 'valueObtainer', 88 * fill the cache and return the value. If new cache entry was added, try to cleanup the whole 89 * cache a bit by removing few outdated entries if there were any, or remove the oldest entry 90 * if the capacity was exceeded. 91 */ get(SteadyTimePoint currentTs,Keys...keys)92 Value get(SteadyTimePoint currentTs, Keys... keys) { 93 std::unique_lock<Mutex> lock(mutex_); 94 uint64_t ms = std::chrono::duration_cast<std::chrono::milliseconds>(maxTime_).count(); 95 if (maxTime_ms != ms) { 96 maxTime_ = std::chrono::milliseconds(maxTime_ms); 97 } 98 auto keyTuple = std::make_tuple(keys...); 99 auto iterator = keysToTimeAndValue_.find(keyTuple); 100 if (iterator != keysToTimeAndValue_.end()) { 101 auto keyTuplePointer = &iterator->first; 102 auto& ts = std::get<0>(iterator->second); 103 if (ts + maxTime_ >= currentTs) { 104 ++cacheHit; 105 auto& value = std::get<1>(iterator->second); 106 return value; 107 } else { 108 ++cacheExpired; 109 ++cacheMiss; 110 auto tsAndKeys = std::make_pair(ts, keyTuplePointer); 111 sassert(timeToKeys_.erase(tsAndKeys) == 1); 112 keysToTimeAndValue_.erase(keyTuple); 113 } 114 } else { 115 ++cacheMiss; 116 } 117 118 // Don't call valueObtainer under a lock 119 lock.unlock(); 120 auto value = valueObtainer_(keys...); 121 lock.lock(); 122 // If there was a race and the cache was filled after the lock was released, return the 123 // value that was just obtained, don't update the cache itself. 124 iterator = keysToTimeAndValue_.find(keyTuple); 125 if (iterator != keysToTimeAndValue_.end()) { 126 return value; 127 } 128 129 try { 130 auto tsAndValue = std::make_pair(currentTs, value); 131 keysToTimeAndValue_.insert(std::make_pair(keyTuple, std::move(tsAndValue))); 132 auto keyToTimeAndValue = keysToTimeAndValue_.find(keyTuple); 133 auto keysTuplePointer = &keyToTimeAndValue->first; 134 auto tsAndKeys = std::make_pair(currentTs, keysTuplePointer); 135 timeToKeys_.insert(tsAndKeys); 136 } catch (...) { 137 keysToTimeAndValue_.erase(keyTuple); 138 throw; 139 } 140 141 // If one value was (possibly) added, remove a few values, to keep 142 // number of elements in the cache limited 143 uint64_t few = 3; 144 this->cleanupWithoutLocking(currentTs, few); 145 return value; 146 } 147 148 /** 149 * Remove 'maxOperations' or less entries that are either outdated or make the cache exceed 150 * its capacity. 151 * 152 * Due to the way 'get' method was implemented the cache should never exceed its limit by more 153 * then 1, so this function does not have to be called by hand. 154 */ cleanup(SteadyTimePoint currentTs,uint32_t maxOperations)155 void cleanup(SteadyTimePoint currentTs, uint32_t maxOperations) { 156 std::unique_lock<Mutex> lock(mutex_); 157 cleanupWithoutLocking(currentTs, maxOperations); 158 } 159 160 /** 161 * Erase a cache entry, if there was one matching the key. Returns the number of erased 162 * entries; 163 */ erase(Keys...keys)164 uint32_t erase(Keys... keys) { 165 std::unique_lock<Mutex> lock(mutex_); 166 auto keyTuple = std::make_tuple(keys...); 167 auto iterator = keysToTimeAndValue_.find(keyTuple); 168 return eraseWithoutLocking(iterator); 169 } 170 171 /** 172 * Erases cache entries from [lowerBound, upperBound) range. Returns the number of erased 173 * entries; 174 */ erase(Keys...lowerBound,Keys...upperBound)175 uint32_t erase(Keys... lowerBound, Keys... upperBound) { 176 std::unique_lock<Mutex> lock(mutex_); 177 178 auto lowerKeys = std::make_tuple(lowerBound...); 179 auto lowerIt = keysToTimeAndValue_.lower_bound(lowerKeys); 180 181 auto upperKeys = std::make_tuple(upperBound...); 182 auto upperIt = keysToTimeAndValue_.lower_bound(upperKeys); 183 184 auto removed = 0; 185 for (auto it = lowerIt; it != upperIt;) { 186 removed += eraseWithoutLocking(it++); 187 } 188 return removed; 189 } 190 191 /** 192 * Removes all elements from cache. 193 */ clear()194 void clear() { 195 std::unique_lock<Mutex> lock(mutex_); 196 timeToKeys_.clear(); 197 keysToTimeAndValue_.clear(); 198 } 199 200 std::atomic<uint64_t> cacheHit; 201 std::atomic<uint64_t> cacheExpired; 202 std::atomic<uint64_t> cacheMiss; 203 std::atomic<uint64_t> maxTime_ms; 204 205 private: 206 typedef std::tuple<Keys...> KeysTuple; 207 typedef std::pair<SteadyTimePoint, Value> TimeAndValue; 208 typedef AlmostGenericTupleHash<Keys...> Hasher; 209 typedef std::pair<SteadyTimePoint, const KeysTuple*> TimeAndKeysPtr; 210 211 typedef typename std::conditional< 212 HashMapType::value, 213 std::unordered_map<KeysTuple, TimeAndValue, Hasher>, 214 std::map<KeysTuple, TimeAndValue> 215 >::type KeysTupleToTimeAndValue; 216 217 typedef typename ReentrancyType::Mutex Mutex; 218 219 /** 220 * Given an iterator erases a cache entry without acquiring a lock. Return 1 if some element 221 * was removed and 0 otherwise. 222 */ eraseWithoutLocking(typename KeysTupleToTimeAndValue::iterator iterator)223 uint32_t eraseWithoutLocking(typename KeysTupleToTimeAndValue::iterator iterator) { 224 if (iterator != keysToTimeAndValue_.end()) { 225 auto keyTuplePointer = &iterator->first; 226 TimeAndValue& timeAndValue = iterator->second; 227 auto ts = timeAndValue.first; 228 auto tsAndKeys = std::make_pair(ts, keyTuplePointer); 229 sassert(timeToKeys_.erase(tsAndKeys) == 1); 230 keysToTimeAndValue_.erase(iterator); 231 return 1; 232 } else { 233 return 0; 234 } 235 } 236 237 /** 238 * Does the same as 'cleanup', but without acquiring a lock 239 */ cleanupWithoutLocking(SteadyTimePoint currentTs,uint64_t maxOperations)240 void cleanupWithoutLocking(SteadyTimePoint currentTs, uint64_t maxOperations) { 241 for (uint64_t i = 0; i < maxOperations; ++i) { 242 auto oldestEntry = timeToKeys_.begin(); 243 if (oldestEntry == timeToKeys_.end()) { 244 return; 245 } 246 auto& ts = std::get<0>(*oldestEntry); 247 if ((ts + maxTime_ < currentTs) || (timeToKeys_.size() > maxElements_)) { 248 ++cacheExpired; 249 auto keyTuplePtr = std::get<1>(*oldestEntry); 250 timeToKeys_.erase(oldestEntry); 251 sassert(keysToTimeAndValue_.erase(*keyTuplePtr) == 1); 252 } else { 253 return; 254 } 255 } 256 } 257 258 SteadyDuration maxTime_; 259 uint64_t maxElements_; 260 ValueObtainer valueObtainer_; 261 Mutex mutex_; 262 KeysTupleToTimeAndValue keysToTimeAndValue_; 263 std::set<TimeAndKeysPtr> timeToKeys_; 264 }; 265