1 2 /** 3 * Copyright (C) 2018-present MongoDB, Inc. 4 * 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the Server Side Public License, version 1, 7 * as published by MongoDB, Inc. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * Server Side Public License for more details. 13 * 14 * You should have received a copy of the Server Side Public License 15 * along with this program. If not, see 16 * <http://www.mongodb.com/licensing/server-side-public-license>. 17 * 18 * As a special exception, the copyright holders give permission to link the 19 * code of portions of this program with the OpenSSL library under certain 20 * conditions as described in each individual source file and distribute 21 * linked combinations including the program with the OpenSSL library. You 22 * must comply with the Server Side Public License in all respects for 23 * all of the code used other than as permitted herein. If you modify file(s) 24 * with this exception, you may extend this exception to your version of the 25 * file(s), but you are not obligated to do so. If you do not wish to do so, 26 * delete this exception statement from your version. If you delete this 27 * exception statement from all source files in the program, then also delete 28 * it in the license file. 29 */ 30 31 #pragma once 32 33 #include <list> 34 #include <memory> 35 36 #include "mongo/base/status.h" 37 #include "mongo/stdx/unordered_map.h" 38 #include "mongo/util/assert_util.h" 39 40 namespace mongo { 41 42 /** 43 * A key-value store structure with a least recently used (LRU) replacement 44 * policy. The number of entries allowed in the kv-store is set as a constant 45 * upon construction. 46 * 47 * Caveat: 48 * This kv-store is NOT thread safe! The client to this utility is responsible 49 * for protecting concurrent access to the LRU store if used in a threaded 50 * context. 51 * 52 * Implemented as a doubly-linked list with a hash map for quickly locating the kv-store entries. 53 * The add(), get(), and remove() operations are all O(1). 54 * 55 * The keys of generic type K map to values of type V*. The V* 56 * pointers are owned by the kv-store. 57 * 58 * TODO: We could move this into the util/ directory and do any cleanup necessary to make it 59 * fully general. 60 */ 61 template <class K, class V> 62 class LRUKeyValue { 63 public: LRUKeyValue(size_t maxSize)64 LRUKeyValue(size_t maxSize) : _maxSize(maxSize), _currentSize(0){}; 65 ~LRUKeyValue()66 ~LRUKeyValue() { 67 clear(); 68 } 69 70 typedef std::pair<K, V*> KVListEntry; 71 72 typedef std::list<KVListEntry> KVList; 73 typedef typename KVList::iterator KVListIt; 74 typedef typename KVList::const_iterator KVListConstIt; 75 76 typedef stdx::unordered_map<K, KVListIt> KVMap; 77 typedef typename KVMap::const_iterator KVMapConstIt; 78 79 /** 80 * Add an (K, V*) pair to the store, where 'key' can 81 * be used to retrieve value 'entry' from the store. 82 * 83 * Takes ownership of 'entry'. 84 * 85 * If 'key' already exists in the kv-store, 'entry' will 86 * simply replace what is already there. 87 * 88 * The least recently used entry is evicted if the 89 * kv-store is full prior to the add() operation. 90 * 91 * If an entry is evicted, it will be returned in 92 * an unique_ptr for the caller to use before disposing. 93 */ add(const K & key,V * entry)94 std::unique_ptr<V> add(const K& key, V* entry) { 95 // If the key already exists, delete it first. 96 KVMapConstIt i = _kvMap.find(key); 97 if (i != _kvMap.end()) { 98 KVListIt found = i->second; 99 delete found->second; 100 _kvMap.erase(i); 101 _kvList.erase(found); 102 _currentSize--; 103 } 104 105 _kvList.push_front(std::make_pair(key, entry)); 106 _kvMap[key] = _kvList.begin(); 107 _currentSize++; 108 109 // If the store has grown beyond its allowed size, 110 // evict the least recently used entry. 111 if (_currentSize > _maxSize) { 112 V* evictedEntry = _kvList.back().second; 113 invariant(evictedEntry); 114 115 _kvMap.erase(_kvList.back().first); 116 _kvList.pop_back(); 117 _currentSize--; 118 invariant(_currentSize == _maxSize); 119 120 // Pass ownership of evicted entry to caller. 121 // If caller chooses to ignore this unique_ptr, 122 // the evicted entry will be deleted automatically. 123 return std::unique_ptr<V>(evictedEntry); 124 } 125 return std::unique_ptr<V>(); 126 } 127 128 /** 129 * Retrieve the value associated with 'key' from 130 * the kv-store. The value is returned through the 131 * out-parameter 'entryOut'. 132 * 133 * The kv-store retains ownership of 'entryOut', so 134 * it should not be deleted by the caller. 135 * 136 * As a side effect, the retrieved entry is promoted 137 * to the most recently used. 138 */ get(const K & key,V ** entryOut)139 Status get(const K& key, V** entryOut) const { 140 KVMapConstIt i = _kvMap.find(key); 141 if (i == _kvMap.end()) { 142 return Status(ErrorCodes::NoSuchKey, "no such key in LRU key-value store"); 143 } 144 KVListIt found = i->second; 145 V* foundEntry = found->second; 146 147 // Promote the kv-store entry to the front of the list. 148 // It is now the most recently used. 149 _kvMap.erase(i); 150 _kvList.erase(found); 151 _kvList.push_front(std::make_pair(key, foundEntry)); 152 _kvMap[key] = _kvList.begin(); 153 154 *entryOut = foundEntry; 155 return Status::OK(); 156 } 157 158 /** 159 * Remove the kv-store entry keyed by 'key'. 160 */ remove(const K & key)161 Status remove(const K& key) { 162 KVMapConstIt i = _kvMap.find(key); 163 if (i == _kvMap.end()) { 164 return Status(ErrorCodes::NoSuchKey, "no such key in LRU key-value store"); 165 } 166 KVListIt found = i->second; 167 delete found->second; 168 _kvMap.erase(i); 169 _kvList.erase(found); 170 _currentSize--; 171 return Status::OK(); 172 } 173 174 /** 175 * Deletes all entries in the kv-store. 176 */ clear()177 void clear() { 178 for (KVListIt i = _kvList.begin(); i != _kvList.end(); i++) { 179 delete i->second; 180 } 181 _kvList.clear(); 182 _kvMap.clear(); 183 _currentSize = 0; 184 } 185 186 /** 187 * Returns true if entry is found in the kv-store. 188 */ hasKey(const K & key)189 bool hasKey(const K& key) const { 190 return _kvMap.find(key) != _kvMap.end(); 191 } 192 193 /** 194 * Returns the number of entries currently in the kv-store. 195 */ size()196 size_t size() const { 197 return _currentSize; 198 } 199 200 /** 201 * TODO: The kv-store should implement its own iterator. Calling through to the underlying 202 * iterator exposes the internals, and forces the caller to make a horrible type 203 * declaration. 204 */ begin()205 KVListConstIt begin() const { 206 return _kvList.begin(); 207 } 208 end()209 KVListConstIt end() const { 210 return _kvList.end(); 211 } 212 213 private: 214 // The maximum allowable number of entries in the kv-store. 215 const size_t _maxSize; 216 217 // The number of entries currently in the kv-store. 218 size_t _currentSize; 219 220 // (K, V*) pairs are stored in this std::list. They are sorted in order 221 // of use, where the front is the most recently used and the back is the 222 // least recently used. 223 mutable KVList _kvList; 224 225 // Maps from a key to the corresponding std::list entry. 226 mutable KVMap _kvMap; 227 }; 228 229 } // namespace mongo 230