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