Async++ unknown
Async (co_await/co_return) code for C++
Loading...
Searching...
No Matches
mutex.h
1#pragma once
3
4#include <atomic>
5#include <cassert>
6#include <cstdint>
7#include <mutex>
8
9namespace asyncpp {
10 class mutex_lock;
20 class mutex {
21 public:
22 struct lock_awaiter;
25 constexpr static struct {
27 friend class mutex_lock;
29 constexpr mutex() noexcept : m_state{state_unlocked}, m_awaiters{nullptr} {}
31 explicit constexpr mutex(decltype(construct_locked)) noexcept
32 : m_state{state_locked_no_waiters}, m_awaiters{nullptr} {}
39 [[maybe_unused]] auto state = m_state.load(std::memory_order_relaxed);
40 assert(state == state_unlocked || state == state_locked_no_waiters);
41 assert(m_awaiters == nullptr);
42 }
43 mutex(const mutex&) noexcept = delete;
44 mutex(mutex&&) noexcept = delete;
45 mutex& operator=(const mutex&) noexcept = delete;
46 mutex& operator=(mutex&&) noexcept = delete;
47
54 [[nodiscard]] bool try_lock() noexcept {
55 auto old = state_unlocked;
56 return m_state.compare_exchange_strong(old, state_locked_no_waiters, std::memory_order::acquire,
57 std::memory_order::relaxed);
58 }
65 [[nodiscard]] constexpr lock_awaiter lock() noexcept;
72 [[nodiscard]] constexpr scoped_lock_awaiter lock_scoped() noexcept;
79 void unlock() noexcept;
84 [[nodiscard]] bool is_locked() const noexcept {
85 return m_state.load(std::memory_order::relaxed) != state_unlocked;
86 }
87
88 private:
89 static constexpr std::uintptr_t state_locked_no_waiters = 0;
90 static constexpr std::uintptr_t state_unlocked = 1;
91 std::atomic<uintptr_t> m_state;
92 lock_awaiter* m_awaiters;
93 };
94
95 struct [[nodiscard]] mutex::lock_awaiter {
96 constexpr explicit lock_awaiter(class mutex* mtx) : mutex(mtx) {}
97 [[nodiscard]] constexpr bool await_ready() const noexcept { return false; }
98 [[nodiscard]] bool await_suspend(coroutine_handle<> hndl) noexcept {
99 handle = hndl;
100 auto old = mutex->m_state.load(std::memory_order::acquire);
101 while (true) {
102 if (old == state_unlocked) {
103 if (mutex->m_state.compare_exchange_weak(old, state_locked_no_waiters, std::memory_order::acquire,
104 std::memory_order::relaxed))
105 return false;
106 } else {
107 // NOLINTNEXTLINE(performance-no-int-to-ptr)
108 next = reinterpret_cast<lock_awaiter*>(old);
109 if (mutex->m_state.compare_exchange_weak(old, reinterpret_cast<std::uintptr_t>(this),
110 std::memory_order::release, std::memory_order::relaxed))
111 return true;
112 }
113 }
114 }
115 constexpr void await_resume() const noexcept {}
116
117 class mutex* mutex;
118 lock_awaiter* next{nullptr};
119 coroutine_handle<> handle{};
120 };
121
126 public:
131 mutex_lock(class mutex& mtx, std::adopt_lock_t) noexcept : m_mtx(&mtx), m_locked{true} {
132 assert(mtx.is_locked());
133 }
138 explicit constexpr mutex_lock(class mutex& mtx) noexcept : m_mtx(&mtx), m_locked{false} {}
139 constexpr mutex_lock(const mutex_lock&) noexcept = delete;
140 constexpr mutex_lock& operator=(const mutex_lock&) noexcept = delete;
141 constexpr mutex_lock(mutex_lock&& other) noexcept : m_mtx(other.m_mtx), m_locked{other.m_locked} {
142 other.m_mtx = nullptr;
143 other.m_locked = false;
144 }
145 mutex_lock& operator=(mutex_lock&& other) noexcept {
146 if (m_mtx != nullptr && m_locked) m_mtx->unlock();
147 m_mtx = other.m_mtx;
148 other.m_mtx = nullptr;
149 m_locked = other.m_locked;
150 other.m_locked = false;
151 return *this;
152 }
153 ~mutex_lock() {
154 if (m_mtx != nullptr && m_locked) m_mtx->unlock();
155 }
156
161 void unlock() noexcept {
162 assert(m_locked);
163 m_mtx->unlock();
164 m_locked = false;
165 }
166
172 [[nodiscard]] bool try_lock() noexcept {
173 assert(!m_locked);
174 auto res = m_mtx->try_lock();
175 m_locked = res;
176 return res;
177 }
178
184 [[nodiscard]] auto lock() noexcept {
185 struct awaiter {
186 explicit awaiter(mutex_lock* parent) : m_parent(parent), m_mutex_awaiter(parent->m_mtx) {}
187 [[nodiscard]] bool await_ready() const noexcept {
188 return m_parent->m_locked || m_mutex_awaiter.await_ready();
189 }
190 [[nodiscard]] auto await_suspend(coroutine_handle<> hndl) noexcept {
191 return m_mutex_awaiter.await_suspend(hndl);
192 }
193 void await_resume() const noexcept {
194 m_mutex_awaiter.await_resume();
195 m_parent->m_locked = true;
196 }
197
198 private:
199 mutex_lock* m_parent;
200 mutex::lock_awaiter m_mutex_awaiter;
201 };
202 return awaiter{this};
203 }
204
206 [[nodiscard]] bool is_locked() const noexcept { return m_locked; }
208 [[nodiscard]] class mutex& mutex() const noexcept { return *m_mtx; }
209
210 private:
211 class mutex* m_mtx;
212 bool m_locked;
213 };
214
215 struct [[nodiscard]] mutex::scoped_lock_awaiter {
216 constexpr explicit scoped_lock_awaiter(class mutex* mtx) : awaiter(mtx) {}
217 [[nodiscard]] constexpr bool await_ready() const noexcept { return awaiter.await_ready(); }
218 [[nodiscard]] bool await_suspend(coroutine_handle<> hndl) noexcept { return awaiter.await_suspend(hndl); }
219 [[nodiscard]] mutex_lock await_resume() const noexcept {
220 awaiter.await_resume();
221 return mutex_lock{*awaiter.mutex, std::adopt_lock};
222 }
223
224 private:
225 lock_awaiter awaiter;
226 };
227
228 constexpr inline mutex::lock_awaiter mutex::lock() noexcept { return lock_awaiter{this}; }
229
230 constexpr inline mutex::scoped_lock_awaiter mutex::lock_scoped() noexcept { return scoped_lock_awaiter{this}; }
231
232 inline void mutex::unlock() noexcept {
233 assert(m_state.load(std::memory_order::relaxed) != state_unlocked);
234
235 auto head = m_awaiters;
236 if (head == nullptr) {
237 auto old = state_locked_no_waiters;
238 const auto didrelease = m_state.compare_exchange_strong(old, state_unlocked, std::memory_order::release,
239 std::memory_order::relaxed);
240 if (didrelease) return;
241 old = m_state.exchange(state_locked_no_waiters, std::memory_order::acquire);
242 assert(old != state_locked_no_waiters && old != state_unlocked);
243 //NOLINTNEXTLINE(performance-no-int-to-ptr)
244 auto next = reinterpret_cast<lock_awaiter*>(old);
245 do {
246 auto temp = next->next;
247 next->next = head;
248 head = next;
249 next = temp;
250 } while (next != nullptr);
251 }
252 assert(head != nullptr);
253 m_awaiters = head->next;
254 head->handle.resume();
255 }
256
257} // namespace asyncpp
RAII type to automatically unlock a mutex once it leaves scope.
Definition mutex.h:125
void unlock() noexcept
Unlock the lock See mutex::unlock() for details.
Definition mutex.h:161
bool is_locked() const noexcept
Check if the mutex is held by this lock.
Definition mutex.h:206
mutex_lock(class mutex &mtx, std::adopt_lock_t) noexcept
Construct a mutex_lock for the given mutex, adopting the lock.
Definition mutex.h:131
auto lock() noexcept
Asynchronously lock the contained mutex.
Definition mutex.h:184
class mutex & mutex() const noexcept
Get the wrapped mutex.
Definition mutex.h:208
constexpr mutex_lock(class mutex &mtx) noexcept
Construct an unlocked mutex_lock for the given mutex.
Definition mutex.h:138
bool try_lock() noexcept
Try locking the contained mutex.
Definition mutex.h:172
A mutex with an asynchronous lock() operation.
Definition mutex.h:20
constexpr mutex(decltype(construct_locked)) noexcept
Construct mutex in its locked state.
Definition mutex.h:31
bool is_locked() const noexcept
Query if the lock is currently locked.
Definition mutex.h:84
constexpr lock_awaiter lock() noexcept
Acquire the the mutex using co_await.
Definition mutex.h:228
constexpr mutex() noexcept
Construct mutex in its unlocked state.
Definition mutex.h:29
constexpr scoped_lock_awaiter lock_scoped() noexcept
Acquire the the mutex using co_await and wrap it in a mutex_lock.
Definition mutex.h:230
bool try_lock() noexcept
Attempt to acquire the mutex whitout blocking or yielding.
Definition mutex.h:54
void unlock() noexcept
Unlock the mutex.
Definition mutex.h:232
~mutex()
Destruct mutex.
Definition mutex.h:38
static constexpr struct asyncpp::mutex::@0 construct_locked
Tag value used to construct locked mutex.
Provides a consistent import interface for coroutine, experimental/coroutine or a best effort fallbac...
Definition mutex.h:95