• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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