• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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:
Handle(const KeyType & key,const ValueType & value,const ExpirationType & expiration,const ExpirationType & now,bool onGet)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:
Iterator(const ExpiringCache & cache)93     explicit Iterator(const ExpiringCache& cache)
94         : cache_(cache),
95           it_(cache_.entries_.begin()) {
96     }
~Iterator()97     ~Iterator() {}
98 
HasNext()99     bool HasNext() const { return it_ != cache_.entries_.end(); }
Advance()100     void Advance() { ++it_; }
101 
key()102     const KeyType& key() const { return it_->first; }
value()103     const ValueType& value() const { return it_->second.first; }
expiration()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|.
ExpiringCache(size_t max_entries)117   explicit ExpiringCache(size_t max_entries) : max_entries_(max_entries) {}
~ExpiringCache()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.
Get(const KeyType & key,const ExpirationType & now)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|.
Put(const KeyType & key,const ValueType & value,const ExpirationType & now,const ExpirationType & expiration)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.
Clear()160   void Clear() {
161     entries_.clear();
162   }
163 
164   // Returns the number of entries in the cache.
size()165   size_t size() const { return entries_.size(); }
166 
167   // Returns the maximum number of entries in the cache.
max_entries()168   size_t max_entries() const { return max_entries_; }
169 
empty()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()|.
Compact(const ExpirationType & now)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 
Evict(typename EntryMap::iterator it,const ExpirationType & now,bool on_get)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