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