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
145 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 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.
225 rcu::WritablePtr<RawMap, RcuTraits> StartWrite();
226
227 /// @brief Returns a readonly copy of the map
228 /// @note Equivalent to `{begin(), end()}` construct, preferable
229 /// for long-running operations.
230 Snapshot GetSnapshot() const;
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) {
307 txn.Commit();
308 }
309 }
310 return value;
311}
312
313template <typename K, typename V, typename RcuMapTraits>
314typename RcuMap<K, V, RcuMapTraits>::InsertReturnType RcuMap<
315 K,
316 V,
317 RcuMapTraits>::Insert(const K& key, typename RcuMap<K, V, RcuMapTraits>::ValuePtr value) {
318 InsertReturnType result{Get(key), false};
319 if (result.value) {
320 return result;
321 }
322
323 return DoInsert(key, std::move(value));
324}
325
326template <typename K, typename V, typename RcuMapTraits>
327template <typename... Args>
328typename RcuMap<K, V, RcuMapTraits>::InsertReturnType RcuMap<
329 K,
330 V,
331 RcuMapTraits>::Emplace(const K& key, Args&&... args) {
332 InsertReturnType result{Get(key), false};
333 if (result.value) {
334 return result;
335 }
336
337 return DoInsert(key, std::make_shared<V>(std::forward<Args>(args)...));
338}
339
340template <typename K, typename V, typename RcuMapTraits>
341typename RcuMap<K, V, RcuMapTraits>::InsertReturnType RcuMap<
342 K,
343 V,
344 RcuMapTraits>::DoInsert(const K& key, typename RcuMap<K, V, RcuMapTraits>::ValuePtr value) {
345 auto txn = rcu_.StartWrite();
346 auto insertion_result = txn->emplace(key, std::move(value));
347 InsertReturnType result{insertion_result.first->second, insertion_result.second};
348 if (result.inserted) {
349 txn.Commit();
350 }
351 return result;
352}
353
354template <typename K, typename V, typename RcuMapTraits>
355template <typename... Args>
356typename RcuMap<K, V, RcuMapTraits>::InsertReturnType RcuMap<
357 K,
358 V,
359 RcuMapTraits>::TryEmplace(const K& key, Args&&... args) {
360 InsertReturnType result{Get(key), false};
361 if (!result.value) {
362 auto txn = rcu_.StartWrite();
363 auto insertion_result = txn->try_emplace(key, nullptr);
364 if (insertion_result.second) {
365 result.value = insertion_result.first->second = std::make_shared<V>(std::forward<Args>(args)...);
366 txn.Commit();
367 result.inserted = true;
368 } else {
369 result.value = insertion_result.first->second;
370 }
371 }
372 return result;
373}
374
375template <typename Key, typename Value, typename RcuMapTraits>
376template <typename RawKey>
377void RcuMap<Key, Value, RcuMapTraits>::InsertOrAssign(RawKey&& key, RcuMap::ValuePtr value) {
378 auto txn = rcu_.StartWrite();
379 txn->insert_or_assign(std::forward<RawKey>(key), std::move(value));
380 txn.Commit();
381}
382
383template <typename K, typename V, typename RcuMapTraits>
384// Protects from assignment to map[key]
385// NOLINTNEXTLINE(readability-const-return-type)
386const typename RcuMap<K, V, RcuMapTraits>::ValuePtr RcuMap<K, V, RcuMapTraits>::Get(const K& key) {
387 auto snapshot = rcu_.Read();
388 auto it = snapshot->find(key);
389 if (it == snapshot->end()) {
390 return {};
391 }
392 return it->second;
393}
394
395template <typename K, typename V, typename RcuMapTraits>
396bool RcuMap<K, V, RcuMapTraits>::Erase(const K& key) {
397 if (Get(key)) {
398 auto txn = rcu_.StartWrite();
399 if (txn->erase(key)) {
400 txn.Commit();
401 return true;
402 }
403 }
404 return false;
405}
406
407template <typename K, typename V, typename RcuMapTraits>
408typename RcuMap<K, V, RcuMapTraits>::ValuePtr RcuMap<K, V, RcuMapTraits>::Pop(const K& key) {
409 auto value = Get(key);
410 if (value) {
411 auto txn = rcu_.StartWrite();
412 if (txn->erase(key)) {
413 txn.Commit();
414 }
415 }
416 return value;
417}
418
419template <typename K, typename V, typename RcuMapTraits>
420void RcuMap<K, V, RcuMapTraits>::Clear() {
421 rcu_.Assign({});
422}
423
424template <typename K, typename V, typename RcuMapTraits>
425void RcuMap<K, V, RcuMapTraits>::Assign(RawMap new_map) {
426 rcu_.Assign(std::move(new_map));
427}
428
429template <typename K, typename V, typename RcuMapTraits>
430auto RcuMap<K, V, RcuMapTraits>::StartWrite() -> rcu::WritablePtr<RawMap, RcuTraits> {
431 return rcu_.StartWrite();
432}
433
434template <typename K, typename V, typename RcuMapTraits>
435typename RcuMap<K, V, RcuMapTraits>::Snapshot RcuMap<K, V, RcuMapTraits>::GetSnapshot() const {
436 return {begin(), end()};
437}
438
439template <typename Key, typename Value, typename IterValue, typename RcuMapTraits>
440RcuMapIterator<
441 Key,
442 Value,
443 IterValue,
444 RcuMapTraits>::RcuMapIterator(ReadablePtr<MapType, RcuTraits>&& ptr, typename MapType::const_iterator iter)
445 : ptr_(std::move(ptr)),
446 it_(iter)
447{
448 UpdateCurrent();
449}
450
451template <typename Key, typename Value, typename IterValue, typename RcuMapTraits>
452auto RcuMapIterator<Key, Value, IterValue, RcuMapTraits>::operator++(int) -> RcuMapIterator {
453 RcuMapIterator tmp(*this);
454 ++*this;
455 return tmp;
456}
457
458template <typename Key, typename Value, typename IterValue, typename RcuMapTraits>
459auto RcuMapIterator<Key, Value, IterValue, RcuMapTraits>::operator++() -> RcuMapIterator& {
460 ++it_;
461 UpdateCurrent();
462 return *this;
463}
464
465template <typename Key, typename Value, typename IterValue, typename RcuMapTraits>
466auto RcuMapIterator<Key, Value, IterValue, RcuMapTraits>::operator*() const -> reference {
467 return current_;
468}
469
470template <typename Key, typename Value, typename IterValue, typename RcuMapTraits>
471auto RcuMapIterator<Key, Value, IterValue, RcuMapTraits>::operator->() const -> pointer {
472 return &current_;
473}
474
475template <typename Key, typename Value, typename IterValue, typename RcuMapTraits>
476bool RcuMapIterator<Key, Value, IterValue, RcuMapTraits>::operator==(const RcuMapIterator& rhs) const {
477 if (ptr_) {
478 if (rhs.ptr_) {
479 return it_ == rhs.it_;
480 } else {
481 return it_ == (*ptr_)->end();
482 }
483 } else {
484 return !rhs.ptr_ || rhs.it_ == (*rhs.ptr_)->end();
485 }
486}
487
488template <typename Key, typename Value, typename IterValue, typename RcuMapTraits>
489bool RcuMapIterator<Key, Value, IterValue, RcuMapTraits>::operator!=(const RcuMapIterator& rhs) const {
490 return !(*this == rhs);
491}
492
493template <typename Key, typename Value, typename IterValue, typename RcuMapTraits>
494void RcuMapIterator<Key, Value, IterValue, RcuMapTraits>::UpdateCurrent() {
495 if (it_ != (*ptr_)->end()) {
496 current_ = *it_;
497 }
498}
499
500} // namespace rcu
501
502USERVER_NAMESPACE_END