userver: userver/concurrent/mutex_set.hpp Source File
Loading...
Searching...
No Matches
mutex_set.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/concurrent/mutex_set.hpp
4/// @brief @copybrief concurrent::MutexSet
5
6#include <chrono>
7#include <functional>
8#include <string>
9#include <unordered_set>
10#include <utility>
11
12#include <userver/engine/condition_variable.hpp>
13#include <userver/engine/mutex.hpp>
14#include <userver/engine/task/cancel.hpp>
15#include <userver/utils/cached_hash.hpp>
16#include <userver/utils/fixed_array.hpp>
17
18USERVER_NAMESPACE_BEGIN
19
20namespace concurrent {
21
22namespace impl {
23
24template <typename T, typename Equal>
25struct MutexDatum final {
26 explicit MutexDatum(size_t way_size, const Equal& equal = Equal{}) : set(way_size, {}, equal) {}
27
28 ~MutexDatum() { UASSERT_MSG(set.empty(), "MutexDatum is destroyed while someone is holding the lock"); }
29
30 engine::Mutex mutex;
31 engine::ConditionVariable cv;
32 std::unordered_set<T, std::hash<T>, Equal> set;
33};
34
35} // namespace impl
36
37/// Mutex-like object associated with the key of a MutexSet. It provides the
38/// same interface as engine::Mutex, you may use it with std::unique_lock,
39/// std::lock_guard, etc.
40/// @note different ItemMutex'es of the same MutexSet obtained with the same
41/// argument to GetMutexForKey() share the same critical section. IOW, if
42/// the first mutex is locked, the second one will block until the first
43/// mutex is unlocked.
44/// @note can be used only from coroutines.
45template <typename Key, typename Equal>
46class ItemMutex final {
47public:
48 using HashAndKey = utils::CachedHash<Key>;
49
50 using MutexDatum = impl::MutexDatum<HashAndKey, utils::CachedHashKeyEqual<Equal>>;
51
52 ItemMutex(MutexDatum& md, HashAndKey&& key);
53
54 void lock();
55
56 void unlock();
57
58 bool try_lock();
59
60 template <typename Rep, typename Period>
61 bool try_lock_for(std::chrono::duration<Rep, Period>);
62
63 template <typename Clock, typename Duration>
64 bool try_lock_until(std::chrono::time_point<Clock, Duration>);
65
66private:
67 bool TryFinishLocking();
68
69 MutexDatum& md_;
70 const HashAndKey key_;
71};
72
73/// @ingroup userver_concurrency userver_containers
74///
75/// @brief A dynamic set of mutexes
76///
77/// It can be used for separate critical sections for
78/// multiple keys when the key set is not known at compile time and may change
79/// in runtime.
80///
81/// Example:
82/// @snippet src/concurrent/mutex_set_test.cpp Sample mutex set usage
83template <typename Key = std::string, typename Hash = std::hash<Key>, typename Equal = std::equal_to<Key>>
84class MutexSet final : Hash {
85public:
86 explicit MutexSet(size_t ways = 1, size_t way_size = 1, const Hash& hash = Hash{}, const Equal& equal = Equal{});
87
88 /// Get the mutex-like object for a key. Coroutine-safe.
89 /// @note the returned object holds a reference to MutexSet, so make sure
90 /// that MutexSet is alive while you're working with ItemMutex.
91 ItemMutex<Key, Equal> GetMutexForKey(Key key);
92
93private:
94 using MutexDatum = typename ItemMutex<Key, Equal>::MutexDatum;
95 utils::FixedArray<MutexDatum> mutex_data_;
96};
97
98template <typename Key, typename Hash, typename Equal>
99MutexSet<Key, Hash, Equal>::MutexSet(size_t ways, size_t way_size, const Hash& hash, const Equal& equal)
100 : Hash(hash), mutex_data_(ways, way_size, utils::CachedHashKeyEqual<Equal>{equal}) {}
101
102template <typename Key, typename Hash, typename Equal>
103ItemMutex<Key, Equal> MutexSet<Key, Hash, Equal>::GetMutexForKey(Key key) {
104 const auto hash_value = Hash::operator()(key);
105 const auto size = mutex_data_.size();
106
107 // Compilers optimize a % b and a / b to a single div operation
108 const auto way = hash_value % size;
109 const auto new_hash = hash_value / size;
110
111 return ItemMutex<Key, Equal>(mutex_data_[way], {new_hash, std::move(key)});
112}
113
114template <typename Key, typename Equal>
115ItemMutex<Key, Equal>::ItemMutex(MutexDatum& md, HashAndKey&& key) : md_(md), key_(std::move(key)) {}
116
117template <typename Key, typename Equal>
118void ItemMutex<Key, Equal>::lock() {
119 engine::TaskCancellationBlocker blocker;
120 std::unique_lock<engine::Mutex> lock(md_.mutex);
121
122 [[maybe_unused]] auto is_locked = md_.cv.Wait(lock, [this] { return TryFinishLocking(); });
123 UASSERT(is_locked);
124}
125
126template <typename Key, typename Equal>
127void ItemMutex<Key, Equal>::unlock() {
128 std::unique_lock lock(md_.mutex);
129
130 // Forcing the destructor of the node to run outside of the critical section
131 [[maybe_unused]] auto node = md_.set.extract(key_);
132
133 // We must wakeup all the waiters, because otherwise we can wake up the wrong
134 // one and loose the wakeup:
135 //
136 // Task #1 waits for mutex A, task #2 waits for mutex B
137 // Task #3
138 // * unlocks mutex A
139 // * does NotifyOne
140 // * wakeups thread #2
141 // * mutex B is still locked, task #2 goes to sleep
142 // * we lost the wakeup :(
143 md_.cv.NotifyAll();
144
145 lock.unlock();
146
147 // Destructor of node is run here
148}
149
150template <typename Key, typename Equal>
151bool ItemMutex<Key, Equal>::try_lock() {
152 std::unique_lock<engine::Mutex> lock(md_.mutex);
153 return TryFinishLocking();
154}
155
156template <typename Key, typename Equal>
157template <typename Rep, typename Period>
158bool ItemMutex<Key, Equal>::try_lock_for(std::chrono::duration<Rep, Period> duration) {
159 std::unique_lock<engine::Mutex> lock(md_.mutex);
160 return md_.cv.WaitFor(lock, duration, [this] { return TryFinishLocking(); });
161}
162
163template <typename Key, typename Equal>
164template <typename Clock, typename Duration>
165bool ItemMutex<Key, Equal>::try_lock_until(std::chrono::time_point<Clock, Duration> time_point) {
166 std::unique_lock<engine::Mutex> lock(md_.mutex);
167 return md_.cv.WaitUntil(lock, time_point, [this] { return TryFinishLocking(); });
168}
169
170template <typename Key, typename Equal>
171bool ItemMutex<Key, Equal>::TryFinishLocking() {
172 return md_.set.insert(key_).second;
173}
174
175} // namespace concurrent
176
177USERVER_NAMESPACE_END