1 /*
2  * Copyright 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #pragma once
18 
19 #include <bluetooth/log.h>
20 
21 #include <functional>
22 #include <iterator>
23 #include <list>
24 #include <mutex>
25 #include <optional>
26 #include <thread>
27 #include <unordered_map>
28 
29 #include "common/list_map.h"
30 
31 namespace bluetooth {
32 namespace common {
33 
34 // An LRU map-cache the evict the oldest item when reaching capacity
35 //
36 // Usage:
37 //   - keys are sorted from warmest to coldest
38 //   - iterating through the cache won't warm up keys
39 //   - operations on iterators won't warm up keys
40 //   - find(), contains(), insert_or_assign() will warm up the key
41 //   - insert_or_assign() will evict coldest key when cache reaches capacity
42 //   - NOT THREAD SAFE
43 //
44 // Performance:
45 //   - Key look-up and modification is O(1)
46 //   - Memory consumption is:
47 //     O(2*capacity*sizeof(K) + capacity*(sizeof(nullptr)+sizeof(V)))
48 //
49 // Template:
50 //   - Key key type
51 //   - T value type
52 // */
53 template <typename Key, typename T>
54 class LruCache {
55 public:
56   using value_type = typename ListMap<Key, T>::value_type;
57   // different from c++17 node_type on purpose as we want node to be copyable
58   using node_type = typename ListMap<Key, T>::node_type;
59   using iterator = typename ListMap<Key, T>::iterator;
60   using const_iterator = typename ListMap<Key, T>::const_iterator;
61 
62   // Constructor a LRU cache with |capacity|
LruCache(size_t capacity)63   explicit LruCache(size_t capacity) : capacity_(capacity) {
64     log::assert_that(capacity_ != 0, "Unable to have 0 LRU Cache capacity");
65   }
66 
67   // for move
68   LruCache(LruCache&& other) noexcept = default;
69   LruCache& operator=(LruCache&& other) noexcept = default;
70 
71   // copy-constructor
72   // iterators in key_map_ cannot be copied directly
LruCache(const LruCache & other)73   LruCache(const LruCache& other) : capacity_(other.capacity_), list_map_(other.list_map_) {}
74 
75   // copy-assignment
76   // iterators in key_map_ cannot be copied directly
77   LruCache& operator=(const LruCache& other) {
78     if (&other == this) {
79       return *this;
80     }
81     capacity_ = other.capacity_;
82     list_map_ = other.list_map_;
83     return *this;
84   }
85 
86   // comparison operators
87   bool operator==(const LruCache& rhs) const {
88     return capacity_ == rhs.capacity_ && list_map_ == rhs.list_map_;
89   }
90   bool operator!=(const LruCache& rhs) const { return !(*this == rhs); }
91 
~LruCache()92   ~LruCache() { clear(); }
93 
94   // Clear the cache
clear()95   void clear() { list_map_.clear(); }
96 
97   // Find the value of a key, and move the key to the head of cache, if there is one. Return
98   // iterator to value if key exists, end() if not. Iterator might be invalidated when removed or
99   // evicted. Const version.
100   //
101   // LRU: Will warm up key
102   // LRU: Access to returned iterator won't move key in LRU
find(const Key & key)103   const_iterator find(const Key& key) const { return const_cast<LruCache*>(this)->find(key); }
104 
105   // Find the value of a key, and move the key to the head of cache, if there is one. Return
106   // iterator to value if key exists, end() if not. Iterator might be invalidated when removed or
107   // evicted
108   //
109   // LRU: Will warm up key
110   // LRU: Access to returned iterator won't move key in LRU
find(const Key & key)111   iterator find(const Key& key) {
112     auto iter = list_map_.find(key);
113     if (iter == list_map_.end()) {
114       return end();
115     }
116     // move to front
117     list_map_.splice(list_map_.begin(), list_map_, iter);
118     return iter;
119   }
120 
121   // Check if key exist in the cache. Return true if key exist in cache, false, if not
122   //
123   // LRU: Will warm up key
contains(const Key & key)124   bool contains(const Key& key) const { return find(key) != list_map_.end(); }
125 
126   // Put a key-value pair to the head of cache, evict the oldest key if cache is at capacity.
127   // Eviction is based on key ONLY. Hence, updating a key will not evict the oldest key. Return
128   // evicted value if old value was evicted, std::nullopt if not. The return value will be evaluated
129   // to true in a boolean context if a value is contained by std::optional, false otherwise.
130   //
131   // LRU: Will warm up key
insert_or_assign(const Key & key,T value)132   std::optional<node_type> insert_or_assign(const Key& key, T value) {
133     if (contains(key)) {
134       // contains() calls find() that moved the node to the head
135       list_map_.begin()->second = std::move(value);
136       return std::nullopt;
137     }
138     // remove tail if at capacity
139     std::optional<node_type> evicted_node = std::nullopt;
140     if (list_map_.size() == capacity_) {
141       evicted_node = list_map_.extract(std::prev(list_map_.end())->first);
142     }
143     // insert new one to front of list
144     list_map_.insert_or_assign(list_map_.begin(), key, std::move(value));
145     return evicted_node;
146   }
147 
148   // Put a key-value pair to the head of cache, evict the oldest key if cache is at capacity.
149   // Eviction is based on key ONLY. Hence, updating a key will not evict the oldest key. This method
150   // tries to construct the value in-place. If the key already exist, this method only update the
151   // value. Return inserted iterator, whether insertion happens, and evicted value if old value was
152   // evicted or std::nullopt
153   //
154   // LRU: Will warm up key
155   template <class... Args>
try_emplace(const Key & key,Args &&...args)156   std::tuple<iterator, bool, std::optional<node_type>> try_emplace(const Key& key, Args&&... args) {
157     if (contains(key)) {
158       // contains() calls find() that moved the node to the head
159       return std::make_tuple(end(), false, std::nullopt);
160     }
161     // remove tail if at capacity
162     std::optional<node_type> evicted_node = std::nullopt;
163     if (list_map_.size() == capacity_) {
164       evicted_node = list_map_.extract(std::prev(list_map_.end())->first);
165     }
166     // insert new one to front of list
167     auto pair = list_map_.try_emplace(list_map_.begin(), key, std::forward<Args>(args)...);
168     return std::make_tuple(pair.first, pair.second, std::move(evicted_node));
169   }
170 
171   // Delete a key from cache, return removed value if old value was evicted, std::nullopt if not.
172   // The return value will be evaluated to true in a boolean context if a value is contained by
173   // std::optional, false otherwise.
extract(const Key & key)174   inline std::optional<node_type> extract(const Key& key) { return list_map_.extract(key); }
175 
176   /// Remove an iterator pointed item from the lru cache and return the iterator immediately after
177   /// the erased item
erase(const_iterator iter)178   iterator erase(const_iterator iter) { return list_map_.erase(iter); }
179 
180   // Return size of the cache
size()181   inline size_t size() const { return list_map_.size(); }
182 
183   // Iterator interface for begin
begin()184   inline iterator begin() { return list_map_.begin(); }
185 
186   // Return iterator interface for begin, const
begin()187   inline const_iterator begin() const { return list_map_.begin(); }
188 
189   // Return iterator interface for end
end()190   inline iterator end() { return list_map_.end(); }
191 
192   // Iterator interface for end, const
end()193   inline const_iterator end() const { return list_map_.end(); }
194 
195 private:
196   size_t capacity_;
197   ListMap<Key, T> list_map_;
198 };
199 
200 }  // namespace common
201 }  // namespace bluetooth
202