]>
git.ipfire.org Git - thirdparty/squid.git/blob - src/base/ClpMap.h
2 * Copyright (C) 1996-2023 The Squid Software Foundation and contributors
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
9 #ifndef SQUID__SRC_BASE_CLPMAP_H
10 #define SQUID__SRC_BASE_CLPMAP_H
12 #include "mem/PoolingAllocator.h"
13 #include "SquidMath.h"
14 #include "time/gadgets.h"
20 #include <unordered_map>
24 DefaultMemoryUsage(const Value
&e
)
29 /// An in-memory associative container enforcing three primary caching policies:
30 /// * Capacity: The memory used by cached entries has a configurable limit;
31 /// * Lifetime: Entries are hidden (and may be deleted) after their TTL expires;
32 /// * Priority: Capacity victims are purged in LRU order.
33 /// Individual cache entry operations have average constant-time complexity.
35 /// Value must meet STL requirements of Erasable and EmplaceConstructible.
36 /// Key must come with std::hash<Key> and std::equal_to<Key> instantiations.
37 /// Key::length() must return the number of memory bytes in use by the key.
38 /// MemoryUsedBy() must return the number of memory bytes in use by the value.
39 template <class Key
, class Value
, uint64_t MemoryUsedBy(const Value
&) = DefaultMemoryUsage
>
43 /// maximum desired entry caching duration (a.k.a. TTL), in seconds
46 explicit ClpMap(const uint64_t capacity
) { setMemLimit(capacity
); }
47 ClpMap(uint64_t capacity
, Ttl defaultTtl
);
50 // copying disabled because it may be expensive for large maps
51 // moving (implicitly) disabled for simplicity sake
52 ClpMap(const ClpMap
&) = delete;
53 ClpMap
&operator =(const ClpMap
&) = delete;
55 /// \return a pointer to a fresh cached value (or nil)
56 /// The underlying value is owned by the map, so the pointer may be
57 /// invalidated by any non-constant method call, including another get().
58 /// Also moves the found entry to the end of the purging queue.
59 const Value
*get(const Key
&);
61 /// Copy the given value into the map (with the given key and TTL)
62 /// \retval true the value was successfully copied into the map
63 /// \retval false caching was rejected (the map remains unchanged)
64 bool add(const Key
&, const Value
&, Ttl
);
66 /// Copy the given value into the map (with the given key and default TTL)
67 bool add(const Key
&key
, const Value
&v
) { return add(key
, v
, defaultTtl_
); }
69 /// Remove the corresponding entry (if any)
70 void del(const Key
&);
72 /// Reset the memory capacity for this map, purging if needed
73 void setMemLimit(uint64_t newLimit
);
75 /// The memory capacity for the map
76 uint64_t memLimit() const { return memLimit_
; }
78 /// The free space of the map
79 uint64_t freeMem() const { return memLimit() - memoryUsed(); }
81 /// The current (approximate) memory usage of the map
82 uint64_t memoryUsed() const { return memUsed_
; }
84 /// The number of currently stored entries, including expired ones
85 size_t entries() const { return entries_
.size(); }
88 /// the keeper of cache entry Key, Value, and caching-related entry metadata
92 Entry(const Key
&, const Value
&, const Ttl
);
94 /// whether the entry is stale
95 bool expired() const { return expires
< squid_curtime
; }
98 Key key
; ///< the entry search key; see ClpMap::get()
99 Value value
; ///< cached value provided by the map user
100 time_t expires
= 0; ///< get() stops returning the entry after this time
101 uint64_t memCounted
= 0; ///< memory accounted for this entry in our ClpMap
104 /// Entries in LRU order
105 using Entries
= std::list
<Entry
, PoolingAllocator
<Entry
> >;
106 using EntriesIterator
= typename
Entries::iterator
;
108 using IndexItem
= std::pair
<const Key
, EntriesIterator
>;
109 /// key:entry_position mapping for fast entry lookups by key
110 using Index
= std::unordered_map
<Key
, EntriesIterator
, std::hash
<Key
>, std::equal_to
<Key
>, PoolingAllocator
<IndexItem
> >;
111 using IndexIterator
= typename
Index::iterator
;
113 static std::optional
<uint64_t> MemoryCountedFor(const Key
&, const Value
&);
115 void trim(uint64_t wantSpace
);
116 void erase(const IndexIterator
&);
117 IndexIterator
find(const Key
&);
119 /// cached entries, including expired ones, in LRU order
122 /// entries_ positions indexed by the entry key
125 /// entry TTL to use if none provided to add()
126 Ttl defaultTtl_
= std::numeric_limits
<Ttl
>::max();
128 /// the maximum memory we are allowed to use for all cached entries
129 uint64_t memLimit_
= 0;
131 /// the total amount of memory we currently use for all cached entries
132 uint64_t memUsed_
= 0;
135 template <class Key
, class Value
, uint64_t MemoryUsedBy(const Value
&)>
136 ClpMap
<Key
, Value
, MemoryUsedBy
>::ClpMap(const uint64_t capacity
, const Ttl defaultTtl
):
137 defaultTtl_(defaultTtl
)
139 assert(defaultTtl
>= 0);
140 setMemLimit(capacity
);
143 template <class Key
, class Value
, uint64_t MemoryUsedBy(const Value
&)>
145 ClpMap
<Key
, Value
, MemoryUsedBy
>::setMemLimit(const uint64_t newLimit
)
147 if (memUsed_
> newLimit
)
148 trim(memLimit_
- newLimit
);
149 memLimit_
= newLimit
;
152 /// \returns the index position of an entry identified by its key (or end())
153 template <class Key
, class Value
, uint64_t MemoryUsedBy(const Value
&)>
154 typename ClpMap
<Key
, Value
, MemoryUsedBy
>::IndexIterator
155 ClpMap
<Key
, Value
, MemoryUsedBy
>::find(const Key
&key
)
157 const auto i
= index_
.find(key
);
158 if (i
== index_
.end())
161 const auto entryPosition
= i
->second
;
162 if (!entryPosition
->expired()) {
163 if (entryPosition
!= entries_
.begin())
164 entries_
.splice(entries_
.begin(), entries_
, entryPosition
);
167 // else fall through to cleanup
173 template <class Key
, class Value
, uint64_t MemoryUsedBy(const Value
&)>
175 ClpMap
<Key
, Value
, MemoryUsedBy
>::get(const Key
&key
)
177 const auto i
= find(key
);
178 if (i
!= index_
.end()) {
179 const auto &entry
= *(i
->second
);
185 template <class Key
, class Value
, uint64_t MemoryUsedBy(const Value
&)>
186 std::optional
<uint64_t>
187 ClpMap
<Key
, Value
, MemoryUsedBy
>::MemoryCountedFor(const Key
&k
, const Value
&v
)
189 // Both storage and index store keys, but we count keySz once, assuming that
190 // copying a Key does not consume more memory. This assumption holds for
191 // Key=SBuf, but, ideally, we should be outsourcing this decision to another
192 // configurable function, storing each key once, or hard-coding Key=SBuf.
193 const auto keySz
= k
.length();
195 // approximate calculation (e.g., containers store wrappers not value_types)
196 return NaturalSum
<uint64_t>(
199 sizeof(typename
Entries::value_type
),
202 sizeof(typename
Index::value_type
));
205 template <class Key
, class Value
, uint64_t MemoryUsedBy(const Value
&)>
207 ClpMap
<Key
, Value
, MemoryUsedBy
>::add(const Key
&key
, const Value
&v
, const Ttl ttl
)
209 // optimization: avoid del() search, MemoryCountedFor() in always-empty maps
216 return false; // already expired; will never be returned by get()
218 const auto memoryRequirements
= MemoryCountedFor(key
, v
);
219 if (!memoryRequirements
)
220 return false; // cannot even compute memory requirements
222 const auto wantSpace
= memoryRequirements
.value();
223 if (wantSpace
> memLimit() || wantSpace
== 0) // 0 is 64-bit integer overflow
224 return false; // will never fit
227 auto &addedEntry
= entries_
.emplace_front(key
, v
, ttl
);
228 index_
.emplace(key
, entries_
.begin());
230 addedEntry
.memCounted
= wantSpace
;
231 memUsed_
+= wantSpace
;
232 assert(memUsed_
>= wantSpace
); // no overflows
236 /// removes the cached entry (identified by its index) from the map
237 template <class Key
, class Value
, uint64_t MemoryUsedBy(const Value
&)>
239 ClpMap
<Key
, Value
, MemoryUsedBy
>::erase(const IndexIterator
&i
)
241 assert(i
!= index_
.end());
242 const auto entryPosition
= i
->second
;
244 assert(entryPosition
!= entries_
.end());
245 const auto sz
= entryPosition
->memCounted
;
246 assert(memUsed_
>= sz
);
249 index_
.erase(i
); // destroys a "pointer" to our Entry
250 entries_
.erase(entryPosition
); // destroys our Entry
253 template <class Key
, class Value
, uint64_t MemoryUsedBy(const Value
&)>
255 ClpMap
<Key
, Value
, MemoryUsedBy
>::del(const Key
&key
)
257 const auto i
= find(key
);
258 if (i
!= index_
.end())
262 /// purges entries to make free memory large enough to fit wantSpace bytes
263 template <class Key
, class Value
, uint64_t MemoryUsedBy(const Value
&)>
265 ClpMap
<Key
, Value
, MemoryUsedBy
>::trim(const uint64_t wantSpace
)
267 assert(wantSpace
<= memLimit()); // no infinite loops and in-vain trimming
268 while (freeMem() < wantSpace
) {
269 assert(!entries_
.empty());
270 // TODO: Purge expired entries first. They are useless, but their
271 // presence may lead to purging potentially useful fresh entries here.
272 del(entries_
.rbegin()->key
);
276 template <class Key
, class Value
, uint64_t MemoryUsedBy(const Value
&)>
277 ClpMap
<Key
, Value
, MemoryUsedBy
>::Entry::Entry(const Key
&aKey
, const Value
&v
, const Ttl ttl
) :
280 expires(0) // reset below
282 SetToNaturalSumOrMax(expires
, squid_curtime
, ttl
);
285 #endif /* SQUID__SRC_BASE_CLPMAP_H */