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