userver: userver/concurrent/variable.hpp Source File
Loading...
Searching...
No Matches
variable.hpp
1#pragma once
2
3#include <cstdlib>
4#include <mutex>
5#include <optional>
6#include <shared_mutex> // for shared_lock
7
8#include <userver/compiler/impl/lifetime.hpp>
9#include <userver/engine/mutex.hpp>
10
11USERVER_NAMESPACE_BEGIN
12
13/// Locking stuff
14namespace concurrent {
15
16/// Proxy class for locked access to data protected with locking::SharedLock<T>
17template <typename Lock, typename Data>
18class LockedPtr final {
19public:
20 using Mutex = typename Lock::mutex_type;
21
22 LockedPtr(Mutex& mutex, Data& data)
23 : lock_(mutex),
24 data_(data)
25 {}
26 LockedPtr(Lock&& lock, Data& data)
27 : lock_(std::move(lock)),
28 data_(data)
29 {}
30
31 Data& operator*() & USERVER_IMPL_LIFETIME_BOUND { return data_; }
32 const Data& operator*() const& USERVER_IMPL_LIFETIME_BOUND { return data_; }
33
34 /// Don't use *tmp for temporary value, store it to variable.
35 Data& operator*() && { return *GetOnRvalue(); }
36
37 Data* operator->() & USERVER_IMPL_LIFETIME_BOUND { return &data_; }
38 const Data* operator->() const& USERVER_IMPL_LIFETIME_BOUND { return &data_; }
39
40 /// Don't use tmp-> for temporary value, store it to variable.
41 Data* operator->() && { return GetOnRvalue(); }
42
43 /// For node-based containers, e.g. `std::unordered_map`, where container access needs to be locked,
44 /// but after that the reference is stable and does not need the lock anymore.
45 Data& GetUnsafeForStableSubobject() & { return data_; }
46 const Data& GetUnsafeForStableSubobject() const& { return data_; }
47
48 /// Don't use *tmp for temporary value, store it to variable.
49 Data& GetUnsafeForStableSubobject() && { return *GetOnRvalue(); }
50
51 Lock& GetLock() USERVER_IMPL_LIFETIME_BOUND { return lock_; }
52
53private:
54 const Data* GetOnRvalue() {
55 static_assert(!sizeof(Data), "Don't use temporary LockedPtr, store it to a variable");
56 std::abort();
57 }
58
59 Lock lock_;
60 Data& data_;
61};
62
63/// @ingroup userver_concurrency userver_containers
64///
65/// Container for shared data protected with a mutex of any type
66/// (mutex, shared mutex, etc.).
67/// ## Example usage:
68///
69/// @snippet concurrent/variable_test.cpp Sample concurrent::Variable usage
70///
71/// @see @ref scripts/docs/en/userver/synchronization.md
72template <typename Data, typename Mutex = engine::Mutex>
73class Variable final {
74public:
75 template <typename... Arg>
76 Variable(Arg&&... arg)
77 : data_(std::forward<Arg>(arg)...)
78 {}
79
80 LockedPtr<std::unique_lock<Mutex>, Data> UniqueLock() { return {mutex_, data_}; }
81
82 LockedPtr<std::unique_lock<Mutex>, const Data> UniqueLock() const { return {mutex_, data_}; }
83
84 std::optional<LockedPtr<std::unique_lock<Mutex>, const Data>> UniqueLock(std::try_to_lock_t) const {
85 return DoUniqueLock(*this, std::try_to_lock);
86 }
87
88 std::optional<LockedPtr<std::unique_lock<Mutex>, Data>> UniqueLock(std::try_to_lock_t) {
89 return DoUniqueLock(*this, std::try_to_lock);
90 }
91
92 std::optional<LockedPtr<std::unique_lock<Mutex>, const Data>> UniqueLock(std::chrono::milliseconds try_duration
93 ) const {
94 return DoUniqueLock(*this, try_duration);
95 }
96
97 std::optional<LockedPtr<std::unique_lock<Mutex>, Data>> UniqueLock(std::chrono::milliseconds try_duration) {
98 return DoUniqueLock(*this, try_duration);
99 }
100
101 LockedPtr<std::shared_lock<Mutex>, const Data> SharedLock() const { return {mutex_, data_}; }
102
103 /// Useful for grabbing a reference to an object in a node-based container,
104 /// e.g. `std::unordered_map`. Values must support concurrent modification.
105 LockedPtr<std::shared_lock<Mutex>, Data> SharedMutableLockUnsafe() { return {mutex_, data_}; }
106
107 LockedPtr<std::lock_guard<Mutex>, Data> Lock() { return {mutex_, data_}; }
108
109 LockedPtr<std::lock_guard<Mutex>, const Data> Lock() const { return {mutex_, data_}; }
110
111 /// Get raw mutex. Use with caution. For simple use cases call Lock(),
112 /// UniqueLock(), SharedLock() instead.
113 Mutex& GetMutexUnsafe() const { return mutex_; }
114
115 /// Get raw data. Use with extreme caution, only for cases where it is
116 /// impossible to access data with safe methods (e.g. std::scoped_lock with
117 /// multiple mutexes). For simple use cases call Lock(), UniqueLock(),
118 /// SharedLock() instead.
119 Data& GetDataUnsafe() { return data_; }
120
121 const Data& GetDataUnsafe() const { return data_; }
122
123private:
124 mutable Mutex mutex_;
125 Data data_;
126
127 /// We need this function to work around const/non-const methods. This
128 /// helper accepts concurrent variables as template parameter and thus
129 /// will accept both const and non-const vars. And Data typename will
130 /// resolve to const/non-const accordingly.
131 template <typename VariableType, typename... StdUniqueLockArgs>
132 static auto DoUniqueLock(VariableType& concurrent_variable, StdUniqueLockArgs&&... args) {
133 std::unique_lock<Mutex> lock(concurrent_variable.mutex_, std::forward<StdUniqueLockArgs>(args)...);
134 return lock ? std::optional{LockedPtr{std::move(lock), concurrent_variable.data_}} : std::nullopt;
135 }
136};
137
138} // namespace concurrent
139
140USERVER_NAMESPACE_END