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.
5 #ifndef NET_BASE_EXPIRING_CACHE_H_
6 #define NET_BASE_EXPIRING_CACHE_H_
11 #include "base/basictypes.h"
12 #include "base/gtest_prod_util.h"
13 #include "base/time/time.h"
17 template <typename KeyType
,
19 typename ExpirationType
>
20 class NoopEvictionHandler
{
22 void Handle(const KeyType
& key
,
23 const ValueType
& value
,
24 const ExpirationType
& expiration
,
25 const ExpirationType
& now
,
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|.
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|:
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));
55 // Alternatively, there may be some more complex expiration criteria, at which
56 // point a custom functor may be used:
58 // struct ComplexExpirationFunctor {
59 // bool operator()(const ComplexExpiration& now,
60 // const ComplexExpiration& expiration) const;
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
,
69 typename ExpirationType
,
70 typename ExpirationCompare
,
71 typename EvictionHandler
= NoopEvictionHandler
<KeyType
,
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
81 // Tuple to represent the value and when it expires.
82 typedef std::pair
<ValueType
, ExpirationType
> Entry
;
83 typedef std::map
<KeyType
, Entry
> EntryMap
;
86 typedef KeyType key_type
;
87 typedef ValueType value_type
;
88 typedef ExpirationType expiration_type
;
90 // This class provides a read-only iterator over items in the ExpiringCache
93 explicit Iterator(const ExpiringCache
& cache
)
95 it_(cache_
.entries_
.begin()) {
99 bool HasNext() const { return it_
!= cache_
.entries_
.end(); }
100 void Advance() { ++it_
; }
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
; }
107 const ExpiringCache
& cache_
;
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_
;
116 // Constructs an ExpiringCache that stores up to |max_entries|.
117 explicit ExpiringCache(size_t max_entries
) : max_entries_(max_entries
) {}
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())
130 // Immediately remove expired entries.
131 if (!expiration_comp_(now
, it
->second
.second
)) {
132 Evict(it
, now
, true);
136 return &it
->second
.first
;
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_
)
150 // No existing entry. Creating a new one.
151 entries_
.insert(std::make_pair(key
, Entry(value
, expiration
)));
153 // Update an existing cache entry.
154 it
->second
.first
= value
;
155 it
->second
.second
= expiration
;
159 // Empties the cache.
164 // Returns the number of entries in the cache.
165 size_t size() const { return entries_
.size(); }
167 // Returns the maximum number of entries in the cache.
168 size_t max_entries() const { return max_entries_
; }
170 bool empty() const { return entries_
.empty(); }
173 FRIEND_TEST_ALL_PREFIXES(ExpiringCacheTest
, Compact
);
174 FRIEND_TEST_ALL_PREFIXES(ExpiringCacheTest
, CustomFunctor
);
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);
188 if (entries_
.size() < max_entries_
)
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);
198 void Evict(typename
EntryMap::iterator it
,
199 const ExpirationType
& now
,
201 eviction_handler_
.Handle(it
->first
, it
->second
.first
, it
->second
.second
,
206 // Bound on total size of the cache.
210 ExpirationCompare expiration_comp_
;
211 EvictionHandler eviction_handler_
;
213 DISALLOW_COPY_AND_ASSIGN(ExpiringCache
);
218 #endif // NET_BASE_EXPIRING_CACHE_H_