[email protected] | 2e9a06e | 2012-02-24 08:40:31 | [diff] [blame] | 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_ |
[email protected] | 2e9a06e | 2012-02-24 08:40:31 | [diff] [blame] | 7 | |
Avi Drissman | 13fc893 | 2015-12-20 04:40:46 | [diff] [blame] | 8 | #include <stddef.h> |
| 9 | |
[email protected] | 2e9a06e | 2012-02-24 08:40:31 | [diff] [blame] | 10 | #include <map> |
| 11 | #include <utility> |
| 12 | |
[email protected] | 2e9a06e | 2012-02-24 08:40:31 | [diff] [blame] | 13 | #include "base/gtest_prod_util.h" |
Avi Drissman | 13fc893 | 2015-12-20 04:40:46 | [diff] [blame] | 14 | #include "base/macros.h" |
[email protected] | 9da992db | 2013-06-28 05:40:47 | [diff] [blame] | 15 | #include "base/time/time.h" |
[email protected] | 2e9a06e | 2012-02-24 08:40:31 | [diff] [blame] | 16 | |
| 17 | namespace net { |
| 18 | |
[email protected] | 0de360c6 | 2012-11-08 22:26:48 | [diff] [blame] | 19 | template <typename KeyType, |
| 20 | typename ValueType, |
| 21 | typename ExpirationType> |
| 22 | class NoopEvictionHandler { |
| 23 | public: |
| 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 | |
[email protected] | 2e9a06e | 2012-02-24 08:40:31 | [diff] [blame] | 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. |
[email protected] | 94bf72f | 2012-06-19 19:37:28 | [diff] [blame] | 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::TimeDelta::FromMinutes(5)); |
| 53 | // // Add another value that expires in 10 minutes. |
| 54 | // cache.Put("key2", "value2", base::TimeTicks::Now(), |
| 55 | // base::TimeTicks::Now() + base::TimeDelta::FromMinutes(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, |
[email protected] | 0de360c6 | 2012-11-08 22:26:48 | [diff] [blame] | 72 | typename ExpirationCompare, |
| 73 | typename EvictionHandler = NoopEvictionHandler<KeyType, |
| 74 | ValueType, |
| 75 | ExpirationType> > |
[email protected] | 2e9a06e | 2012-02-24 08:40:31 | [diff] [blame] | 76 | class ExpiringCache { |
| 77 | private: |
| 78 | // Intentionally violate the C++ Style Guide so that EntryMap is known to be |
| 79 | // a dependent type. Without this, Clang's two-phase lookup complains when |
| 80 | // using EntryMap::const_iterator, while GCC and MSVC happily resolve the |
| 81 | // typename. |
| 82 | |
| 83 | // Tuple to represent the value and when it expires. |
[email protected] | 94bf72f | 2012-06-19 19:37:28 | [diff] [blame] | 84 | typedef std::pair<ValueType, ExpirationType> Entry; |
[email protected] | 2e9a06e | 2012-02-24 08:40:31 | [diff] [blame] | 85 | typedef std::map<KeyType, Entry> EntryMap; |
| 86 | |
| 87 | public: |
| 88 | typedef KeyType key_type; |
| 89 | typedef ValueType value_type; |
[email protected] | 94bf72f | 2012-06-19 19:37:28 | [diff] [blame] | 90 | typedef ExpirationType expiration_type; |
[email protected] | 2e9a06e | 2012-02-24 08:40:31 | [diff] [blame] | 91 | |
| 92 | // This class provides a read-only iterator over items in the ExpiringCache |
| 93 | class Iterator { |
| 94 | public: |
| 95 | explicit Iterator(const ExpiringCache& cache) |
| 96 | : cache_(cache), |
| 97 | it_(cache_.entries_.begin()) { |
| 98 | } |
| 99 | ~Iterator() {} |
| 100 | |
| 101 | bool HasNext() const { return it_ != cache_.entries_.end(); } |
| 102 | void Advance() { ++it_; } |
| 103 | |
| 104 | const KeyType& key() const { return it_->first; } |
| 105 | const ValueType& value() const { return it_->second.first; } |
[email protected] | 94bf72f | 2012-06-19 19:37:28 | [diff] [blame] | 106 | const ExpirationType& expiration() const { return it_->second.second; } |
[email protected] | 2e9a06e | 2012-02-24 08:40:31 | [diff] [blame] | 107 | |
| 108 | private: |
| 109 | const ExpiringCache& cache_; |
| 110 | |
| 111 | // Use a second layer of type indirection, as both EntryMap and |
| 112 | // EntryMap::const_iterator are dependent types. |
| 113 | typedef typename ExpiringCache::EntryMap EntryMap; |
| 114 | typename EntryMap::const_iterator it_; |
| 115 | }; |
| 116 | |
| 117 | |
| 118 | // Constructs an ExpiringCache that stores up to |max_entries|. |
| 119 | explicit ExpiringCache(size_t max_entries) : max_entries_(max_entries) {} |
| 120 | ~ExpiringCache() {} |
| 121 | |
| 122 | // Returns the value matching |key|, which must be valid at the time |now|. |
| 123 | // Returns NULL if the item is not found or has expired. If the item has |
| 124 | // expired, it is immediately removed from the cache. |
| 125 | // Note: The returned pointer remains owned by the ExpiringCache and is |
| 126 | // invalidated by a call to a non-const method. |
[email protected] | 94bf72f | 2012-06-19 19:37:28 | [diff] [blame] | 127 | const ValueType* Get(const KeyType& key, const ExpirationType& now) { |
[email protected] | 2e9a06e | 2012-02-24 08:40:31 | [diff] [blame] | 128 | typename EntryMap::iterator it = entries_.find(key); |
| 129 | if (it == entries_.end()) |
| 130 | return NULL; |
| 131 | |
| 132 | // Immediately remove expired entries. |
[email protected] | 94bf72f | 2012-06-19 19:37:28 | [diff] [blame] | 133 | if (!expiration_comp_(now, it->second.second)) { |
[email protected] | 0de360c6 | 2012-11-08 22:26:48 | [diff] [blame] | 134 | Evict(it, now, true); |
[email protected] | 2e9a06e | 2012-02-24 08:40:31 | [diff] [blame] | 135 | return NULL; |
| 136 | } |
| 137 | |
| 138 | return &it->second.first; |
| 139 | } |
| 140 | |
| 141 | // Updates or replaces the value associated with |key|. |
| 142 | void Put(const KeyType& key, |
| 143 | const ValueType& value, |
[email protected] | 94bf72f | 2012-06-19 19:37:28 | [diff] [blame] | 144 | const ExpirationType& now, |
| 145 | const ExpirationType& expiration) { |
[email protected] | 2e9a06e | 2012-02-24 08:40:31 | [diff] [blame] | 146 | typename EntryMap::iterator it = entries_.find(key); |
| 147 | if (it == entries_.end()) { |
| 148 | // Compact the cache if it grew beyond the limit. |
| 149 | if (entries_.size() == max_entries_ ) |
| 150 | Compact(now); |
| 151 | |
| 152 | // No existing entry. Creating a new one. |
| 153 | entries_.insert(std::make_pair(key, Entry(value, expiration))); |
| 154 | } else { |
| 155 | // Update an existing cache entry. |
| 156 | it->second.first = value; |
| 157 | it->second.second = expiration; |
| 158 | } |
| 159 | } |
| 160 | |
| 161 | // Empties the cache. |
| 162 | void Clear() { |
| 163 | entries_.clear(); |
| 164 | } |
| 165 | |
| 166 | // Returns the number of entries in the cache. |
| 167 | size_t size() const { return entries_.size(); } |
| 168 | |
| 169 | // Returns the maximum number of entries in the cache. |
| 170 | size_t max_entries() const { return max_entries_; } |
| 171 | |
| 172 | bool empty() const { return entries_.empty(); } |
| 173 | |
| 174 | private: |
| 175 | FRIEND_TEST_ALL_PREFIXES(ExpiringCacheTest, Compact); |
[email protected] | 94bf72f | 2012-06-19 19:37:28 | [diff] [blame] | 176 | FRIEND_TEST_ALL_PREFIXES(ExpiringCacheTest, CustomFunctor); |
[email protected] | 2e9a06e | 2012-02-24 08:40:31 | [diff] [blame] | 177 | |
| 178 | // Prunes entries from the cache to bring it below |max_entries()|. |
[email protected] | 94bf72f | 2012-06-19 19:37:28 | [diff] [blame] | 179 | void Compact(const ExpirationType& now) { |
[email protected] | 2e9a06e | 2012-02-24 08:40:31 | [diff] [blame] | 180 | // Clear out expired entries. |
| 181 | typename EntryMap::iterator it; |
| 182 | for (it = entries_.begin(); it != entries_.end(); ) { |
[email protected] | 94bf72f | 2012-06-19 19:37:28 | [diff] [blame] | 183 | if (!expiration_comp_(now, it->second.second)) { |
[email protected] | 0de360c6 | 2012-11-08 22:26:48 | [diff] [blame] | 184 | Evict(it++, now, false); |
[email protected] | 2e9a06e | 2012-02-24 08:40:31 | [diff] [blame] | 185 | } else { |
| 186 | ++it; |
| 187 | } |
| 188 | } |
| 189 | |
| 190 | if (entries_.size() < max_entries_) |
| 191 | return; |
| 192 | |
| 193 | // If the cache is still too full, start deleting items 'randomly'. |
| 194 | for (it = entries_.begin(); |
| 195 | it != entries_.end() && entries_.size() >= max_entries_;) { |
[email protected] | 0de360c6 | 2012-11-08 22:26:48 | [diff] [blame] | 196 | Evict(it++, now, false); |
[email protected] | 2e9a06e | 2012-02-24 08:40:31 | [diff] [blame] | 197 | } |
| 198 | } |
| 199 | |
[email protected] | 0de360c6 | 2012-11-08 22:26:48 | [diff] [blame] | 200 | void Evict(typename EntryMap::iterator it, |
| 201 | const ExpirationType& now, |
| 202 | bool on_get) { |
| 203 | eviction_handler_.Handle(it->first, it->second.first, it->second.second, |
| 204 | now, on_get); |
| 205 | entries_.erase(it); |
| 206 | } |
| 207 | |
[email protected] | 2e9a06e | 2012-02-24 08:40:31 | [diff] [blame] | 208 | // Bound on total size of the cache. |
| 209 | size_t max_entries_; |
| 210 | |
| 211 | EntryMap entries_; |
[email protected] | 94bf72f | 2012-06-19 19:37:28 | [diff] [blame] | 212 | ExpirationCompare expiration_comp_; |
[email protected] | 0de360c6 | 2012-11-08 22:26:48 | [diff] [blame] | 213 | EvictionHandler eviction_handler_; |
[email protected] | 2e9a06e | 2012-02-24 08:40:31 | [diff] [blame] | 214 | |
| 215 | DISALLOW_COPY_AND_ASSIGN(ExpiringCache); |
| 216 | }; |
| 217 | |
| 218 | } // namespace net |
| 219 | |
| 220 | #endif // NET_BASE_EXPIRING_CACHE_H_ |