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/// @warning Inserting N elements one by one requires O(N^2) operations because each insertion copies the whole map.
108///
109/// Writer access is protected by `RcuMapTraits::MutexType`. The default @ref rcu::DefaultRcuMapTraits selects
110/// @ref engine::Mutex. With it and other regular mutex types, concurrent keyset changes are serialized for the whole
111/// map. Read-modify-write operations first acquire the mutex and then copy the latest committed map snapshot, so they
112/// include changes made by preceding writers. The mutex is held until the new snapshot is committed or discarded,
113/// while readers continue using an older snapshot without waiting. This guarantee concerns copying the map snapshot;
114/// preparation of a candidate Value is documented by each insertion method separately.
115///
116/// @note No synchronization is provided for value access, it must be implemented by Value when necessary.
117///
118/// ## Example usage:
119///
120/// @snippet core/src/rcu/rcu_map_test.cpp Sample rcu::RcuMap usage
121///
122/// @see @ref scripts/docs/en/userver/synchronization.md
123template <typename Key, typename Value, typename RcuMapTraits>
124class RcuMap final {
125 using RcuTraits = typename impl::RcuTraitsFromRcuMapTraits<RcuMapTraits>;
126
127public:
128 static_assert(!std::is_reference_v<Key>);
129 static_assert(!std::is_reference_v<Value>);
130 static_assert(!std::is_const_v<Key>);
131
132 template <typename ValuePtrType>
134
135 using Hash = typename RcuMapTraits::Hash;
136 using KeyEqual = typename RcuMapTraits::KeyEqual;
137 using MutexType = typename RcuMapTraits::MutexType;
138 using ValuePtr = std::shared_ptr<Value>;
139 using Iterator = RcuMapIterator<Key, Value, Value, RcuMapTraits>;
140 using ConstValuePtr = std::shared_ptr<const Value>;
141 using ConstIterator = RcuMapIterator<Key, Value, const Value, RcuMapTraits>;
142 using RawMap = std::unordered_map<Key, ValuePtr, Hash, KeyEqual>;
143 using Snapshot = std::unordered_map<Key, ConstValuePtr, Hash, KeyEqual>;
144 using InsertReturnType = InsertReturnTypeImpl<ValuePtr>;
145
146 RcuMap() = default;
147
148 RcuMap(const RcuMap&) = delete;
149 RcuMap(RcuMap&&) = delete;
150 RcuMap& operator=(const RcuMap&) = delete;
151 RcuMap& operator=(RcuMap&&) = delete;
152
153 /// Returns an estimated size of the map at some point in time
154 std::size_t SizeApprox() const;
155
156 /// @name Iteration support
157 /// @details Keyset is fixed at the start of the iteration and is not affected
158 /// by concurrent changes.
159 /// @{
160 ConstIterator begin() const;
161 ConstIterator end() const;
162 Iterator begin();
163 Iterator end();
164 /// @}
165
166 /// @brief Returns a readonly value pointer by its key if exists
167 /// @throws MissingKeyException if the key is not present
168 const utils::NotNull<ConstValuePtr> operator[](const Key&) const;
169
170 /// @brief Returns a modifiable value pointer by key if exists or default-creates one
171 /// @note Copies the whole map if the key doesn't exist.
172 /// @note The decisive presence check and insertion are serialized with other writers. Concurrent calls for the
173 /// same missing key don't overwrite each other and return pointers to the same published value, unless another
174 /// writer removes or replaces the key in between.
175 const utils::NotNull<ValuePtr> operator[](const Key&);
176
177 /// @brief Inserts a new element into the container if there is no element with the key in the container.
178 /// Returns a pair consisting of a pointer to the inserted element, or the
179 /// already-existing element if no insertion happened, and a bool denoting whether the insertion took place.
180 /// @note Copies the whole map if the key doesn't exist.
181 /// @note The supplied Value pointer may be discarded if another writer inserts an equivalent key before this
182 /// operation acquires the writer mutex.
183 /// @note The decisive presence check and insertion are serialized with other writers. Concurrent calls for
184 /// equivalent keys don't overwrite each other: at most one can return `inserted == true` while the key remains
185 /// present.
186 InsertReturnType Insert(const Key& key, ValuePtr value);
187
188 /// @brief Inserts a new element into the container constructed in-place with
189 /// the given args if there is no element with the key in the container.
190 /// Returns a pair consisting of a pointer to the inserted element, or the
191 /// already-existing element if no insertion happened, and a bool denoting whether the insertion took place.
192 /// @note Copies the whole map if the key doesn't exist.
193 /// @note The Value candidate is constructed before acquiring the writer mutex and may be discarded if another
194 /// writer inserts an equivalent key first. @ref rcu::RcuMap::TryEmplace avoids this extra construction.
195 /// @note The decisive presence check and insertion are serialized with other writers. Concurrent calls for
196 /// equivalent keys don't overwrite each other: at most one can return `inserted == true` while the key remains
197 /// present.
198 template <typename... Args>
199 InsertReturnType Emplace(const Key& key, Args&&... args);
200
201 /// @brief If a key equivalent to `key` already exists in the container, does nothing. Otherwise, constructs a
202 /// Value from the given args and inserts it into the map.
203 /// Returns a pair consisting of a pointer to the inserted element, or the
204 /// already-existing element if no insertion happened, and a bool denoting whether the insertion took place.
205 /// @note After acquiring the writer mutex, the final presence check uses the latest committed map snapshot. Value
206 /// construction happens only if this check succeeds, although function arguments are evaluated before the call.
207 /// For concurrent calls with equivalent keys, at most one call can commit the insertion and return
208 /// `inserted == true`; once it commits, the other calls return its value with `inserted == false`, unless another
209 /// writer removes or replaces the key.
210 template <typename... Args>
211 InsertReturnType TryEmplace(const Key& key, Args&&... args);
212
213 /// @brief If a key equivalent to `key` already exists in the container,
214 /// replaces the associated value. Otherwise, inserts a new pair into the map.
215 /// @note Serialized with other write operations by the writer mutex. Concurrent assignments are applied one by
216 /// one; the last committed assignment determines the value.
217 template <typename RawKey>
218 void InsertOrAssign(RawKey&& key, ValuePtr value);
219
220 /// @brief Returns a readonly value pointer by its key; nullptr (a default constructed ConstValuePtr) if no such key
221 /// Supports heterogeneous lookup when RcuMapTraits provide transparent Hash and KeyEqual.
222 template <typename CompatibleKey = Key>
223 const ConstValuePtr Get(const CompatibleKey& key) const;
224
225 /// @brief Returns a modifiable value pointer by key; nullptr (a default constructed ValuePtr) if no such key
226 template <typename CompatibleKey = Key>
227 const ValuePtr Get(const CompatibleKey& key);
228
229 /// @brief Removes a key from the map
230 /// @returns whether the key was present
231 /// @note Copies the whole map, might be slow for large maps.
232 bool Erase(const Key&);
233
234 /// @brief Removes a key from the map returning its value
235 /// @returns a value if the key was present, empty pointer otherwise
236 /// @note Copies the whole map, might be slow for large maps.
237 ValuePtr Pop(const Key&);
238
239 /// Resets the map to an empty state
240 void Clear();
241
242 /// Replace current data by data from `new_map`.
243 void Assign(RawMap new_map);
244
245 /// @brief Starts a transaction, used to perform a series of arbitrary changes
246 /// to the map.
247 /// @details Acquires the same writer mutex as all other map writes, then copies the latest committed map. The
248 /// returned transaction owns the mutex until `Commit` or destruction, so concurrent write transactions proceed
249 /// one by one. Readers don't wait for the transaction. Don't forget to `Commit` to apply the changes.
250 rcu::WritablePtr<RawMap, RcuTraits> StartWrite();
251
252 /// @brief Returns a readonly copy of the map
253 /// @note Equivalent to `{begin(), end()}` construct, preferable
254 /// for long-running operations.
255 Snapshot GetSnapshot() const;
256
257private:
258 InsertReturnType DoInsert(const Key& key, ValuePtr value);
259
260 rcu::Variable<RawMap, RcuTraits> rcu_;
261};
262
263template <typename K, typename V, typename RcuMapTraits>
264template <typename ValuePtrType>
265struct RcuMap<K, V, RcuMapTraits>::InsertReturnTypeImpl {
266 ValuePtrType value;
267 bool inserted;
268};
269
270template <typename K, typename V, typename RcuMapTraits>
271typename RcuMap<K, V, RcuMapTraits>::ConstIterator RcuMap<K, V, RcuMapTraits>::begin() const {
272 auto ptr = rcu_.Read();
273 const auto iter = ptr->cbegin();
274 return typename RcuMap<K, V, RcuMapTraits>::ConstIterator(std::move(ptr), iter);
275}
276
277template <typename K, typename V, typename RcuMapTraits>
278typename RcuMap<K, V, RcuMapTraits>::ConstIterator RcuMap<K, V, RcuMapTraits>::end() const {
279 // End iterator must be empty, because otherwise begin and end calls will
280 // return iterators that point into different map snapshots.
281 return {};
282}
283
284template <typename K, typename V, typename RcuMapTraits>
285typename RcuMap<K, V, RcuMapTraits>::Iterator RcuMap<K, V, RcuMapTraits>::begin() {
286 auto ptr = rcu_.Read();
287 const auto iter = ptr->cbegin();
288 return {std::move(ptr), iter};
289}
290
291template <typename K, typename V, typename RcuMapTraits>
292typename RcuMap<K, V, RcuMapTraits>::Iterator RcuMap<K, V, RcuMapTraits>::end() {
293 // End iterator must be empty, because otherwise begin and end calls will
294 // return iterators that point into different map snapshots.
295 return {};
296}
297
298template <typename K, typename V, typename RcuMapTraits>
299std::size_t RcuMap<K, V, RcuMapTraits>::SizeApprox() const {
300 auto ptr = rcu_.Read();
301 return ptr->size();
302}
303
304template <typename K, typename V, typename RcuMapTraits>
305// Protects from assignment to map[key]
306// NOLINTNEXTLINE(readability-const-return-type)
307const utils::NotNull<typename RcuMap<K, V, RcuMapTraits>::ConstValuePtr> RcuMap<
308 K,
309 V,
310 RcuMapTraits>::operator[](const K& key) const {
311 if (auto value = Get(key)) {
312 return utils::NotNull{value};
313 }
314 throw MissingKeyException("Key ") << key << " is missing";
315}
316
317template <typename K, typename V, typename RcuMapTraits>
318template <typename CompatibleKey>
319// Protects from assignment to map[key]
320// NOLINTNEXTLINE(readability-const-return-type)
321const typename RcuMap<K, V, RcuMapTraits>::ConstValuePtr RcuMap<K, V, RcuMapTraits>::Get(const CompatibleKey& key
322) const {
323 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
324 return const_cast<RcuMap<K, V, RcuMapTraits>*>(this)->Get(key);
325}
326
327template <typename K, typename V, typename RcuMapTraits>
328template <typename CompatibleKey>
329// Protects from assignment to map[key]
330// NOLINTNEXTLINE(readability-const-return-type)
331const typename RcuMap<K, V, RcuMapTraits>::ValuePtr RcuMap<K, V, RcuMapTraits>::Get(const CompatibleKey& key) {
332 auto snapshot = rcu_.Read();
333 auto it = snapshot->find(key);
334 if (it == snapshot->end()) {
335 return {};
336 }
337 return it->second;
338}
339
340template <typename K, typename V, typename RcuMapTraits>
341// Protects from assignment to map[key]
342// NOLINTNEXTLINE(readability-const-return-type)
343const utils::NotNull<typename RcuMap<K, V, RcuMapTraits>::ValuePtr> RcuMap<K, V, RcuMapTraits>::operator[](const K& key
344) {
345 auto value = Get(key);
346 if (!value) {
347 auto ptr = std::make_shared<V>();
348 auto txn = rcu_.StartWrite();
349 auto insertion_result = txn->emplace(key, std::move(ptr));
350 value = insertion_result.first->second;
351 if (insertion_result.second) {
352 txn.Commit();
353 }
354 }
355 return utils::NotNull{value};
356}
357
358template <typename K, typename V, typename RcuMapTraits>
359typename RcuMap<K, V, RcuMapTraits>::InsertReturnType RcuMap<
360 K,
361 V,
362 RcuMapTraits>::Insert(const K& key, typename RcuMap<K, V, RcuMapTraits>::ValuePtr value) {
363 InsertReturnType result{.value = Get(key), .inserted = false};
364 if (result.value) {
365 return result;
366 }
367
368 return DoInsert(key, std::move(value));
369}
370
371template <typename K, typename V, typename RcuMapTraits>
372template <typename... Args>
373typename RcuMap<K, V, RcuMapTraits>::InsertReturnType RcuMap<
374 K,
375 V,
376 RcuMapTraits>::Emplace(const K& key, Args&&... args) {
377 InsertReturnType result{.value = Get(key), .inserted = false};
378 if (result.value) {
379 return result;
380 }
381
382 return DoInsert(key, std::make_shared<V>(std::forward<Args>(args)...));
383}
384
385template <typename K, typename V, typename RcuMapTraits>
386typename RcuMap<K, V, RcuMapTraits>::InsertReturnType RcuMap<
387 K,
388 V,
389 RcuMapTraits>::DoInsert(const K& key, typename RcuMap<K, V, RcuMapTraits>::ValuePtr value) {
390 auto txn = rcu_.StartWrite();
391 auto insertion_result = txn->emplace(key, std::move(value));
392 InsertReturnType result{.value = insertion_result.first->second, .inserted = insertion_result.second};
393 if (result.inserted) {
394 txn.Commit();
395 }
396 return result;
397}
398
399template <typename K, typename V, typename RcuMapTraits>
400template <typename... Args>
401typename RcuMap<K, V, RcuMapTraits>::InsertReturnType RcuMap<
402 K,
403 V,
404 RcuMapTraits>::TryEmplace(const K& key, Args&&... args) {
405 InsertReturnType result{.value = Get(key), .inserted = false};
406 if (!result.value) {
407 auto txn = rcu_.StartWrite();
408 auto insertion_result = txn->try_emplace(key, nullptr);
409 if (insertion_result.second) {
410 result.value = insertion_result.first->second = std::make_shared<V>(std::forward<Args>(args)...);
411 txn.Commit();
412 result.inserted = true;
413 } else {
414 result.value = insertion_result.first->second;
415 }
416 }
417 return result;
418}
419
420template <typename Key, typename Value, typename RcuMapTraits>
421template <typename RawKey>
422void RcuMap<Key, Value, RcuMapTraits>::InsertOrAssign(RawKey&& key, RcuMap::ValuePtr value) {
423 auto txn = rcu_.StartWrite();
424 txn->insert_or_assign(std::forward<RawKey>(key), std::move(value));
425 txn.Commit();
426}
427
428template <typename K, typename V, typename RcuMapTraits>
429bool RcuMap<K, V, RcuMapTraits>::Erase(const K& key) {
430 if (Get(key)) {
431 auto txn = rcu_.StartWrite();
432 if (txn->erase(key)) {
433 txn.Commit();
434 return true;
435 }
436 }
437 return false;
438}
439
440template <typename K, typename V, typename RcuMapTraits>
441typename RcuMap<K, V, RcuMapTraits>::ValuePtr RcuMap<K, V, RcuMapTraits>::Pop(const K& key) {
442 auto value = Get(key);
443 if (value) {
444 auto txn = rcu_.StartWrite();
445 if (txn->erase(key)) {
446 txn.Commit();
447 }
448 }
449 return value;
450}
451
452template <typename K, typename V, typename RcuMapTraits>
453void RcuMap<K, V, RcuMapTraits>::Clear() {
454 rcu_.Assign({});
455}
456
457template <typename K, typename V, typename RcuMapTraits>
458void RcuMap<K, V, RcuMapTraits>::Assign(RawMap new_map) {
459 rcu_.Assign(std::move(new_map));
460}
461
462template <typename K, typename V, typename RcuMapTraits>
463auto RcuMap<K, V, RcuMapTraits>::StartWrite() -> rcu::WritablePtr<RawMap, RcuTraits> {
464 return rcu_.StartWrite();
465}
466
467template <typename K, typename V, typename RcuMapTraits>
468typename RcuMap<K, V, RcuMapTraits>::Snapshot RcuMap<K, V, RcuMapTraits>::GetSnapshot() const {
469 return {begin(), end()};
470}
471
472template <typename Key, typename Value, typename IterValue, typename RcuMapTraits>
473RcuMapIterator<
474 Key,
475 Value,
476 IterValue,
477 RcuMapTraits>::RcuMapIterator(ReadablePtr<MapType, RcuTraits>&& ptr, typename MapType::const_iterator iter)
478 : ptr_(std::move(ptr)),
479 it_(iter)
480{
481 UpdateCurrent();
482}
483
484template <typename Key, typename Value, typename IterValue, typename RcuMapTraits>
485auto RcuMapIterator<Key, Value, IterValue, RcuMapTraits>::operator++(int) -> RcuMapIterator {
486 RcuMapIterator tmp(*this);
487 ++*this;
488 return tmp;
489}
490
491template <typename Key, typename Value, typename IterValue, typename RcuMapTraits>
492auto RcuMapIterator<Key, Value, IterValue, RcuMapTraits>::operator++() -> RcuMapIterator& {
493 ++it_;
494 UpdateCurrent();
495 return *this;
496}
497
498template <typename Key, typename Value, typename IterValue, typename RcuMapTraits>
499auto RcuMapIterator<Key, Value, IterValue, RcuMapTraits>::operator*() const -> reference {
500 return current_;
501}
502
503template <typename Key, typename Value, typename IterValue, typename RcuMapTraits>
504auto RcuMapIterator<Key, Value, IterValue, RcuMapTraits>::operator->() const -> pointer {
505 return &current_;
506}
507
508template <typename Key, typename Value, typename IterValue, typename RcuMapTraits>
509bool RcuMapIterator<Key, Value, IterValue, RcuMapTraits>::operator==(const RcuMapIterator& rhs) const {
510 if (ptr_) {
511 if (rhs.ptr_) {
512 return it_ == rhs.it_;
513 } else {
514 return it_ == (*ptr_)->end();
515 }
516 } else {
517 return !rhs.ptr_ || rhs.it_ == (*rhs.ptr_)->end();
518 }
519}
520
521template <typename Key, typename Value, typename IterValue, typename RcuMapTraits>
522bool RcuMapIterator<Key, Value, IterValue, RcuMapTraits>::operator!=(const RcuMapIterator& rhs) const {
523 return !(*this == rhs);
524}
525
526template <typename Key, typename Value, typename IterValue, typename RcuMapTraits>
527void RcuMapIterator<Key, Value, IterValue, RcuMapTraits>::UpdateCurrent() {
528 if (it_ != (*ptr_)->end()) {
529 current_ = *it_;
530 }
531}
532
533} // namespace rcu
534
535USERVER_NAMESPACE_END