userver: userver/rcu/rcu_map.hpp Source File
Loading...
Searching...
No Matches
rcu_map.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/rcu/rcu_map.hpp
4/// @brief @copybrief rcu::RcuMap
5
6#include <iterator>
7#include <memory>
8#include <optional>
9#include <type_traits>
10#include <unordered_map>
11#include <utility>
12
13#include <userver/rcu/rcu.hpp>
14#include <userver/utils/traceful_exception.hpp>
15
16USERVER_NAMESPACE_BEGIN
17
18namespace rcu {
19
20namespace impl {
21
22template <typename RcuMapTraits>
23struct RcuTraitsFromRcuMapTraits : public DefaultRcuTraits {
24 using MutexType = typename RcuMapTraits::MutexType;
25 using DeleterType = typename RcuMapTraits::DeleterType;
26};
27
28struct ShouldInheritFromDefaultRcuMapTraits {};
29
30} // namespace impl
31
32/// Thrown on missing element access
34public:
35 using utils::TracefulException::TracefulException;
36};
37
38/// Default RcuMap traits.
39/// Member types:
40/// - `Hash` is a functor type that returns hash value for `Key`
41/// - `keyEqual` is a functor type that provide equality test for two values of
42/// type `Key`
43/// - `MutexType` is a writer's mutex type that has to be used to protect
44/// structure on update
45template <typename Key>
46struct DefaultRcuMapTraits : public impl::ShouldInheritFromDefaultRcuMapTraits {
47 using Hash = std::hash<Key>;
48 using KeyEqual = std::equal_to<Key>;
49 using MutexType = engine::Mutex;
50 using DeleterType = AsyncDeleter;
51};
52
53/// @brief Forward iterator for the rcu::RcuMap
54///
55/// Use member functions of rcu::RcuMap to retrieve the iterator.
56template <typename Key, typename Value, typename IterValue, typename RcuMapTraits>
57class RcuMapIterator final {
58 static_assert(
59 std::is_base_of_v<impl::ShouldInheritFromDefaultRcuMapTraits, RcuMapTraits>,
60 "RcuMapTraits should inherit from rcu::DefaultRcuMapTraits"
61 );
62 using Hash = typename RcuMapTraits::Hash;
63 using KeyEqual = typename RcuMapTraits::KeyEqual;
64 using MapType = std::unordered_map<Key, std::shared_ptr<Value>, Hash, KeyEqual>;
65 using BaseIterator = typename MapType::const_iterator;
66 using RcuTraits = typename impl::RcuTraitsFromRcuMapTraits<RcuMapTraits>;
67
68public:
69 using iterator_category = std::input_iterator_tag;
70 using difference_type = ptrdiff_t;
71 using value_type = std::pair<Key, std::shared_ptr<IterValue>>;
72 using reference = const value_type&;
73 using pointer = const value_type*;
74
75 RcuMapIterator() = default;
76
77 RcuMapIterator operator++(int);
78 RcuMapIterator& operator++();
79 reference operator*() const;
80 pointer operator->() const;
81
82 bool operator==(const RcuMapIterator&) const;
83 bool operator!=(const RcuMapIterator&) const;
84
85 /// @cond
86 /// For internal use only
87 RcuMapIterator(ReadablePtr<MapType, RcuTraits>&& ptr, BaseIterator iter);
88 /// @endcond
89
90private:
91 void UpdateCurrent();
92
93 std::optional<ReadablePtr<MapType, RcuTraits>> ptr_;
94 BaseIterator it_;
95 value_type current_;
96};
97
98/// @ingroup userver_concurrency userver_containers
99///
100/// @brief Map-like structure allowing RCU keyset updates.
101///
102/// Only keyset changes are thread-safe in scope of this class.
103/// Values are stored in `shared_ptr`s and are not copied during keyset change.
104/// The map itself is implemented as rcu::Variable, so every keyset change
105/// (e.g. insert or erase) triggers the whole map copying.
106/// @note No synchronization is provided for value access, it must be
107/// implemented by Value when necessary.
108///
109/// ## Example usage:
110///
111/// @snippet rcu/rcu_map_test.cpp Sample rcu::RcuMap usage
112///
113/// @see @ref scripts/docs/en/userver/synchronization.md
114template <typename Key, typename Value, typename RcuMapTraits>
115class RcuMap final {
116 using RcuTraits = typename impl::RcuTraitsFromRcuMapTraits<RcuMapTraits>;
117
118public:
119 static_assert(!std::is_reference_v<Key>);
120 static_assert(!std::is_reference_v<Value>);
121 static_assert(!std::is_const_v<Key>);
122
123 template <typename ValuePtrType>
124 struct InsertReturnTypeImpl;
125
126 using Hash = typename RcuMapTraits::Hash;
127 using KeyEqual = typename RcuMapTraits::KeyEqual;
128 using MutexType = typename RcuMapTraits::MutexType;
129 using ValuePtr = std::shared_ptr<Value>;
130 using Iterator = RcuMapIterator<Key, Value, Value, RcuMapTraits>;
131 using ConstValuePtr = std::shared_ptr<const Value>;
132 using ConstIterator = RcuMapIterator<Key, Value, const Value, RcuMapTraits>;
133 using RawMap = std::unordered_map<Key, ValuePtr, Hash, KeyEqual>;
134 using Snapshot = std::unordered_map<Key, ConstValuePtr, Hash, KeyEqual>;
135 using InsertReturnType = InsertReturnTypeImpl<ValuePtr>;
136
137 RcuMap() = default;
138
139 RcuMap(const RcuMap&) = delete;
140 RcuMap(RcuMap&&) = delete;
141 RcuMap& operator=(const RcuMap&) = delete;
142 RcuMap& operator=(RcuMap&&) = delete;
143
144 /// Returns an estimated size of the map at some point in time
146
147 /// @name Iteration support
148 /// @details Keyset is fixed at the start of the iteration and is not affected
149 /// by concurrent changes.
150 /// @{
151 ConstIterator begin() const;
152 ConstIterator end() const;
153 Iterator begin();
154 Iterator end();
155 /// @}
156
157 /// @brief Returns a readonly value pointer by its key if exists
158 /// @throws MissingKeyException if the key is not present
159 const ConstValuePtr operator[](const Key&) const;
160
161 /// @brief Returns a modifiable value pointer by key if exists or
162 /// default-creates one
163 /// @note Copies the whole map if the key doesn't exist.
164 const ValuePtr operator[](const Key&);
165
166 /// @brief Inserts a new element into the container if there is no element
167 /// with the key in the container.
168 /// Returns a pair consisting of a pointer to the inserted element, or the
169 /// already-existing element if no insertion happened, and a bool denoting
170 /// whether the insertion took place.
171 /// @note Copies the whole map if the key doesn't exist.
172 InsertReturnType Insert(const Key& key, ValuePtr value);
173
174 /// @brief Inserts a new element into the container constructed in-place with
175 /// the given args if there is no element with the key in the container.
176 /// Returns a pair consisting of a pointer to the inserted element, or the
177 /// already-existing element if no insertion happened, and a bool denoting
178 /// whether the insertion took place.
179 /// @note Copies the whole map if the key doesn't exist.
180 template <typename... Args>
181 InsertReturnType Emplace(const Key& key, Args&&... args);
182
183 /// @brief If a key equivalent to `key` already exists in the container, does
184 /// nothing.
185 /// Otherwise, behaves like `Emplace` except that the element is constructed
186 /// as `std::make_shared<Value>(std::piecewise_construct,
187 /// std::forward_as_tuple(key),
188 /// std::forward_as_tuple(std::forward<Args>(args)...))`.
189 /// Returns a pair consisting of a pointer to the inserted element, or the
190 /// already-existing element if no insertion happened, and a bool denoting
191 /// whether the insertion took place.
192 template <typename... Args>
193 InsertReturnType TryEmplace(const Key& key, Args&&... args);
194
195 /// @brief If a key equivalent to `key` already exists in the container,
196 /// replaces the associated value. Otherwise, inserts a new pair into the map.
197 template <typename RawKey>
198 void InsertOrAssign(RawKey&& key, ValuePtr value);
199
200 /// @brief Returns a readonly value pointer by its key or an empty pointer
201 const ConstValuePtr Get(const Key&) const;
202
203 /// @brief Returns a modifiable value pointer by key or an empty pointer
204 const ValuePtr Get(const Key&);
205
206 /// @brief Removes a key from the map
207 /// @returns whether the key was present
208 /// @note Copies the whole map, might be slow for large maps.
209 bool Erase(const Key&);
210
211 /// @brief Removes a key from the map returning its value
212 /// @returns a value if the key was present, empty pointer otherwise
213 /// @note Copies the whole map, might be slow for large maps.
214 ValuePtr Pop(const Key&);
215
216 /// Resets the map to an empty state
217 void Clear();
218
219 /// Replace current data by data from `new_map`.
220 void Assign(RawMap new_map);
221
222 /// @brief Starts a transaction, used to perform a series of arbitrary changes
223 /// to the map.
224 /// @details The map is copied. Don't forget to `Commit` to apply the changes.
226
227 /// @brief Returns a readonly copy of the map
228 /// @note Equivalent to `{begin(), end()}` construct, preferable
229 /// for long-running operations.
231
232private:
233 InsertReturnType DoInsert(const Key& key, ValuePtr value);
234
235 rcu::Variable<RawMap, RcuTraits> rcu_;
236};
237
238template <typename K, typename V, typename RcuMapTraits>
239template <typename ValuePtrType>
240struct RcuMap<K, V, RcuMapTraits>::InsertReturnTypeImpl {
241 ValuePtrType value;
242 bool inserted;
243};
244
245template <typename K, typename V, typename RcuMapTraits>
246typename RcuMap<K, V, RcuMapTraits>::ConstIterator RcuMap<K, V, RcuMapTraits>::begin() const {
247 auto ptr = rcu_.Read();
248 const auto iter = ptr->cbegin();
249 return typename RcuMap<K, V, RcuMapTraits>::ConstIterator(std::move(ptr), iter);
250}
251
252template <typename K, typename V, typename RcuMapTraits>
253typename RcuMap<K, V, RcuMapTraits>::ConstIterator RcuMap<K, V, RcuMapTraits>::end() const {
254 // End iterator must be empty, because otherwise begin and end calls will
255 // return iterators that point into different map snapshots.
256 return {};
257}
258
259template <typename K, typename V, typename RcuMapTraits>
260typename RcuMap<K, V, RcuMapTraits>::Iterator RcuMap<K, V, RcuMapTraits>::begin() {
261 auto ptr = rcu_.Read();
262 const auto iter = ptr->cbegin();
263 return {std::move(ptr), iter};
264}
265
266template <typename K, typename V, typename RcuMapTraits>
267typename RcuMap<K, V, RcuMapTraits>::Iterator RcuMap<K, V, RcuMapTraits>::end() {
268 // End iterator must be empty, because otherwise begin and end calls will
269 // return iterators that point into different map snapshots.
270 return {};
271}
272
273template <typename K, typename V, typename RcuMapTraits>
274size_t RcuMap<K, V, RcuMapTraits>::SizeApprox() const {
275 auto ptr = rcu_.Read();
276 return ptr->size();
277}
278
279template <typename K, typename V, typename RcuMapTraits>
280// Protects from assignment to map[key]
281// NOLINTNEXTLINE(readability-const-return-type)
282const typename RcuMap<K, V, RcuMapTraits>::ConstValuePtr RcuMap<K, V, RcuMapTraits>::operator[](const K& key) const {
283 if (auto value = Get(key)) {
284 return value;
285 }
286 throw MissingKeyException("Key ") << key << " is missing";
287}
288
289template <typename K, typename V, typename RcuMapTraits>
290// Protects from assignment to map[key]
291// NOLINTNEXTLINE(readability-const-return-type)
292const typename RcuMap<K, V, RcuMapTraits>::ConstValuePtr RcuMap<K, V, RcuMapTraits>::Get(const K& key) const {
293 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
294 return const_cast<RcuMap<K, V, RcuMapTraits>*>(this)->Get(key);
295}
296
297template <typename K, typename V, typename RcuMapTraits>
298// Protects from assignment to map[key]
299// NOLINTNEXTLINE(readability-const-return-type)
300const typename RcuMap<K, V, RcuMapTraits>::ValuePtr RcuMap<K, V, RcuMapTraits>::operator[](const K& key) {
301 auto value = Get(key);
302 if (!value) {
303 auto txn = rcu_.StartWrite();
304 auto insertion_result = txn->emplace(key, std::make_shared<V>());
305 value = insertion_result.first->second;
306 if (insertion_result.second) txn.Commit();
307 }
308 return value;
309}
310
311template <typename K, typename V, typename RcuMapTraits>
312typename RcuMap<K, V, RcuMapTraits>::InsertReturnType
313RcuMap<K, V, RcuMapTraits>::Insert(const K& key, typename RcuMap<K, V, RcuMapTraits>::ValuePtr value) {
314 InsertReturnType result{Get(key), false};
315 if (result.value) return result;
316
317 return DoInsert(key, std::move(value));
318}
319
320template <typename K, typename V, typename RcuMapTraits>
321template <typename... Args>
322typename RcuMap<K, V, RcuMapTraits>::InsertReturnType
323RcuMap<K, V, RcuMapTraits>::Emplace(const K& key, Args&&... args) {
324 InsertReturnType result{Get(key), false};
325 if (result.value) return result;
326
327 return DoInsert(key, std::make_shared<V>(std::forward<Args>(args)...));
328}
329
330template <typename K, typename V, typename RcuMapTraits>
331typename RcuMap<K, V, RcuMapTraits>::InsertReturnType
332RcuMap<K, V, RcuMapTraits>::DoInsert(const K& key, typename RcuMap<K, V, RcuMapTraits>::ValuePtr value) {
333 auto txn = rcu_.StartWrite();
334 auto insertion_result = txn->emplace(key, std::move(value));
335 InsertReturnType result{insertion_result.first->second, insertion_result.second};
336 if (result.inserted) txn.Commit();
337 return result;
338}
339
340template <typename K, typename V, typename RcuMapTraits>
341template <typename... Args>
342typename RcuMap<K, V, RcuMapTraits>::InsertReturnType
343RcuMap<K, V, RcuMapTraits>::TryEmplace(const K& key, Args&&... args) {
344 InsertReturnType result{Get(key), false};
345 if (!result.value) {
346 auto txn = rcu_.StartWrite();
347 auto insertion_result = txn->try_emplace(key, nullptr);
348 if (insertion_result.second) {
349 result.value = insertion_result.first->second = std::make_shared<V>(std::forward<Args>(args)...);
350 txn.Commit();
351 result.inserted = true;
352 } else {
353 result.value = insertion_result.first->second;
354 }
355 }
356 return result;
357}
358
359template <typename Key, typename Value, typename RcuMapTraits>
360template <typename RawKey>
361void RcuMap<Key, Value, RcuMapTraits>::InsertOrAssign(RawKey&& key, RcuMap::ValuePtr value) {
362 auto txn = rcu_.StartWrite();
363 txn->insert_or_assign(std::forward<RawKey>(key), std::move(value));
364 txn.Commit();
365}
366
367template <typename K, typename V, typename RcuMapTraits>
368// Protects from assignment to map[key]
369// NOLINTNEXTLINE(readability-const-return-type)
370const typename RcuMap<K, V, RcuMapTraits>::ValuePtr RcuMap<K, V, RcuMapTraits>::Get(const K& key) {
371 auto snapshot = rcu_.Read();
372 auto it = snapshot->find(key);
373 if (it == snapshot->end()) return {};
374 return it->second;
375}
376
377template <typename K, typename V, typename RcuMapTraits>
378bool RcuMap<K, V, RcuMapTraits>::Erase(const K& key) {
379 if (Get(key)) {
380 auto txn = rcu_.StartWrite();
381 if (txn->erase(key)) {
382 txn.Commit();
383 return true;
384 }
385 }
386 return false;
387}
388
389template <typename K, typename V, typename RcuMapTraits>
390typename RcuMap<K, V, RcuMapTraits>::ValuePtr RcuMap<K, V, RcuMapTraits>::Pop(const K& key) {
391 auto value = Get(key);
392 if (value) {
393 auto txn = rcu_.StartWrite();
394 if (txn->erase(key)) txn.Commit();
395 }
396 return value;
397}
398
399template <typename K, typename V, typename RcuMapTraits>
400void RcuMap<K, V, RcuMapTraits>::Clear() {
401 rcu_.Assign({});
402}
403
404template <typename K, typename V, typename RcuMapTraits>
405void RcuMap<K, V, RcuMapTraits>::Assign(RawMap new_map) {
406 rcu_.Assign(std::move(new_map));
407}
408
409template <typename K, typename V, typename RcuMapTraits>
410auto RcuMap<K, V, RcuMapTraits>::StartWrite() -> rcu::WritablePtr<RawMap, RcuTraits> {
411 return rcu_.StartWrite();
412}
413
414template <typename K, typename V, typename RcuMapTraits>
415typename RcuMap<K, V, RcuMapTraits>::Snapshot RcuMap<K, V, RcuMapTraits>::GetSnapshot() const {
416 return {begin(), end()};
417}
418
419template <typename Key, typename Value, typename IterValue, typename RcuMapTraits>
420RcuMapIterator<Key, Value, IterValue, RcuMapTraits>::RcuMapIterator(
421 ReadablePtr<MapType, RcuTraits>&& ptr,
422 typename MapType::const_iterator iter
423)
424 : ptr_(std::move(ptr)), it_(iter) {
425 UpdateCurrent();
426}
427
428template <typename Key, typename Value, typename IterValue, typename RcuMapTraits>
429auto RcuMapIterator<Key, Value, IterValue, RcuMapTraits>::operator++(int) -> RcuMapIterator {
430 RcuMapIterator tmp(*this);
431 ++*this;
432 return tmp;
433}
434
435template <typename Key, typename Value, typename IterValue, typename RcuMapTraits>
436auto RcuMapIterator<Key, Value, IterValue, RcuMapTraits>::operator++() -> RcuMapIterator& {
437 ++it_;
438 UpdateCurrent();
439 return *this;
440}
441
442template <typename Key, typename Value, typename IterValue, typename RcuMapTraits>
443auto RcuMapIterator<Key, Value, IterValue, RcuMapTraits>::operator*() const -> reference {
444 return current_;
445}
446
447template <typename Key, typename Value, typename IterValue, typename RcuMapTraits>
448auto RcuMapIterator<Key, Value, IterValue, RcuMapTraits>::operator->() const -> pointer {
449 return &current_;
450}
451
452template <typename Key, typename Value, typename IterValue, typename RcuMapTraits>
453bool RcuMapIterator<Key, Value, IterValue, RcuMapTraits>::operator==(const RcuMapIterator& rhs) const {
454 if (ptr_) {
455 if (rhs.ptr_) {
456 return it_ == rhs.it_;
457 } else {
458 return it_ == (*ptr_)->end();
459 }
460 } else {
461 return !rhs.ptr_ || rhs.it_ == (*rhs.ptr_)->end();
462 }
463}
464
465template <typename Key, typename Value, typename IterValue, typename RcuMapTraits>
466bool RcuMapIterator<Key, Value, IterValue, RcuMapTraits>::operator!=(const RcuMapIterator& rhs) const {
467 return !(*this == rhs);
468}
469
470template <typename Key, typename Value, typename IterValue, typename RcuMapTraits>
471void RcuMapIterator<Key, Value, IterValue, RcuMapTraits>::UpdateCurrent() {
472 if (it_ != (*ptr_)->end()) {
473 current_ = *it_;
474 }
475}
476
477} // namespace rcu
478
479USERVER_NAMESPACE_END