8000 Feature/guarded mutex (#13996) · arangodb/arangodb@d1a2e3e · GitHub
[go: up one dir, main page]

Skip to content

Commit d1a2e3e

Browse files
authored
Feature/guarded mutex (#13996)
1 parent b877321 commit d1a2e3e

File tree

6 files changed

+580
-6
lines changed

6 files changed

+580
-6
lines changed

lib/Basics/Guarded.h

Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
////////////////////////////////////////////////////////////////////////////////
2+
/// DISCLAIMER
3+
///
4+
/// Copyright 2019 ArangoDB GmbH, Cologne, Germany
5+
///
6+
/// Licensed under the Apache License, Version 2.0 (the "License");
7+
/// you may not use this file except in compliance with the License.
8+
/// You may obtain a copy of the License at
9+
///
10+
/// http://www.apache.org/licenses/LICENSE-2.0
11+
///
12+
/// Unless required by applicable law or agreed to in writing, software
13+
/// distributed under the License is distributed on an "AS IS" BASIS,
14+
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
/// See the License for the specific language governing permissions and
16+
/// limitations under the License.
17+
///
18+
/// Copyright holder is ArangoDB GmbH, Cologne, Germany
19+
///
20+
/// @author Tobias Gödderz
21+
////////////////////////////////////////////////////////////////////////////////
22+
23+
#pragma once
24+
25+
#include "Basics/system-compiler.h"
26+
27+
#include <algorithm>
28+
#include <functional>
29+
#include <memory>
30+
#include <mutex>
31+
#include <optional>
32+
#include <stdexcept>
33+
#include <variant>
34+
35+
/**
36+
* SYNOPSIS
37+
*
38+
* The class Guarded contains a value and an associated mutex. It does allow
39+
* access to the value only while owning a lock to the mutex.
40+
*
41+
* For example, given a
42+
*
43+
* struct UnderGuard { int value; }
44+
*
45+
* , define a guarded object as follows:
46+
*
47+
* Guarded<UnderGuard> guarded(7);
48+
*
49+
* The constructor's arguments will be forwarded.
50+
*
51+
* It can either be accessed by passing a lambda:
52+
*
53+
* guarded.doUnderLock([](UnderGuard& obj) { obj.value = 12; });
54+
*
55+
* This will lock the mutex before the lambda's execution, and release it after.
56+
*
57+
* Or it can be accessed by creating a MutexGuard:
58+
*
59+
* auto mutexGuard = guarded.getLockedGuard();
60+
* mutexGuard->value = 13;
61+
*
62+
* getLockedGuard() will lock the mutex, and mutexGuard will release it upon
63+
* destruction.
64+
*
65+
* For simple access, there are copy() and store():
66+
*
67+
* UnderGuard value = guarded.copy();
68+
* guarded.store(UnderGuard{3});
69+
*
70+
* If copy/assign don't suffice for some reason - e.g. because you want to:
71+
* - "try" for the lock, or
72+
* - access to one specific member or anything like that, or
73+
* - get/modify a non-copy-constructible or non-copy-assignable value,
74+
* use any of the more general methods described above instead.
75+
*/
76+
77+
namespace arangodb {
78+
79+
class Mutex;
80+
81+
template <class T, class L>
82+
class MutexGuard {
83+
public:
84+
explicit MutexGuard(T& value, L mutexLock);
85+
~MutexGuard() = default;
86+
87+
MutexGuard(MutexGuard&&) noexcept = default;
88+
auto operator=(MutexGuard&&) noexcept -> MutexGuard& = default;
89+
90+
auto get() const noexcept -> T&;
91+
auto operator->() const noexcept -> T*;
92+
93+
private:
94+
struct nop {
95+
void operator()(T*) {}
96+
};
97+
std::unique_ptr<T, nop> _value;
98+
L _mutexLock;
99+
};
100+
101+
template <class T, class L>
102+
MutexGuard<T, L>::MutexGuard(T& value, L mutexLock)
103+
: _value(&value), _mutexLock(std::move(mutexLock)) {
104+
if (ADB_UNLIKELY(!_mutexLock.owns_lock())) {
105+
throw std::invalid_argument("Lock not owned");
106+
}
107+
}
108+
109+
template <class T, class L>
110+
auto MutexGuard<T, L>::get() const noexcept -> T& {
111+
return *_value;
112+
}
113+
114+
template <class T, class L>
115+
auto MutexGuard<T, L>::operator->() const noexcept -> T* {
116+
return std::addressof(get());
117+
}
118+
119+
template <class T, class M = std::mutex, template <class> class L = std::unique_lock>
120+
class Guarded {
121+
public:
122+
using value_type = T;
123+
using mutex_type = M;
124+
using lock_type = L<M>;
125+
126+
explicit Guarded(T&& value = T());
127+
template <typename... Args>
128+
explicit Guarded(Args&&...);
129+
130+
~Guarded() = default;
131+
Guarded(Guarded const&) = delete;
132+
Guarded(Guarded&&) = delete;
133+
auto operator=(Guarded const&) -> Guarded& = delete;
134+
auto operator=(Guarded&&) -> Guarded& = delete;
135+
136+
template <class F, class R = std::invoke_result_t<F, value_type&>>
137+
auto doUnderLock(F&& callback) -> R;
138+
template <class F, class R = std::invoke_result_t<F, value_type&>>
139+
auto doUnderLock(F&& callback) const -> R;
140+
141+
template <class F, class R = std::invoke_result_t<F, value_type&>,
142+
class Q = std::conditional_t<std::is_void_v<R>, std::monostate, R>>
143+
[[nodiscard]] auto tryUnderLock(F&& callback) -> std::optional<Q>;
144+
template <class F, class R = std::invoke_result_t<F, value_type&>,
145+
class Q = std::conditional_t<std::is_void_v<R>, std::monostate, R>>
146+
[[nodiscard]] auto tryUnderLock(F&& callback) const -> std::optional<Q>;
147+
148+
// get a copy of the value, made under the lock.
149+
template <typename U = T, std::enable_if_t<std::is_copy_constructible_v<U>, int> = 0>
150+
auto copy() const -> T;
151+
152+
// assign a new value using operator=, under the lock.
153+
template <class U, std::enable_if_t<std::is_assignable_v<T, U>, int> = 0>
154+
void assign(U&&);
155+
156+
using mutex_guard_type = MutexGuard<value_type, lock_type>;
157+
using const_mutex_guard_type = MutexGuard<value_type const, lock_type>;
158+
159+
auto getLockedGuard() -> mutex_guard_type;
160+
auto getLockedGuard() const -> const_mutex_guard_type;
161+
162+
auto tryLockedGuard() -> std::optional<MutexGuard<value_type, lock_type>>;
163+
auto tryLockedGuard() const -> std::optional<MutexGuard<value_type const, lock_type>>;
164+
// TODO add more types of "get guard" (like try or eventual)
165+
166+
private:
167+
template <class F, class R = std::invoke_result_t<F, value_type&>,
168+
class Q = std::conditional_t<std::is_void_v<R>, std::monostate, R>>
169+
[[nodiscard]] static auto tryCallUnderLock(M& Mutex, F&& callback, T& value)
170+
-> std::optional<Q>;
171+
172+
value_type _value;
173+
mutable mutex_type _mutex;
174+
10000 };
175+
176+
template <class T, class M, template <class> class L>
177+
Guarded<T, M, L>::Guarded(T&& value) : _value{std::move(value)}, _mutex{} {}
178+
179+
template <class T, class M, template <class> class L>
180+
template <typename... Args>
181+
Guarded<T, M, L>::Guarded(Args&&... args) : _value{std::forward<Args>(args)...}, _mutex{} {}
182+
183+
template <class T, class M, template <class> class L>
184+
template <class F, class R>
185+
auto Guarded<T, M, L>::doUnderLock(F&& callback) -> R {
186+
auto guard = lock_type(_mutex);
187+
return std::invoke(std::forward<F>(callback), _value);
188+
}
189+
190+
template <class T, class M, template <class> class L>
191+
template <class F, class R>
192+
auto Guarded<T, M, L>::doUnderLock(F&& callback) const -> R {
193+
auto guard = lock_type(_mutex);
194+
return std::invoke(std::forward<F>(callback), _value);
195+
}
196+
197+
template <class T, class M, template <class> class L>
198+
template <class F, class R, class Q>
199+
auto Guarded<T, M, L>::tryCallUnderLock(M& mutex, F&& callback, T& value) -> std::optional<Q> {
200+
auto guard = lock_type(mutex, std::try_to_lock);
201+
202+
if (guard.owns_lock()) {
203+
if constexpr (!std::is_void_v<R>) {
204+
return std::forward<F>(callback)(value);
205+
} else {
206+
std::forward<F>(callback)(value);
207+
return std::monostate{};
208+
}
209+
} else {
210+
return std::nullopt;
211+
}
212+
}
213+
214+
template <class T, class M, template <class> class L>
215+
template <class F, class R, class Q>
216+
auto Guarded<T, M, L>::tryUnderLock(F&& callback) -> std::optional<Q> {
217+
return tryCallUnderLock(_mutex, std::forward<F>(callback), _value);
218+
}
219+
220+
template <class T, class M, template <class> class L>
221+
template <class F, class R, class Q>
222+
auto Guarded<T, M, L>::tryUnderLock(F&& callback) const -> std::optional<Q> {
223+
return tryCallUnderLock(_mutex, std::forward<F>(callback), _value);
224+
}
225+
226+
template <class T, class M, template <class> class L>
227+
template <typename U, std::enable_if_t<std::is_copy_constructible_v<U>, int>>
228+
auto Guarded<T, M, L>::copy() const -> T {
229+
auto guard = lock_type(this->_mutex);
230+
return _value;
231+
}
232+
233+
template <class T, class M, template <class> class L>
234+
template <class U, std::enable_if_t<std::is_assignable_v<T, U>, int>>
235+
void Guarded<T, M, L>::assign(U&& value) {
236+
auto guard = lock_type(_mutex);
237+
_value = std::forward<U>(value);
238+
}
239+
240+
template <class T, class M, template <class> class L>
241+
auto Guarded<T, M, L>::getLockedGuard() -> MutexGuard<T, L<M>> {
242+
return MutexGuard(_value, lock_type(_mutex));
243+
}
244+
245+
template <class T, class M, template <class> class L>
246+
auto Guarded<T, M, L>::getLockedGuard() const -> MutexGuard<T const, L<M>> {
247+
return MutexGuard(_value, lock_type(_mutex));
248+
}
249+
250+
template <class T, class M, template <class> class L>
251+
auto Guarded<T, M, L>::tryLockedGuard() -> std::optional<MutexGuard<T, L<M>>> {
252+
auto lock = lock_type(_mutex, std::try_lock);
253+
254+
if (lock.owns_lock()) {
255+
return MutexGuard(_value, std::move(lock));
256+
} else {
257+
return std::nullopt;
258+
}
259+
}
260+
261+
template <class T, class M, template <class> class L>
262+
auto Guarded<T, M, L>::tryLockedGuard() const
263+
-> std::optional<MutexGuard<T const, L<M>>> {
264+
auto lock = lock_type(_mutex, std::try_lock);
265+
266+
if (lock.owns_lock()) {
267+
return MutexGuard(_value, std::move(lock));
268+
} else {
269+
return std::nullopt;
270+
}
271+
}
272+
273+
} // namespace arangodb

lib/Basics/Mutex.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ void Mutex::lock() {
8484
#endif
8585
}
8686

87-
bool Mutex::tryLock() {
87+
bool Mutex::try_lock() {
8888
#ifdef ARANGODB_ENABLE_DEADLOCK_DETECTION
8989
// we must not hold the lock ourselves here
9090
TRI_ASSERT(_holder != Thread::currentThreadId());
@@ -146,7 +146,7 @@ Mutex::~Mutex() = default;
146146

147147
void Mutex::lock() { AcquireSRWLockExclusive(&_mutex); }
148148

149-
bool Mutex::tryLock() { return TryAcquireSRWLockExclusive(&_mutex) != 0; }
149+
bool Mutex::try_lock() { return TryAcquireSRWLockExclusive(&_mutex) != 0; }
150150

151151
void Mutex::unlock() { ReleaseSRWLockExclusive(&_mutex); }
152152

lib/Basics/Mutex.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,17 +53,18 @@
5353
namespace arangodb {
5454

5555
class Mutex {
56-
private:
56+
public:
5757
Mutex(Mutex const&) = delete;
5858
Mutex& operator=(Mutex const&) = delete;
5959

60-
public:
6160
Mutex();
6261
~Mutex();
6362

6463
public:
6564
void lock();
66-
bool tryLock();
65+
// purposefully violate our naming convention (try_lock instead of tryLock)
66+
// in order to be compatible with std::mutex
67+
bool try_lock();
6768
void unlock();
6869

6970
// assert that the mutex is locked by the current thread. will do

lib/Basics/MutexLocker.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ class MutexLocker {
126126

127127
bool tryLock() {
128128
TRI_ASSERT(!_isLocked);
129-
if (_mutex->tryLock()) {
129+
if (_mutex->try_lock()) {
130130
_isLocked = true;
131131
}
132132
return _isLocked;

0 commit comments

Comments
 (0)
0