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