1 // Copyright 2012 The Chromium Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #ifndef NET_BASE_EXPIRING_CACHE_H_ 6 #define NET_BASE_EXPIRING_CACHE_H_ 7 8 #include <stddef.h> 9 10 #include <map> 11 #include <utility> 12 13 #include "base/gtest_prod_util.h" 14 #include "base/memory/raw_ref.h" 15 #include "base/time/time.h" 16 17 namespace net { 18 19 template <typename KeyType, 20 typename ValueType, 21 typename ExpirationType> 22 class NoopEvictionHandler { 23 public: Handle(const KeyType & key,const ValueType & value,const ExpirationType & expiration,const ExpirationType & now,bool onGet)24 void Handle(const KeyType& key, 25 const ValueType& value, 26 const ExpirationType& expiration, 27 const ExpirationType& now, 28 bool onGet) const { 29 } 30 }; 31 32 // Cache implementation where all entries have an explicit expiration policy. As 33 // new items are added, expired items will be removed first. 34 // The template types have the following requirements: 35 // KeyType must be LessThanComparable, Assignable, and CopyConstructible. 36 // ValueType must be CopyConstructible and Assignable. 37 // ExpirationType must be CopyConstructible and Assignable. 38 // ExpirationCompare is a function class that takes two arguments of the 39 // type ExpirationType and returns a bool. If |comp| is an instance of 40 // ExpirationCompare, then the expression |comp(current, expiration)| shall 41 // return true iff |current| is still valid within |expiration|. 42 // 43 // A simple use of this class may use base::TimeTicks, which provides a 44 // monotonically increasing clock, for the expiration type. Because it's always 45 // increasing, std::less<> can be used, which will simply ensure that |now| is 46 // sorted before |expiration|: 47 // 48 // ExpiringCache<std::string, std::string, base::TimeTicks, 49 // std::less<base::TimeTicks> > cache(0); 50 // // Add a value that expires in 5 minutes 51 // cache.Put("key1", "value1", base::TimeTicks::Now(), 52 // base::TimeTicks::Now() + base::Minutes(5)); 53 // // Add another value that expires in 10 minutes. 54 // cache.Put("key2", "value2", base::TimeTicks::Now(), 55 // base::TimeTicks::Now() + base::Minutes(10)); 56 // 57 // Alternatively, there may be some more complex expiration criteria, at which 58 // point a custom functor may be used: 59 // 60 // struct ComplexExpirationFunctor { 61 // bool operator()(const ComplexExpiration& now, 62 // const ComplexExpiration& expiration) const; 63 // }; 64 // ExpiringCache<std::string, std::string, ComplexExpiration, 65 // ComplexExpirationFunctor> cache(15); 66 // // Add a value that expires once the 'sprocket' has 'cog'-ified. 67 // cache.Put("key1", "value1", ComplexExpiration("sprocket"), 68 // ComplexExpiration("cog")); 69 template <typename KeyType, 70 typename ValueType, 71 typename ExpirationType, 72 typename ExpirationCompare, 73 typename EvictionHandler = NoopEvictionHandler<KeyType, 74 ValueType, 75 ExpirationType> > 76 class ExpiringCache { 77 public: 78 ExpiringCache(const ExpiringCache&) = delete; 79 ExpiringCache& operator=(const ExpiringCache&) = delete; 80 81 private: 82 // Intentionally violate the C++ Style Guide so that EntryMap is known to be 83 // a dependent type. Without this, Clang's two-phase lookup complains when 84 // using EntryMap::const_iterator, while GCC and MSVC happily resolve the 85 // typename. 86 87 // Tuple to represent the value and when it expires. 88 typedef std::pair<ValueType, ExpirationType> Entry; 89 typedef std::map<KeyType, Entry> EntryMap; 90 91 public: 92 typedef KeyType key_type; 93 typedef ValueType value_type; 94 typedef ExpirationType expiration_type; 95 96 // This class provides a read-only iterator over items in the ExpiringCache 97 class Iterator { 98 public: Iterator(const ExpiringCache & cache)99 explicit Iterator(const ExpiringCache& cache) 100 : cache_(cache), it_(cache_->entries_.begin()) {} 101 ~Iterator() = default; 102 HasNext()103 bool HasNext() const { return it_ != cache_->entries_.end(); } Advance()104 void Advance() { ++it_; } 105 key()106 const KeyType& key() const { return it_->first; } value()107 const ValueType& value() const { return it_->second.first; } expiration()108 const ExpirationType& expiration() const { return it_->second.second; } 109 110 private: 111 const raw_ref<const ExpiringCache> cache_; 112 113 // Use a second layer of type indirection, as both EntryMap and 114 // EntryMap::const_iterator are dependent types. 115 typedef typename ExpiringCache::EntryMap EntryMap; 116 typename EntryMap::const_iterator it_; 117 }; 118 119 120 // Constructs an ExpiringCache that stores up to |max_entries|. ExpiringCache(size_t max_entries)121 explicit ExpiringCache(size_t max_entries) : max_entries_(max_entries) {} 122 ~ExpiringCache() = default; 123 124 // Returns the value matching |key|, which must be valid at the time |now|. 125 // Returns NULL if the item is not found or has expired. If the item has 126 // expired, it is immediately removed from the cache. 127 // Note: The returned pointer remains owned by the ExpiringCache and is 128 // invalidated by a call to a non-const method. Get(const KeyType & key,const ExpirationType & now)129 const ValueType* Get(const KeyType& key, const ExpirationType& now) { 130 typename EntryMap::iterator it = entries_.find(key); 131 if (it == entries_.end()) 132 return nullptr; 133 134 // Immediately remove expired entries. 135 if (!expiration_comp_(now, it->second.second)) { 136 Evict(it, now, true); 137 return nullptr; 138 } 139 140 return &it->second.first; 141 } 142 143 // Updates or replaces the value associated with |key|. Put(const KeyType & key,const ValueType & value,const ExpirationType & now,const ExpirationType & expiration)144 void Put(const KeyType& key, 145 const ValueType& value, 146 const ExpirationType& now, 147 const ExpirationType& expiration) { 148 typename EntryMap::iterator it = entries_.find(key); 149 if (it == entries_.end()) { 150 // Compact the cache if it grew beyond the limit. 151 if (entries_.size() == max_entries_ ) 152 Compact(now); 153 154 // No existing entry. Creating a new one. 155 entries_.insert(std::make_pair(key, Entry(value, expiration))); 156 } else { 157 // Update an existing cache entry. 158 it->second.first = value; 159 it->second.second = expiration; 160 } 161 } 162 163 // Empties the cache. Clear()164 void Clear() { 165 entries_.clear(); 166 } 167 168 // Returns the number of entries in the cache. size()169 size_t size() const { return entries_.size(); } 170 171 // Returns the maximum number of entries in the cache. max_entries()172 size_t max_entries() const { return max_entries_; } 173 empty()174 bool empty() const { return entries_.empty(); } 175 176 private: 177 FRIEND_TEST_ALL_PREFIXES(ExpiringCacheTest, Compact); 178 FRIEND_TEST_ALL_PREFIXES(ExpiringCacheTest, CustomFunctor); 179 180 // Prunes entries from the cache to bring it below |max_entries()|. Compact(const ExpirationType & now)181 void Compact(const ExpirationType& now) { 182 // Clear out expired entries. 183 typename EntryMap::iterator it; 184 for (it = entries_.begin(); it != entries_.end(); ) { 185 if (!expiration_comp_(now, it->second.second)) { 186 Evict(it++, now, false); 187 } else { 188 ++it; 189 } 190 } 191 192 if (entries_.size() < max_entries_) 193 return; 194 195 // If the cache is still too full, start deleting items 'randomly'. 196 for (it = entries_.begin(); 197 it != entries_.end() && entries_.size() >= max_entries_;) { 198 Evict(it++, now, false); 199 } 200 } 201 Evict(typename EntryMap::iterator it,const ExpirationType & now,bool on_get)202 void Evict(typename EntryMap::iterator it, 203 const ExpirationType& now, 204 bool on_get) { 205 eviction_handler_.Handle(it->first, it->second.first, it->second.second, 206 now, on_get); 207 entries_.erase(it); 208 } 209 210 // Bound on total size of the cache. 211 size_t max_entries_; 212 213 EntryMap entries_; 214 ExpirationCompare expiration_comp_; 215 EvictionHandler eviction_handler_; 216 }; 217 218 } // namespace net 219 220 #endif // NET_BASE_EXPIRING_CACHE_H_ 221