Skip to content

Commit c374ecc

Browse files
committed
Add multi_lock, P3833
Signed-off-by: Ted Lyngmo <[email protected]>
1 parent 88c7ad8 commit c374ecc

File tree

6 files changed

+663
-16
lines changed

6 files changed

+663
-16
lines changed

README.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ SPDX-License-Identifier: MIT
77
<!-- markdownlint-disable-next-line line-length -->
88
![Library Status](https://github.com/bemanproject/beman/blob/main/images/badges/beman_badge-beman_library_under_development.svg) ![Continuous Integration Tests](https://github.com/bemanproject/timed_lock_alg/actions/workflows/ci_tests.yml/badge.svg) ![Lint Check (pre-commit)](https://github.com/bemanproject/timed_lock_alg/actions/workflows/pre-commit-check.yml/badge.svg) [![Coverage](https://coveralls.io/repos/github/bemanproject/timed_lock_alg/badge.svg?branch=main)](https://coveralls.io/github/bemanproject/timed_lock_alg?branch=main) ![Standard Target](https://github.com/bemanproject/beman/blob/main/images/badges/cpp29.svg) [![Compiler Explorer Example](https://img.shields.io/badge/Try%20it%20on%20Compiler%20Explorer-grey?logo=compilerexplorer&logoColor=67c52a)](https://godbolt.org/z/jPYdxT3E7)
99

10-
`beman.timed_lock_alg` implements timed lock algorithms for multiple lockables.
10+
`beman.timed_lock_alg` implements timed lock algorithms for multiple lockables and `std::multi_lock`.
1111

12-
**Implements**: `std::try_lock_until` and `std::try_lock_for` proposed in [Timed lock algorithms for multiple lockables (P3832R0)](https://wg21.link/P3832R0).
12+
**Implements**: `std::try_lock_until` and `std::try_lock_for` proposed in [Timed lock algorithms for multiple lockables (P3832R0)](https://wg21.link/P3832R0) and `std::multi_lock` proposed in [`std::multi_lock` (P3833R0)](https://wg21.link/P3832R0)
1313

1414
**Status**: [Under development and not yet ready for production use.](https://github.com/bemanproject/beman/blob/main/docs/beman_library_maturity_model.md#under-development-and-not-yet-ready-for-production-use)
1515

@@ -33,6 +33,17 @@ if (std::try_lock_for(100ms, m1, m2) == -1) {
3333
}
3434
```
3535

36+
`std::multi_lock` is a flexible RAII container usable with zero to many _BasicLockables_.
37+
38+
Example:
39+
```
40+
std::timed_mutex m1, m2;
41+
std::multi_lock lock(100ms, m1, m2);
42+
if (lock) {
43+
// lock acquired within timeout
44+
}
45+
```
46+
3647
Full runnable examples can be found in [`examples/`](examples/).
3748

3849
## Dependencies

include/beman/timed_lock_alg/mutex.hpp

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,5 +112,154 @@ template <class Rep, class Period, detail::TimedLockable... Ls>
112112
[[nodiscard]] int try_lock_for(const std::chrono::duration<Rep, Period>& dur, Ls&... ls) {
113113
return try_lock_until(std::chrono::steady_clock::now() + dur, ls...);
114114
}
115+
116+
template <detail::BasicLockable... Ms>
117+
class multi_lock {
118+
public:
119+
using mutex_type = std::tuple<Ms*...>;
120+
121+
// Constructors
122+
multi_lock() noexcept = default;
123+
124+
explicit multi_lock(Ms&... ms)
125+
requires(sizeof...(Ms) > 0)
126+
: m_ms(std::addressof(ms)...) {
127+
lock();
128+
}
129+
130+
multi_lock(std::defer_lock_t, Ms&... ms) noexcept : m_ms(std::addressof(ms)...) {}
131+
132+
multi_lock(std::try_to_lock_t, Ms&... ms)
133+
requires(... && detail::Lockable<Ms>)
134+
: m_ms(std::addressof(ms)...) {
135+
try_lock();
136+
}
137+
138+
multi_lock(std::adopt_lock_t, Ms&... ms) noexcept : m_ms(std::addressof(ms)...), m_locked(true) {}
139+
140+
template <class Rep, class Period>
141+
requires(... && detail::TimedLockable<Ms>)
142+
multi_lock(const std::chrono::duration<Rep, Period>& dur, Ms&... ms) : m_ms(std::addressof(ms)...) {
143+
try_lock_for(dur);
144+
}
145+
146+
template <class Clock, class Duration>
147+
requires(... && detail::TimedLockable<Ms>)
148+
multi_lock(const std::chrono::time_point<Clock, Duration>& tp, Ms&... ms) : m_ms(std::addressof(ms)...) {
149+
try_lock_until(tp);
150+
}
151+
152+
// Destructor
153+
~multi_lock() {
154+
if (m_locked)
155+
unlock();
156+
}
157+
158+
// Move operations
159+
multi_lock(multi_lock&& other) noexcept
160+
: m_ms(std::exchange(other.m_ms, std::tuple<Ms*...>{})), m_locked(std::exchange(other.m_locked, false)) {}
161+
162+
multi_lock& operator=(multi_lock&& other) noexcept {
163+
multi_lock(std::move(other)).swap(*this);
164+
return *this;
165+
}
166+
167+
// Deleted copy operations
168+
multi_lock(const multi_lock&) = delete;
169+
multi_lock& operator=(const multi_lock&) = delete;
170+
171+
// Locking operations
172+
private:
173+
void lock_check() {
174+
if (m_locked) {
175+
throw std::system_error(std::make_error_code(std::errc::resource_deadlock_would_occur));
176+
}
177+
if constexpr (sizeof...(Ms) != 0) {
178+
if (std::get<0>(m_ms) == nullptr) {
179+
throw std::system_error(std::make_error_code(std::errc::operation_not_permitted));
180+
}
181+
}
182+
}
183+
184+
public:
185+
void lock()
186+
requires(sizeof...(Ms) == 1 || (... && detail::Lockable<Ms>))
187+
{
188+
lock_check();
189+
if constexpr (sizeof...(Ms) == 1) {
190+
std::get<sizeof...(Ms) - 1>(m_ms)->lock();
191+
} else if constexpr (sizeof...(Ms) > 1) {
192+
std::apply([](auto... ms) { std::lock(*ms...); }, m_ms);
193+
}
194+
m_locked = true;
195+
}
196+
197+
int try_lock()
198+
requires(... && detail::Lockable<Ms>)
199+
{
200+
lock_check();
201+
int rv;
202+
if constexpr (sizeof...(Ms) == 0) {
203+
rv = -1;
204+
} else if constexpr (sizeof...(Ms) == 1) {
205+
rv = -static_cast<int>(std::get<sizeof...(Ms) - 1>(m_ms)->try_lock());
206+
} else {
207+
rv = std::apply([](auto... ms) { return std::try_lock(*ms...); }, m_ms);
208+
}
209+
m_locked = rv == -1;
210+
return rv;
211+
}
212+
213+
template <class Rep, class Period>
214+
requires(... && detail::TimedLockable<Ms>)
215+
int try_lock_for(const std::chrono::duration<Rep, Period>& dur) {
216+
lock_check();
217+
int rv = std::apply([&](auto... ms) { return beman::timed_lock_alg::try_lock_for(dur, *ms...); }, m_ms);
218+
m_locked = rv == -1;
219+
return rv;
220+
}
221+
222+
template <class Clock, class Duration>
223+
requires(... && detail::TimedLockable<Ms>)
224+
int try_lock_until(const std::chrono::time_point<Clock, Duration>& tp) {
225+
lock_check();
226+
int rv = std::apply([&](auto... ms) { return beman::timed_lock_alg::try_lock_until(tp, *ms...); }, m_ms);
227+
m_locked = rv == -1;
228+
return rv;
229+
}
230+
231+
void unlock() {
232+
if (not m_locked) {
233+
throw std::system_error(std::make_error_code(std::errc::operation_not_permitted));
234+
}
235+
auto unlocker = std::apply([](auto... ms) { return std::scoped_lock(std::adopt_lock, *ms...); }, m_ms);
236+
m_locked = false;
237+
}
238+
239+
// Modifiers
240+
void swap(multi_lock& other) noexcept {
241+
std::swap(m_ms, other.m_ms);
242+
std::swap(m_locked, other.m_locked);
243+
}
244+
245+
mutex_type release() noexcept {
246+
m_locked = false;
247+
return std::exchange(m_ms, mutex_type{});
248+
}
249+
250+
// Observers
251+
std::tuple<Ms*...> mutex() const noexcept { return m_ms; }
252+
bool owns_lock() const noexcept { return m_locked; }
253+
explicit operator bool() const noexcept { return m_locked; }
254+
255+
private:
256+
mutex_type m_ms;
257+
bool m_locked = false;
258+
};
259+
260+
template <class... Ms>
261+
void swap(multi_lock<Ms...>& lhs, multi_lock<Ms...>& rhs) noexcept {
262+
lhs.swap(rhs);
263+
}
115264
} // namespace beman::timed_lock_alg
116265
#endif

tests/beman/timed_lock_alg/CMakeLists.txt

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,25 @@
22

33
find_package(GTest REQUIRED)
44

5-
add_executable(beman.timed_lock_alg.tests.mutex)
6-
target_sources(beman.timed_lock_alg.tests.mutex PRIVATE mutex.test.cpp)
5+
add_executable(beman.timed_lock_alg.tests.try_lock)
6+
target_sources(beman.timed_lock_alg.tests.try_lock PRIVATE try_lock.test.cpp)
77
target_link_libraries(
8-
beman.timed_lock_alg.tests.mutex
8+
beman.timed_lock_alg.tests.try_lock
99
PRIVATE beman::timed_lock_alg GTest::gtest GTest::gtest_main
1010
)
1111

1212
include(GoogleTest)
13-
gtest_discover_tests(beman.timed_lock_alg.tests.mutex)
13+
gtest_discover_tests(beman.timed_lock_alg.tests.try_lock)
14+
15+
add_executable(beman.timed_lock_alg.tests.multi_lock)
16+
target_sources(
17+
beman.timed_lock_alg.tests.multi_lock
18+
PRIVATE multi_lock.test.cpp
19+
)
20+
target_link_libraries(
21+
beman.timed_lock_alg.tests.multi_lock
22+
PRIVATE beman::timed_lock_alg GTest::gtest GTest::gtest_main
23+
)
24+
25+
include(GoogleTest)
26+
gtest_discover_tests(beman.timed_lock_alg.tests.multi_lock)
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
#ifndef BEMAN_TIMED_LOCK_ALG_TESTS_MOCK_TIMED_MUTEX_HPP
4+
#define BEMAN_TIMED_LOCK_ALG_TESTS_MOCK_TIMED_MUTEX_HPP
5+
6+
#include <atomic>
7+
#include <chrono>
8+
#include <thread>
9+
10+
namespace beman::timed_lock_alg::test {
11+
12+
struct MockTimedMutex {
13+
std::atomic<bool> locked{false};
14+
std::atomic<bool> should_fail{false};
15+
std::atomic<int> lock_count{0};
16+
std::atomic<int> unlock_count{0};
17+
std::atomic<int> try_lock_count{0};
18+
19+
void lock() {
20+
while (should_fail)
21+
std::this_thread::yield();
22+
++lock_count;
23+
locked = true;
24+
}
25+
26+
bool try_lock() {
27+
++try_lock_count;
28+
if (should_fail)
29+
return false;
30+
++lock_count;
31+
locked = true;
32+
return true;
33+
}
34+
35+
template <class Rep, class Period>
36+
bool try_lock_for(const std::chrono::duration<Rep, Period>&) {
37+
return try_lock();
38+
}
39+
40+
template <class Clock, class Duration>
41+
bool try_lock_until(const std::chrono::time_point<Clock, Duration>&) {
42+
return try_lock();
43+
}
44+
45+
void unlock() {
46+
++unlock_count;
47+
locked = false;
48+
}
49+
};
50+
51+
} // namespace beman::timed_lock_alg::test
52+
53+
#endif // BEMAN_TIMED_LOCK_ALG_TESTS_MOCK_TIMED_MUTEX_HPP

0 commit comments

Comments
 (0)