xref: /aosp_15_r20/external/cronet/net/base/expiring_cache.h (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1*6777b538SAndroid Build Coastguard Worker // Copyright 2012 The Chromium Authors
2*6777b538SAndroid Build Coastguard Worker // Use of this source code is governed by a BSD-style license that can be
3*6777b538SAndroid Build Coastguard Worker // found in the LICENSE file.
4*6777b538SAndroid Build Coastguard Worker 
5*6777b538SAndroid Build Coastguard Worker #ifndef NET_BASE_EXPIRING_CACHE_H_
6*6777b538SAndroid Build Coastguard Worker #define NET_BASE_EXPIRING_CACHE_H_
7*6777b538SAndroid Build Coastguard Worker 
8*6777b538SAndroid Build Coastguard Worker #include <stddef.h>
9*6777b538SAndroid Build Coastguard Worker 
10*6777b538SAndroid Build Coastguard Worker #include <map>
11*6777b538SAndroid Build Coastguard Worker #include <utility>
12*6777b538SAndroid Build Coastguard Worker 
13*6777b538SAndroid Build Coastguard Worker #include "base/gtest_prod_util.h"
14*6777b538SAndroid Build Coastguard Worker #include "base/memory/raw_ref.h"
15*6777b538SAndroid Build Coastguard Worker #include "base/time/time.h"
16*6777b538SAndroid Build Coastguard Worker 
17*6777b538SAndroid Build Coastguard Worker namespace net {
18*6777b538SAndroid Build Coastguard Worker 
19*6777b538SAndroid Build Coastguard Worker template <typename KeyType,
20*6777b538SAndroid Build Coastguard Worker           typename ValueType,
21*6777b538SAndroid Build Coastguard Worker           typename ExpirationType>
22*6777b538SAndroid Build Coastguard Worker class NoopEvictionHandler {
23*6777b538SAndroid Build Coastguard Worker  public:
Handle(const KeyType & key,const ValueType & value,const ExpirationType & expiration,const ExpirationType & now,bool onGet)24*6777b538SAndroid Build Coastguard Worker   void Handle(const KeyType& key,
25*6777b538SAndroid Build Coastguard Worker               const ValueType& value,
26*6777b538SAndroid Build Coastguard Worker               const ExpirationType& expiration,
27*6777b538SAndroid Build Coastguard Worker               const ExpirationType& now,
28*6777b538SAndroid Build Coastguard Worker               bool onGet) const {
29*6777b538SAndroid Build Coastguard Worker   }
30*6777b538SAndroid Build Coastguard Worker };
31*6777b538SAndroid Build Coastguard Worker 
32*6777b538SAndroid Build Coastguard Worker // Cache implementation where all entries have an explicit expiration policy. As
33*6777b538SAndroid Build Coastguard Worker // new items are added, expired items will be removed first.
34*6777b538SAndroid Build Coastguard Worker // The template types have the following requirements:
35*6777b538SAndroid Build Coastguard Worker //  KeyType must be LessThanComparable, Assignable, and CopyConstructible.
36*6777b538SAndroid Build Coastguard Worker //  ValueType must be CopyConstructible and Assignable.
37*6777b538SAndroid Build Coastguard Worker //  ExpirationType must be CopyConstructible and Assignable.
38*6777b538SAndroid Build Coastguard Worker //  ExpirationCompare is a function class that takes two arguments of the
39*6777b538SAndroid Build Coastguard Worker //    type ExpirationType and returns a bool. If |comp| is an instance of
40*6777b538SAndroid Build Coastguard Worker //    ExpirationCompare, then the expression |comp(current, expiration)| shall
41*6777b538SAndroid Build Coastguard Worker //    return true iff |current| is still valid within |expiration|.
42*6777b538SAndroid Build Coastguard Worker //
43*6777b538SAndroid Build Coastguard Worker // A simple use of this class may use base::TimeTicks, which provides a
44*6777b538SAndroid Build Coastguard Worker // monotonically increasing clock, for the expiration type. Because it's always
45*6777b538SAndroid Build Coastguard Worker // increasing, std::less<> can be used, which will simply ensure that |now| is
46*6777b538SAndroid Build Coastguard Worker // sorted before |expiration|:
47*6777b538SAndroid Build Coastguard Worker //
48*6777b538SAndroid Build Coastguard Worker //   ExpiringCache<std::string, std::string, base::TimeTicks,
49*6777b538SAndroid Build Coastguard Worker //                 std::less<base::TimeTicks> > cache(0);
50*6777b538SAndroid Build Coastguard Worker //   // Add a value that expires in 5 minutes
51*6777b538SAndroid Build Coastguard Worker //   cache.Put("key1", "value1", base::TimeTicks::Now(),
52*6777b538SAndroid Build Coastguard Worker //             base::TimeTicks::Now() + base::Minutes(5));
53*6777b538SAndroid Build Coastguard Worker //   // Add another value that expires in 10 minutes.
54*6777b538SAndroid Build Coastguard Worker //   cache.Put("key2", "value2", base::TimeTicks::Now(),
55*6777b538SAndroid Build Coastguard Worker //             base::TimeTicks::Now() + base::Minutes(10));
56*6777b538SAndroid Build Coastguard Worker //
57*6777b538SAndroid Build Coastguard Worker // Alternatively, there may be some more complex expiration criteria, at which
58*6777b538SAndroid Build Coastguard Worker // point a custom functor may be used:
59*6777b538SAndroid Build Coastguard Worker //
60*6777b538SAndroid Build Coastguard Worker //   struct ComplexExpirationFunctor {
61*6777b538SAndroid Build Coastguard Worker //     bool operator()(const ComplexExpiration& now,
62*6777b538SAndroid Build Coastguard Worker //                     const ComplexExpiration& expiration) const;
63*6777b538SAndroid Build Coastguard Worker //   };
64*6777b538SAndroid Build Coastguard Worker //   ExpiringCache<std::string, std::string, ComplexExpiration,
65*6777b538SAndroid Build Coastguard Worker //                 ComplexExpirationFunctor> cache(15);
66*6777b538SAndroid Build Coastguard Worker //   // Add a value that expires once the 'sprocket' has 'cog'-ified.
67*6777b538SAndroid Build Coastguard Worker //   cache.Put("key1", "value1", ComplexExpiration("sprocket"),
68*6777b538SAndroid Build Coastguard Worker //             ComplexExpiration("cog"));
69*6777b538SAndroid Build Coastguard Worker template <typename KeyType,
70*6777b538SAndroid Build Coastguard Worker           typename ValueType,
71*6777b538SAndroid Build Coastguard Worker           typename ExpirationType,
72*6777b538SAndroid Build Coastguard Worker           typename ExpirationCompare,
73*6777b538SAndroid Build Coastguard Worker           typename EvictionHandler = NoopEvictionHandler<KeyType,
74*6777b538SAndroid Build Coastguard Worker                                                          ValueType,
75*6777b538SAndroid Build Coastguard Worker                                                          ExpirationType> >
76*6777b538SAndroid Build Coastguard Worker class ExpiringCache {
77*6777b538SAndroid Build Coastguard Worker  public:
78*6777b538SAndroid Build Coastguard Worker   ExpiringCache(const ExpiringCache&) = delete;
79*6777b538SAndroid Build Coastguard Worker   ExpiringCache& operator=(const ExpiringCache&) = delete;
80*6777b538SAndroid Build Coastguard Worker 
81*6777b538SAndroid Build Coastguard Worker  private:
82*6777b538SAndroid Build Coastguard Worker   // Intentionally violate the C++ Style Guide so that EntryMap is known to be
83*6777b538SAndroid Build Coastguard Worker   // a dependent type. Without this, Clang's two-phase lookup complains when
84*6777b538SAndroid Build Coastguard Worker   // using EntryMap::const_iterator, while GCC and MSVC happily resolve the
85*6777b538SAndroid Build Coastguard Worker   // typename.
86*6777b538SAndroid Build Coastguard Worker 
87*6777b538SAndroid Build Coastguard Worker   // Tuple to represent the value and when it expires.
88*6777b538SAndroid Build Coastguard Worker   typedef std::pair<ValueType, ExpirationType> Entry;
89*6777b538SAndroid Build Coastguard Worker   typedef std::map<KeyType, Entry> EntryMap;
90*6777b538SAndroid Build Coastguard Worker 
91*6777b538SAndroid Build Coastguard Worker  public:
92*6777b538SAndroid Build Coastguard Worker   typedef KeyType key_type;
93*6777b538SAndroid Build Coastguard Worker   typedef ValueType value_type;
94*6777b538SAndroid Build Coastguard Worker   typedef ExpirationType expiration_type;
95*6777b538SAndroid Build Coastguard Worker 
96*6777b538SAndroid Build Coastguard Worker   // This class provides a read-only iterator over items in the ExpiringCache
97*6777b538SAndroid Build Coastguard Worker   class Iterator {
98*6777b538SAndroid Build Coastguard Worker    public:
Iterator(const ExpiringCache & cache)99*6777b538SAndroid Build Coastguard Worker     explicit Iterator(const ExpiringCache& cache)
100*6777b538SAndroid Build Coastguard Worker         : cache_(cache), it_(cache_->entries_.begin()) {}
101*6777b538SAndroid Build Coastguard Worker     ~Iterator() = default;
102*6777b538SAndroid Build Coastguard Worker 
HasNext()103*6777b538SAndroid Build Coastguard Worker     bool HasNext() const { return it_ != cache_->entries_.end(); }
Advance()104*6777b538SAndroid Build Coastguard Worker     void Advance() { ++it_; }
105*6777b538SAndroid Build Coastguard Worker 
key()106*6777b538SAndroid Build Coastguard Worker     const KeyType& key() const { return it_->first; }
value()107*6777b538SAndroid Build Coastguard Worker     const ValueType& value() const { return it_->second.first; }
expiration()108*6777b538SAndroid Build Coastguard Worker     const ExpirationType& expiration() const { return it_->second.second; }
109*6777b538SAndroid Build Coastguard Worker 
110*6777b538SAndroid Build Coastguard Worker    private:
111*6777b538SAndroid Build Coastguard Worker     const raw_ref<const ExpiringCache> cache_;
112*6777b538SAndroid Build Coastguard Worker 
113*6777b538SAndroid Build Coastguard Worker     // Use a second layer of type indirection, as both EntryMap and
114*6777b538SAndroid Build Coastguard Worker     // EntryMap::const_iterator are dependent types.
115*6777b538SAndroid Build Coastguard Worker     typedef typename ExpiringCache::EntryMap EntryMap;
116*6777b538SAndroid Build Coastguard Worker     typename EntryMap::const_iterator it_;
117*6777b538SAndroid Build Coastguard Worker   };
118*6777b538SAndroid Build Coastguard Worker 
119*6777b538SAndroid Build Coastguard Worker 
120*6777b538SAndroid Build Coastguard Worker   // Constructs an ExpiringCache that stores up to |max_entries|.
ExpiringCache(size_t max_entries)121*6777b538SAndroid Build Coastguard Worker   explicit ExpiringCache(size_t max_entries) : max_entries_(max_entries) {}
122*6777b538SAndroid Build Coastguard Worker   ~ExpiringCache() = default;
123*6777b538SAndroid Build Coastguard Worker 
124*6777b538SAndroid Build Coastguard Worker   // Returns the value matching |key|, which must be valid at the time |now|.
125*6777b538SAndroid Build Coastguard Worker   // Returns NULL if the item is not found or has expired. If the item has
126*6777b538SAndroid Build Coastguard Worker   // expired, it is immediately removed from the cache.
127*6777b538SAndroid Build Coastguard Worker   // Note: The returned pointer remains owned by the ExpiringCache and is
128*6777b538SAndroid Build Coastguard Worker   // invalidated by a call to a non-const method.
Get(const KeyType & key,const ExpirationType & now)129*6777b538SAndroid Build Coastguard Worker   const ValueType* Get(const KeyType& key, const ExpirationType& now) {
130*6777b538SAndroid Build Coastguard Worker     typename EntryMap::iterator it = entries_.find(key);
131*6777b538SAndroid Build Coastguard Worker     if (it == entries_.end())
132*6777b538SAndroid Build Coastguard Worker       return nullptr;
133*6777b538SAndroid Build Coastguard Worker 
134*6777b538SAndroid Build Coastguard Worker     // Immediately remove expired entries.
135*6777b538SAndroid Build Coastguard Worker     if (!expiration_comp_(now, it->second.second)) {
136*6777b538SAndroid Build Coastguard Worker       Evict(it, now, true);
137*6777b538SAndroid Build Coastguard Worker       return nullptr;
138*6777b538SAndroid Build Coastguard Worker     }
139*6777b538SAndroid Build Coastguard Worker 
140*6777b538SAndroid Build Coastguard Worker     return &it->second.first;
141*6777b538SAndroid Build Coastguard Worker   }
142*6777b538SAndroid Build Coastguard Worker 
143*6777b538SAndroid Build Coastguard Worker   // Updates or replaces the value associated with |key|.
Put(const KeyType & key,const ValueType & value,const ExpirationType & now,const ExpirationType & expiration)144*6777b538SAndroid Build Coastguard Worker   void Put(const KeyType& key,
145*6777b538SAndroid Build Coastguard Worker            const ValueType& value,
146*6777b538SAndroid Build Coastguard Worker            const ExpirationType& now,
147*6777b538SAndroid Build Coastguard Worker            const ExpirationType& expiration) {
148*6777b538SAndroid Build Coastguard Worker     typename EntryMap::iterator it = entries_.find(key);
149*6777b538SAndroid Build Coastguard Worker     if (it == entries_.end()) {
150*6777b538SAndroid Build Coastguard Worker       // Compact the cache if it grew beyond the limit.
151*6777b538SAndroid Build Coastguard Worker       if (entries_.size() == max_entries_ )
152*6777b538SAndroid Build Coastguard Worker         Compact(now);
153*6777b538SAndroid Build Coastguard Worker 
154*6777b538SAndroid Build Coastguard Worker       // No existing entry. Creating a new one.
155*6777b538SAndroid Build Coastguard Worker       entries_.insert(std::pair(key, Entry(value, expiration)));
156*6777b538SAndroid Build Coastguard Worker     } else {
157*6777b538SAndroid Build Coastguard Worker       // Update an existing cache entry.
158*6777b538SAndroid Build Coastguard Worker       it->second.first = value;
159*6777b538SAndroid Build Coastguard Worker       it->second.second = expiration;
160*6777b538SAndroid Build Coastguard Worker     }
161*6777b538SAndroid Build Coastguard Worker   }
162*6777b538SAndroid Build Coastguard Worker 
163*6777b538SAndroid Build Coastguard Worker   // Empties the cache.
Clear()164*6777b538SAndroid Build Coastguard Worker   void Clear() {
165*6777b538SAndroid Build Coastguard Worker     entries_.clear();
166*6777b538SAndroid Build Coastguard Worker   }
167*6777b538SAndroid Build Coastguard Worker 
168*6777b538SAndroid Build Coastguard Worker   // Returns the number of entries in the cache.
size()169*6777b538SAndroid Build Coastguard Worker   size_t size() const { return entries_.size(); }
170*6777b538SAndroid Build Coastguard Worker 
171*6777b538SAndroid Build Coastguard Worker   // Returns the maximum number of entries in the cache.
max_entries()172*6777b538SAndroid Build Coastguard Worker   size_t max_entries() const { return max_entries_; }
173*6777b538SAndroid Build Coastguard Worker 
empty()174*6777b538SAndroid Build Coastguard Worker   bool empty() const { return entries_.empty(); }
175*6777b538SAndroid Build Coastguard Worker 
176*6777b538SAndroid Build Coastguard Worker  private:
177*6777b538SAndroid Build Coastguard Worker   FRIEND_TEST_ALL_PREFIXES(ExpiringCacheTest, Compact);
178*6777b538SAndroid Build Coastguard Worker   FRIEND_TEST_ALL_PREFIXES(ExpiringCacheTest, CustomFunctor);
179*6777b538SAndroid Build Coastguard Worker 
180*6777b538SAndroid Build Coastguard Worker   // Prunes entries from the cache to bring it below |max_entries()|.
Compact(const ExpirationType & now)181*6777b538SAndroid Build Coastguard Worker   void Compact(const ExpirationType& now) {
182*6777b538SAndroid Build Coastguard Worker     // Clear out expired entries.
183*6777b538SAndroid Build Coastguard Worker     typename EntryMap::iterator it;
184*6777b538SAndroid Build Coastguard Worker     for (it = entries_.begin(); it != entries_.end(); ) {
185*6777b538SAndroid Build Coastguard Worker       if (!expiration_comp_(now, it->second.second)) {
186*6777b538SAndroid Build Coastguard Worker         Evict(it++, now, false);
187*6777b538SAndroid Build Coastguard Worker       } else {
188*6777b538SAndroid Build Coastguard Worker         ++it;
189*6777b538SAndroid Build Coastguard Worker       }
190*6777b538SAndroid Build Coastguard Worker     }
191*6777b538SAndroid Build Coastguard Worker 
192*6777b538SAndroid Build Coastguard Worker     if (entries_.size() < max_entries_)
193*6777b538SAndroid Build Coastguard Worker       return;
194*6777b538SAndroid Build Coastguard Worker 
195*6777b538SAndroid Build Coastguard Worker     // If the cache is still too full, start deleting items 'randomly'.
196*6777b538SAndroid Build Coastguard Worker     for (it = entries_.begin();
197*6777b538SAndroid Build Coastguard Worker          it != entries_.end() && entries_.size() >= max_entries_;) {
198*6777b538SAndroid Build Coastguard Worker       Evict(it++, now, false);
199*6777b538SAndroid Build Coastguard Worker     }
200*6777b538SAndroid Build Coastguard Worker   }
201*6777b538SAndroid Build Coastguard Worker 
Evict(typename EntryMap::iterator it,const ExpirationType & now,bool on_get)202*6777b538SAndroid Build Coastguard Worker   void Evict(typename EntryMap::iterator it,
203*6777b538SAndroid Build Coastguard Worker              const ExpirationType& now,
204*6777b538SAndroid Build Coastguard Worker              bool on_get) {
205*6777b538SAndroid Build Coastguard Worker     eviction_handler_.Handle(it->first, it->second.first, it->second.second,
206*6777b538SAndroid Build Coastguard Worker                              now, on_get);
207*6777b538SAndroid Build Coastguard Worker     entries_.erase(it);
208*6777b538SAndroid Build Coastguard Worker   }
209*6777b538SAndroid Build Coastguard Worker 
210*6777b538SAndroid Build Coastguard Worker   // Bound on total size of the cache.
211*6777b538SAndroid Build Coastguard Worker   size_t max_entries_;
212*6777b538SAndroid Build Coastguard Worker 
213*6777b538SAndroid Build Coastguard Worker   EntryMap entries_;
214*6777b538SAndroid Build Coastguard Worker   ExpirationCompare expiration_comp_;
215*6777b538SAndroid Build Coastguard Worker   EvictionHandler eviction_handler_;
216*6777b538SAndroid Build Coastguard Worker };
217*6777b538SAndroid Build Coastguard Worker 
218*6777b538SAndroid Build Coastguard Worker }  // namespace net
219*6777b538SAndroid Build Coastguard Worker 
220*6777b538SAndroid Build Coastguard Worker #endif  // NET_BASE_EXPIRING_CACHE_H_
221