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