| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef NET_BASE_EXPIRING_CACHE_H_ |
| #define NET_BASE_EXPIRING_CACHE_H_ |
| |
| #include <map> |
| #include <utility> |
| |
| #include "base/basictypes.h" |
| #include "base/gtest_prod_util.h" |
| #include "base/time/time.h" |
| |
| namespace net { |
| |
| template <typename KeyType, |
| typename ValueType, |
| typename ExpirationType> |
| class NoopEvictionHandler { |
| public: |
| void Handle(const KeyType& key, |
| const ValueType& value, |
| const ExpirationType& expiration, |
| const ExpirationType& now, |
| bool onGet) const { |
| } |
| }; |
| |
| // Cache implementation where all entries have an explicit expiration policy. As |
| // new items are added, expired items will be removed first. |
| // The template types have the following requirements: |
| // KeyType must be LessThanComparable, Assignable, and CopyConstructible. |
| // ValueType must be CopyConstructible and Assignable. |
| // ExpirationType must be CopyConstructible and Assignable. |
| // ExpirationCompare is a function class that takes two arguments of the |
| // type ExpirationType and returns a bool. If |comp| is an instance of |
| // ExpirationCompare, then the expression |comp(current, expiration)| shall |
| // return true iff |current| is still valid within |expiration|. |
| // |
| // A simple use of this class may use base::TimeTicks, which provides a |
| // monotonically increasing clock, for the expiration type. Because it's always |
| // increasing, std::less<> can be used, which will simply ensure that |now| is |
| // sorted before |expiration|: |
| // |
| // ExpiringCache<std::string, std::string, base::TimeTicks, |
| // std::less<base::TimeTicks> > cache(0); |
| // // Add a value that expires in 5 minutes |
| // cache.Put("key1", "value1", base::TimeTicks::Now(), |
| // base::TimeTicks::Now() + base::TimeDelta::FromMinutes(5)); |
| // // Add another value that expires in 10 minutes. |
| // cache.Put("key2", "value2", base::TimeTicks::Now(), |
| // base::TimeTicks::Now() + base::TimeDelta::FromMinutes(10)); |
| // |
| // Alternatively, there may be some more complex expiration criteria, at which |
| // point a custom functor may be used: |
| // |
| // struct ComplexExpirationFunctor { |
| // bool operator()(const ComplexExpiration& now, |
| // const ComplexExpiration& expiration) const; |
| // }; |
| // ExpiringCache<std::string, std::string, ComplexExpiration, |
| // ComplexExpirationFunctor> cache(15); |
| // // Add a value that expires once the 'sprocket' has 'cog'-ified. |
| // cache.Put("key1", "value1", ComplexExpiration("sprocket"), |
| // ComplexExpiration("cog")); |
| template <typename KeyType, |
| typename ValueType, |
| typename ExpirationType, |
| typename ExpirationCompare, |
| typename EvictionHandler = NoopEvictionHandler<KeyType, |
| ValueType, |
| ExpirationType> > |
| class ExpiringCache { |
| private: |
| // Intentionally violate the C++ Style Guide so that EntryMap is known to be |
| // a dependent type. Without this, Clang's two-phase lookup complains when |
| // using EntryMap::const_iterator, while GCC and MSVC happily resolve the |
| // typename. |
| |
| // Tuple to represent the value and when it expires. |
| typedef std::pair<ValueType, ExpirationType> Entry; |
| typedef std::map<KeyType, Entry> EntryMap; |
| |
| public: |
| typedef KeyType key_type; |
| typedef ValueType value_type; |
| typedef ExpirationType expiration_type; |
| |
| // This class provides a read-only iterator over items in the ExpiringCache |
| class Iterator { |
| public: |
| explicit Iterator(const ExpiringCache& cache) |
| : cache_(cache), |
| it_(cache_.entries_.begin()) { |
| } |
| ~Iterator() {} |
| |
| bool HasNext() const { return it_ != cache_.entries_.end(); } |
| void Advance() { ++it_; } |
| |
| const KeyType& key() const { return it_->first; } |
| const ValueType& value() const { return it_->second.first; } |
| const ExpirationType& expiration() const { return it_->second.second; } |
| |
| private: |
| const ExpiringCache& cache_; |
| |
| // Use a second layer of type indirection, as both EntryMap and |
| // EntryMap::const_iterator are dependent types. |
| typedef typename ExpiringCache::EntryMap EntryMap; |
| typename EntryMap::const_iterator it_; |
| }; |
| |
| |
| // Constructs an ExpiringCache that stores up to |max_entries|. |
| explicit ExpiringCache(size_t max_entries) : max_entries_(max_entries) {} |
| ~ExpiringCache() {} |
| |
| // Returns the value matching |key|, which must be valid at the time |now|. |
| // Returns NULL if the item is not found or has expired. If the item has |
| // expired, it is immediately removed from the cache. |
| // Note: The returned pointer remains owned by the ExpiringCache and is |
| // invalidated by a call to a non-const method. |
| const ValueType* Get(const KeyType& key, const ExpirationType& now) { |
| typename EntryMap::iterator it = entries_.find(key); |
| if (it == entries_.end()) |
| return NULL; |
| |
| // Immediately remove expired entries. |
| if (!expiration_comp_(now, it->second.second)) { |
| Evict(it, now, true); |
| return NULL; |
| } |
| |
| return &it->second.first; |
| } |
| |
| // Updates or replaces the value associated with |key|. |
| void Put(const KeyType& key, |
| const ValueType& value, |
| const ExpirationType& now, |
| const ExpirationType& expiration) { |
| typename EntryMap::iterator it = entries_.find(key); |
| if (it == entries_.end()) { |
| // Compact the cache if it grew beyond the limit. |
| if (entries_.size() == max_entries_ ) |
| Compact(now); |
| |
| // No existing entry. Creating a new one. |
| entries_.insert(std::make_pair(key, Entry(value, expiration))); |
| } else { |
| // Update an existing cache entry. |
| it->second.first = value; |
| it->second.second = expiration; |
| } |
| } |
| |
| // Empties the cache. |
| void Clear() { |
| entries_.clear(); |
| } |
| |
| // Returns the number of entries in the cache. |
| size_t size() const { return entries_.size(); } |
| |
| // Returns the maximum number of entries in the cache. |
| size_t max_entries() const { return max_entries_; } |
| |
| bool empty() const { return entries_.empty(); } |
| |
| private: |
| FRIEND_TEST_ALL_PREFIXES(ExpiringCacheTest, Compact); |
| FRIEND_TEST_ALL_PREFIXES(ExpiringCacheTest, CustomFunctor); |
| |
| // Prunes entries from the cache to bring it below |max_entries()|. |
| void Compact(const ExpirationType& now) { |
| // Clear out expired entries. |
| typename EntryMap::iterator it; |
| for (it = entries_.begin(); it != entries_.end(); ) { |
| if (!expiration_comp_(now, it->second.second)) { |
| Evict(it++, now, false); |
| } else { |
| ++it; |
| } |
| } |
| |
| if (entries_.size() < max_entries_) |
| return; |
| |
| // If the cache is still too full, start deleting items 'randomly'. |
| for (it = entries_.begin(); |
| it != entries_.end() && entries_.size() >= max_entries_;) { |
| Evict(it++, now, false); |
| } |
| } |
| |
| void Evict(typename EntryMap::iterator it, |
| const ExpirationType& now, |
| bool on_get) { |
| eviction_handler_.Handle(it->first, it->second.first, it->second.second, |
| now, on_get); |
| entries_.erase(it); |
| } |
| |
| // Bound on total size of the cache. |
| size_t max_entries_; |
| |
| EntryMap entries_; |
| ExpirationCompare expiration_comp_; |
| EvictionHandler eviction_handler_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ExpiringCache); |
| }; |
| |
| } // namespace net |
| |
| #endif // NET_BASE_EXPIRING_CACHE_H_ |