blob: a22497b76e51d1ed546def6a0b80a9c1ec117e83 [file] [log] [blame]
[email protected]2e9a06e2012-02-24 08:40:311// 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]2e9a06e2012-02-24 08:40:317
Avi Drissman13fc8932015-12-20 04:40:468#include <stddef.h>
9
[email protected]2e9a06e2012-02-24 08:40:3110#include <map>
11#include <utility>
12
[email protected]2e9a06e2012-02-24 08:40:3113#include "base/gtest_prod_util.h"
Avi Drissman13fc8932015-12-20 04:40:4614#include "base/macros.h"
[email protected]9da992db2013-06-28 05:40:4715#include "base/time/time.h"
[email protected]2e9a06e2012-02-24 08:40:3116
17namespace net {
18
[email protected]0de360c62012-11-08 22:26:4819template <typename KeyType,
20 typename ValueType,
21 typename ExpirationType>
22class 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]2e9a06e2012-02-24 08:40:3132// 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]94bf72f2012-06-19 19:37:2837// 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"));
69template <typename KeyType,
70 typename ValueType,
71 typename ExpirationType,
[email protected]0de360c62012-11-08 22:26:4872 typename ExpirationCompare,
73 typename EvictionHandler = NoopEvictionHandler<KeyType,
74 ValueType,
75 ExpirationType> >
[email protected]2e9a06e2012-02-24 08:40:3176class 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]94bf72f2012-06-19 19:37:2884 typedef std::pair<ValueType, ExpirationType> Entry;
[email protected]2e9a06e2012-02-24 08:40:3185 typedef std::map<KeyType, Entry> EntryMap;
86
87 public:
88 typedef KeyType key_type;
89 typedef ValueType value_type;
[email protected]94bf72f2012-06-19 19:37:2890 typedef ExpirationType expiration_type;
[email protected]2e9a06e2012-02-24 08:40:3191
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]94bf72f2012-06-19 19:37:28106 const ExpirationType& expiration() const { return it_->second.second; }
[email protected]2e9a06e2012-02-24 08:40:31107
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]94bf72f2012-06-19 19:37:28127 const ValueType* Get(const KeyType& key, const ExpirationType& now) {
[email protected]2e9a06e2012-02-24 08:40:31128 typename EntryMap::iterator it = entries_.find(key);
129 if (it == entries_.end())
130 return NULL;
131
132 // Immediately remove expired entries.
[email protected]94bf72f2012-06-19 19:37:28133 if (!expiration_comp_(now, it->second.second)) {
[email protected]0de360c62012-11-08 22:26:48134 Evict(it, now, true);
[email protected]2e9a06e2012-02-24 08:40:31135 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]94bf72f2012-06-19 19:37:28144 const ExpirationType& now,
145 const ExpirationType& expiration) {
[email protected]2e9a06e2012-02-24 08:40:31146 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]94bf72f2012-06-19 19:37:28176 FRIEND_TEST_ALL_PREFIXES(ExpiringCacheTest, CustomFunctor);
[email protected]2e9a06e2012-02-24 08:40:31177
178 // Prunes entries from the cache to bring it below |max_entries()|.
[email protected]94bf72f2012-06-19 19:37:28179 void Compact(const ExpirationType& now) {
[email protected]2e9a06e2012-02-24 08:40:31180 // Clear out expired entries.
181 typename EntryMap::iterator it;
182 for (it = entries_.begin(); it != entries_.end(); ) {
[email protected]94bf72f2012-06-19 19:37:28183 if (!expiration_comp_(now, it->second.second)) {
[email protected]0de360c62012-11-08 22:26:48184 Evict(it++, now, false);
[email protected]2e9a06e2012-02-24 08:40:31185 } 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]0de360c62012-11-08 22:26:48196 Evict(it++, now, false);
[email protected]2e9a06e2012-02-24 08:40:31197 }
198 }
199
[email protected]0de360c62012-11-08 22:26:48200 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]2e9a06e2012-02-24 08:40:31208 // Bound on total size of the cache.
209 size_t max_entries_;
210
211 EntryMap entries_;
[email protected]94bf72f2012-06-19 19:37:28212 ExpirationCompare expiration_comp_;
[email protected]0de360c62012-11-08 22:26:48213 EvictionHandler eviction_handler_;
[email protected]2e9a06e2012-02-24 08:40:31214
215 DISALLOW_COPY_AND_ASSIGN(ExpiringCache);
216};
217
218} // namespace net
219
220#endif // NET_BASE_EXPIRING_CACHE_H_