1  
//
1  
//
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3  
//
3  
//
4  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
4  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
5  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
5  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6  
//
6  
//
7  
// Official repository: https://github.com/cppalliance/capy
7  
// Official repository: https://github.com/cppalliance/capy
8  
//
8  
//
9  

9  

10  
#ifndef BOOST_CAPY_ASYNC_MUTEX_HPP
10  
#ifndef BOOST_CAPY_ASYNC_MUTEX_HPP
11  
#define BOOST_CAPY_ASYNC_MUTEX_HPP
11  
#define BOOST_CAPY_ASYNC_MUTEX_HPP
12  

12  

13  
#include <boost/capy/detail/config.hpp>
13  
#include <boost/capy/detail/config.hpp>
14  
#include <boost/capy/detail/intrusive.hpp>
14  
#include <boost/capy/detail/intrusive.hpp>
15  
#include <boost/capy/concept/executor.hpp>
15  
#include <boost/capy/concept/executor.hpp>
16  
#include <boost/capy/coro.hpp>
16  
#include <boost/capy/coro.hpp>
17  
#include <boost/capy/error.hpp>
17  
#include <boost/capy/error.hpp>
18  
#include <boost/capy/ex/executor_ref.hpp>
18  
#include <boost/capy/ex/executor_ref.hpp>
19  
#include <boost/capy/io_result.hpp>
19  
#include <boost/capy/io_result.hpp>
20  

20  

21  
#include <stop_token>
21  
#include <stop_token>
22  

22  

23  
#include <atomic>
23  
#include <atomic>
24  
#include <coroutine>
24  
#include <coroutine>
25  
#include <new>
25  
#include <new>
26  
#include <utility>
26  
#include <utility>
27  

27  

28  
/*  async_mutex implementation notes
28  
/*  async_mutex implementation notes
29  
    ================================
29  
    ================================
30  

30  

31  
    Waiters form a doubly-linked intrusive list (fair FIFO). lock_awaiter
31  
    Waiters form a doubly-linked intrusive list (fair FIFO). lock_awaiter
32  
    inherits intrusive_list<lock_awaiter>::node; the list is owned by
32  
    inherits intrusive_list<lock_awaiter>::node; the list is owned by
33  
    async_mutex::waiters_.
33  
    async_mutex::waiters_.
34  

34  

35  
    Cancellation via stop_token
35  
    Cancellation via stop_token
36  
    ---------------------------
36  
    ---------------------------
37  
    A std::stop_callback is registered in await_suspend. Two actors can
37  
    A std::stop_callback is registered in await_suspend. Two actors can
38  
    race to resume the suspended coroutine: unlock() and the stop callback.
38  
    race to resume the suspended coroutine: unlock() and the stop callback.
39  
    An atomic bool `claimed_` resolves the race -- whoever does
39  
    An atomic bool `claimed_` resolves the race -- whoever does
40  
    claimed_.exchange(true) and reads false wins. The loser does nothing.
40  
    claimed_.exchange(true) and reads false wins. The loser does nothing.
41  

41  

42  
    The stop callback calls ex_.dispatch(h_). If dispatch runs inline
42  
    The stop callback calls ex_.dispatch(h_). If dispatch runs inline
43  
    (same thread), the stop_callback is destroyed from within its own
43  
    (same thread), the stop_callback is destroyed from within its own
44  
    operator() via await_resume. This is safe: cancel_fn touches no
44  
    operator() via await_resume. This is safe: cancel_fn touches no
45  
    members after dispatch returns (same pattern as delete-this).
45  
    members after dispatch returns (same pattern as delete-this).
46  

46  

47  
    unlock() pops waiters from the front. If the popped waiter was
47  
    unlock() pops waiters from the front. If the popped waiter was
48  
    already claimed by the stop callback, unlock() skips it and tries
48  
    already claimed by the stop callback, unlock() skips it and tries
49  
    the next. await_resume removes the (still-linked) canceled waiter
49  
    the next. await_resume removes the (still-linked) canceled waiter
50  
    via waiters_.remove(this).
50  
    via waiters_.remove(this).
51  

51  

52  
    The stop_callback lives in a union to suppress automatic
52  
    The stop_callback lives in a union to suppress automatic
53  
    construction/destruction. Placement new in await_suspend, explicit
53  
    construction/destruction. Placement new in await_suspend, explicit
54  
    destructor call in await_resume and ~lock_awaiter.
54  
    destructor call in await_resume and ~lock_awaiter.
55  

55  

56  
    Member ordering constraint
56  
    Member ordering constraint
57  
    --------------------------
57  
    --------------------------
58  
    The union containing stop_cb_ must be declared AFTER the members
58  
    The union containing stop_cb_ must be declared AFTER the members
59  
    the callback accesses (h_, ex_, claimed_, canceled_). If the
59  
    the callback accesses (h_, ex_, claimed_, canceled_). If the
60  
    stop_cb_ destructor blocks waiting for a concurrent callback, those
60  
    stop_cb_ destructor blocks waiting for a concurrent callback, those
61  
    members must still be alive (C++ destroys in reverse declaration
61  
    members must still be alive (C++ destroys in reverse declaration
62  
    order).
62  
    order).
63  

63  

64  
    active_ flag
64  
    active_ flag
65  
    ------------
65  
    ------------
66  
    Tracks both list membership and stop_cb_ lifetime (they are always
66  
    Tracks both list membership and stop_cb_ lifetime (they are always
67  
    set and cleared together). Used by the destructor to clean up if the
67  
    set and cleared together). Used by the destructor to clean up if the
68  
    coroutine is destroyed while suspended (e.g. execution_context
68  
    coroutine is destroyed while suspended (e.g. execution_context
69  
    shutdown).
69  
    shutdown).
70  

70  

71  
    Cancellation scope
71  
    Cancellation scope
72  
    ------------------
72  
    ------------------
73  
    Cancellation only takes effect while the coroutine is suspended in
73  
    Cancellation only takes effect while the coroutine is suspended in
74  
    the wait queue. If the mutex is unlocked, await_ready acquires it
74  
    the wait queue. If the mutex is unlocked, await_ready acquires it
75  
    immediately without checking the stop token. This is intentional:
75  
    immediately without checking the stop token. This is intentional:
76  
    the fast path has no token access and no overhead.
76  
    the fast path has no token access and no overhead.
77  

77  

78  
    Threading assumptions
78  
    Threading assumptions
79  
    ---------------------
79  
    ---------------------
80  
    - All list mutations happen on the executor thread (await_suspend,
80  
    - All list mutations happen on the executor thread (await_suspend,
81  
      await_resume, unlock, ~lock_awaiter).
81  
      await_resume, unlock, ~lock_awaiter).
82  
    - The stop callback may fire from any thread, but only touches
82  
    - The stop callback may fire from any thread, but only touches
83  
      claimed_ (atomic) and then calls dispatch. It never touches the
83  
      claimed_ (atomic) and then calls dispatch. It never touches the
84  
      list.
84  
      list.
85  
    - ~lock_awaiter must be called from the executor thread. This is
85  
    - ~lock_awaiter must be called from the executor thread. This is
86  
      guaranteed during normal shutdown but NOT if the coroutine frame
86  
      guaranteed during normal shutdown but NOT if the coroutine frame
87  
      is destroyed from another thread while a stop callback could
87  
      is destroyed from another thread while a stop callback could
88  
      fire (precondition violation, same as cppcoro/folly).
88  
      fire (precondition violation, same as cppcoro/folly).
89  
*/
89  
*/
90  

90  

91  
namespace boost {
91  
namespace boost {
92  
namespace capy {
92  
namespace capy {
93  

93  

94  
/** An asynchronous mutex for coroutines.
94  
/** An asynchronous mutex for coroutines.
95  

95  

96  
    This mutex provides mutual exclusion for coroutines without blocking.
96  
    This mutex provides mutual exclusion for coroutines without blocking.
97  
    When a coroutine attempts to acquire a locked mutex, it suspends and
97  
    When a coroutine attempts to acquire a locked mutex, it suspends and
98  
    is added to an intrusive wait queue. When the holder unlocks, the next
98  
    is added to an intrusive wait queue. When the holder unlocks, the next
99  
    waiter is resumed with the lock held.
99  
    waiter is resumed with the lock held.
100  

100  

101  
    @par Cancellation
101  
    @par Cancellation
102  

102  

103  
    When a coroutine is suspended waiting for the mutex and its stop
103  
    When a coroutine is suspended waiting for the mutex and its stop
104  
    token is triggered, the waiter completes with `error::canceled`
104  
    token is triggered, the waiter completes with `error::canceled`
105  
    instead of acquiring the lock.
105  
    instead of acquiring the lock.
106  

106  

107  
    Cancellation only applies while the coroutine is suspended in the
107  
    Cancellation only applies while the coroutine is suspended in the
108  
    wait queue. If the mutex is unlocked when `lock()` is called, the
108  
    wait queue. If the mutex is unlocked when `lock()` is called, the
109  
    lock is acquired immediately even if the stop token is already
109  
    lock is acquired immediately even if the stop token is already
110  
    signaled.
110  
    signaled.
111  

111  

112  
    @par Zero Allocation
112  
    @par Zero Allocation
113  

113  

114  
    No heap allocation occurs for lock operations.
114  
    No heap allocation occurs for lock operations.
115  

115  

116  
    @par Thread Safety
116  
    @par Thread Safety
117  

117  

118  
    The mutex operations are designed for single-threaded use on one
118  
    The mutex operations are designed for single-threaded use on one
119  
    executor. The stop callback may fire from any thread.
119  
    executor. The stop callback may fire from any thread.
120  

120  

121  
    @par Example
121  
    @par Example
122  
    @code
122  
    @code
123  
    async_mutex cm;
123  
    async_mutex cm;
124  

124  

125  
    task<> protected_operation() {
125  
    task<> protected_operation() {
126  
        auto [ec] = co_await cm.lock();
126  
        auto [ec] = co_await cm.lock();
127  
        if(ec)
127  
        if(ec)
128  
            co_return;
128  
            co_return;
129  
        // ... critical section ...
129  
        // ... critical section ...
130  
        cm.unlock();
130  
        cm.unlock();
131  
    }
131  
    }
132  

132  

133  
    // Or with RAII:
133  
    // Or with RAII:
134  
    task<> protected_operation() {
134  
    task<> protected_operation() {
135  
        auto [ec, guard] = co_await cm.scoped_lock();
135  
        auto [ec, guard] = co_await cm.scoped_lock();
136  
        if(ec)
136  
        if(ec)
137  
            co_return;
137  
            co_return;
138  
        // ... critical section ...
138  
        // ... critical section ...
139  
        // unlocks automatically
139  
        // unlocks automatically
140  
    }
140  
    }
141  
    @endcode
141  
    @endcode
142  
*/
142  
*/
143  
class async_mutex
143  
class async_mutex
144  
{
144  
{
145  
public:
145  
public:
146  
    class lock_awaiter;
146  
    class lock_awaiter;
147  
    class lock_guard;
147  
    class lock_guard;
148  
    class lock_guard_awaiter;
148  
    class lock_guard_awaiter;
149  

149  

150  
private:
150  
private:
151  
    bool locked_ = false;
151  
    bool locked_ = false;
152  
    detail::intrusive_list<lock_awaiter> waiters_;
152  
    detail::intrusive_list<lock_awaiter> waiters_;
153  

153  

154  
public:
154  
public:
155  
    /** Awaiter returned by lock().
155  
    /** Awaiter returned by lock().
156  
    */
156  
    */
157  
    class lock_awaiter
157  
    class lock_awaiter
158  
        : public detail::intrusive_list<lock_awaiter>::node
158  
        : public detail::intrusive_list<lock_awaiter>::node
159  
    {
159  
    {
160  
        friend class async_mutex;
160  
        friend class async_mutex;
161  

161  

162  
        async_mutex* m_;
162  
        async_mutex* m_;
163  
        std::coroutine_handle<> h_;
163  
        std::coroutine_handle<> h_;
164  
        executor_ref ex_;
164  
        executor_ref ex_;
165  

165  

166  
        // These members must be declared before stop_cb_
166  
        // These members must be declared before stop_cb_
167  
        // (see comment on the union below).
167  
        // (see comment on the union below).
168  
        std::atomic<bool> claimed_{false};
168  
        std::atomic<bool> claimed_{false};
169  
        bool canceled_ = false;
169  
        bool canceled_ = false;
170  
        bool active_ = false;
170  
        bool active_ = false;
171  

171  

172  
        struct cancel_fn
172  
        struct cancel_fn
173  
        {
173  
        {
174  
            lock_awaiter* self_;
174  
            lock_awaiter* self_;
175  

175  

176  
            void operator()() const noexcept
176  
            void operator()() const noexcept
177  
            {
177  
            {
178  
                if(!self_->claimed_.exchange(
178  
                if(!self_->claimed_.exchange(
179  
                    true, std::memory_order_acq_rel))
179  
                    true, std::memory_order_acq_rel))
180  
                {
180  
                {
181  
                    self_->canceled_ = true;
181  
                    self_->canceled_ = true;
182  
                    self_->ex_.dispatch(self_->h_);
182  
                    self_->ex_.dispatch(self_->h_);
183  
                }
183  
                }
184  
            }
184  
            }
185  
        };
185  
        };
186  

186  

187  
        using stop_cb_t =
187  
        using stop_cb_t =
188  
            std::stop_callback<cancel_fn>;
188  
            std::stop_callback<cancel_fn>;
189  

189  

190  
        // Aligned storage for stop_cb_t. Declared last:
190  
        // Aligned storage for stop_cb_t. Declared last:
191  
        // its destructor may block while the callback
191  
        // its destructor may block while the callback
192  
        // accesses the members above.
192  
        // accesses the members above.
193  
#ifdef _MSC_VER
193  
#ifdef _MSC_VER
194  
# pragma warning(push)
194  
# pragma warning(push)
195  
# pragma warning(disable: 4324) // padded due to alignas
195  
# pragma warning(disable: 4324) // padded due to alignas
196  
#endif
196  
#endif
197  
        alignas(stop_cb_t)
197  
        alignas(stop_cb_t)
198  
            unsigned char stop_cb_buf_[sizeof(stop_cb_t)];
198  
            unsigned char stop_cb_buf_[sizeof(stop_cb_t)];
199  
#ifdef _MSC_VER
199  
#ifdef _MSC_VER
200  
# pragma warning(pop)
200  
# pragma warning(pop)
201  
#endif
201  
#endif
202  

202  

203  
        stop_cb_t& stop_cb_() noexcept
203  
        stop_cb_t& stop_cb_() noexcept
204  
        {
204  
        {
205  
            return *reinterpret_cast<stop_cb_t*>(
205  
            return *reinterpret_cast<stop_cb_t*>(
206  
                stop_cb_buf_);
206  
                stop_cb_buf_);
207  
        }
207  
        }
208  

208  

209  
    public:
209  
    public:
210  
        ~lock_awaiter()
210  
        ~lock_awaiter()
211  
        {
211  
        {
212  
            if(active_)
212  
            if(active_)
213  
            {
213  
            {
214  
                stop_cb_().~stop_cb_t();
214  
                stop_cb_().~stop_cb_t();
215  
                m_->waiters_.remove(this);
215  
                m_->waiters_.remove(this);
216  
            }
216  
            }
217  
        }
217  
        }
218  

218  

219  
        explicit lock_awaiter(async_mutex* m) noexcept
219  
        explicit lock_awaiter(async_mutex* m) noexcept
220  
            : m_(m)
220  
            : m_(m)
221  
        {
221  
        {
222  
        }
222  
        }
223  

223  

224  
        lock_awaiter(lock_awaiter&& o) noexcept
224  
        lock_awaiter(lock_awaiter&& o) noexcept
225  
            : m_(o.m_)
225  
            : m_(o.m_)
226  
            , h_(o.h_)
226  
            , h_(o.h_)
227  
            , ex_(o.ex_)
227  
            , ex_(o.ex_)
228  
            , claimed_(o.claimed_.load(
228  
            , claimed_(o.claimed_.load(
229  
                std::memory_order_relaxed))
229  
                std::memory_order_relaxed))
230  
            , canceled_(o.canceled_)
230  
            , canceled_(o.canceled_)
231  
            , active_(std::exchange(o.active_, false))
231  
            , active_(std::exchange(o.active_, false))
232  
        {
232  
        {
233  
        }
233  
        }
234  

234  

235  
        lock_awaiter(lock_awaiter const&) = delete;
235  
        lock_awaiter(lock_awaiter const&) = delete;
236  
        lock_awaiter& operator=(lock_awaiter const&) = delete;
236  
        lock_awaiter& operator=(lock_awaiter const&) = delete;
237  
        lock_awaiter& operator=(lock_awaiter&&) = delete;
237  
        lock_awaiter& operator=(lock_awaiter&&) = delete;
238  

238  

239  
        bool await_ready() const noexcept
239  
        bool await_ready() const noexcept
240  
        {
240  
        {
241  
            if(!m_->locked_)
241  
            if(!m_->locked_)
242  
            {
242  
            {
243  
                m_->locked_ = true;
243  
                m_->locked_ = true;
244  
                return true;
244  
                return true;
245  
            }
245  
            }
246  
            return false;
246  
            return false;
247  
        }
247  
        }
248  

248  

249  
        /** IoAwaitable protocol overload. */
249  
        /** IoAwaitable protocol overload. */
250  
        std::coroutine_handle<>
250  
        std::coroutine_handle<>
251  
        await_suspend(
251  
        await_suspend(
252  
            std::coroutine_handle<> h,
252  
            std::coroutine_handle<> h,
253  
            executor_ref ex,
253  
            executor_ref ex,
254  
            std::stop_token token = {}) noexcept
254  
            std::stop_token token = {}) noexcept
255  
        {
255  
        {
256  
            if(token.stop_requested())
256  
            if(token.stop_requested())
257  
            {
257  
            {
258  
                canceled_ = true;
258  
                canceled_ = true;
259  
                return h;
259  
                return h;
260  
            }
260  
            }
261  
            h_ = h;
261  
            h_ = h;
262  
            ex_ = ex;
262  
            ex_ = ex;
263  
            m_->waiters_.push_back(this);
263  
            m_->waiters_.push_back(this);
264  
            ::new(stop_cb_buf_) stop_cb_t(
264  
            ::new(stop_cb_buf_) stop_cb_t(
265  
                token, cancel_fn{this});
265  
                token, cancel_fn{this});
266  
            active_ = true;
266  
            active_ = true;
267  
            return std::noop_coroutine();
267  
            return std::noop_coroutine();
268  
        }
268  
        }
269  

269  

270  
        io_result<> await_resume() noexcept
270  
        io_result<> await_resume() noexcept
271  
        {
271  
        {
272  
            if(active_)
272  
            if(active_)
273  
            {
273  
            {
274  
                stop_cb_().~stop_cb_t();
274  
                stop_cb_().~stop_cb_t();
275  
                if(canceled_)
275  
                if(canceled_)
276  
                {
276  
                {
277  
                    m_->waiters_.remove(this);
277  
                    m_->waiters_.remove(this);
278  
                    active_ = false;
278  
                    active_ = false;
279  
                    return {make_error_code(
279  
                    return {make_error_code(
280  
                        error::canceled)};
280  
                        error::canceled)};
281  
                }
281  
                }
282  
                active_ = false;
282  
                active_ = false;
283  
            }
283  
            }
284  
            if(canceled_)
284  
            if(canceled_)
285  
                return {make_error_code(
285  
                return {make_error_code(
286  
                    error::canceled)};
286  
                    error::canceled)};
287  
            return {{}};
287  
            return {{}};
288  
        }
288  
        }
289  
    };
289  
    };
290  

290  

291  
    /** RAII lock guard for async_mutex.
291  
    /** RAII lock guard for async_mutex.
292  

292  

293  
        Automatically unlocks the mutex when destroyed.
293  
        Automatically unlocks the mutex when destroyed.
294  
    */
294  
    */
295  
    class [[nodiscard]] lock_guard
295  
    class [[nodiscard]] lock_guard
296  
    {
296  
    {
297  
        async_mutex* m_;
297  
        async_mutex* m_;
298  

298  

299  
    public:
299  
    public:
300  
        ~lock_guard()
300  
        ~lock_guard()
301  
        {
301  
        {
302  
            if(m_)
302  
            if(m_)
303  
                m_->unlock();
303  
                m_->unlock();
304  
        }
304  
        }
305  

305  

306  
        lock_guard() noexcept
306  
        lock_guard() noexcept
307  
            : m_(nullptr)
307  
            : m_(nullptr)
308  
        {
308  
        {
309  
        }
309  
        }
310  

310  

311  
        explicit lock_guard(async_mutex* m) noexcept
311  
        explicit lock_guard(async_mutex* m) noexcept
312  
            : m_(m)
312  
            : m_(m)
313  
        {
313  
        {
314  
        }
314  
        }
315  

315  

316  
        lock_guard(lock_guard&& o) noexcept
316  
        lock_guard(lock_guard&& o) noexcept
317  
            : m_(std::exchange(o.m_, nullptr))
317  
            : m_(std::exchange(o.m_, nullptr))
318  
        {
318  
        {
319  
        }
319  
        }
320  

320  

321  
        lock_guard& operator=(lock_guard&& o) noexcept
321  
        lock_guard& operator=(lock_guard&& o) noexcept
322  
        {
322  
        {
323  
            if(this != &o)
323  
            if(this != &o)
324  
            {
324  
            {
325  
                if(m_)
325  
                if(m_)
326  
                    m_->unlock();
326  
                    m_->unlock();
327  
                m_ = std::exchange(o.m_, nullptr);
327  
                m_ = std::exchange(o.m_, nullptr);
328  
            }
328  
            }
329  
            return *this;
329  
            return *this;
330  
        }
330  
        }
331  

331  

332  
        lock_guard(lock_guard const&) = delete;
332  
        lock_guard(lock_guard const&) = delete;
333  
        lock_guard& operator=(lock_guard const&) = delete;
333  
        lock_guard& operator=(lock_guard const&) = delete;
334  
    };
334  
    };
335  

335  

336  
    /** Awaiter returned by scoped_lock() that returns a lock_guard on resume.
336  
    /** Awaiter returned by scoped_lock() that returns a lock_guard on resume.
337  
    */
337  
    */
338  
    class lock_guard_awaiter
338  
    class lock_guard_awaiter
339  
    {
339  
    {
340  
        async_mutex* m_;
340  
        async_mutex* m_;
341  
        lock_awaiter inner_;
341  
        lock_awaiter inner_;
342  

342  

343  
    public:
343  
    public:
344  
        explicit lock_guard_awaiter(async_mutex* m) noexcept
344  
        explicit lock_guard_awaiter(async_mutex* m) noexcept
345  
            : m_(m)
345  
            : m_(m)
346  
            , inner_(m)
346  
            , inner_(m)
347  
        {
347  
        {
348  
        }
348  
        }
349  

349  

350  
        bool await_ready() const noexcept
350  
        bool await_ready() const noexcept
351  
        {
351  
        {
352  
            return inner_.await_ready();
352  
            return inner_.await_ready();
353  
        }
353  
        }
354  

354  

355  
        /** IoAwaitable protocol overload. */
355  
        /** IoAwaitable protocol overload. */
356  
        std::coroutine_handle<>
356  
        std::coroutine_handle<>
357  
        await_suspend(
357  
        await_suspend(
358  
            std::coroutine_handle<> h,
358  
            std::coroutine_handle<> h,
359  
            executor_ref ex,
359  
            executor_ref ex,
360  
            std::stop_token token = {}) noexcept
360  
            std::stop_token token = {}) noexcept
361  
        {
361  
        {
362  
            return inner_.await_suspend(h, ex, token);
362  
            return inner_.await_suspend(h, ex, token);
363  
        }
363  
        }
364  

364  

365  
        io_result<lock_guard> await_resume() noexcept
365  
        io_result<lock_guard> await_resume() noexcept
366  
        {
366  
        {
367  
            auto r = inner_.await_resume();
367  
            auto r = inner_.await_resume();
368  
            if(r.ec)
368  
            if(r.ec)
369  
                return {r.ec, {}};
369  
                return {r.ec, {}};
370  
            return {{}, lock_guard(m_)};
370  
            return {{}, lock_guard(m_)};
371  
        }
371  
        }
372  
    };
372  
    };
373  

373  

374  
    async_mutex() = default;
374  
    async_mutex() = default;
375  

375  

376  
    // Non-copyable, non-movable
376  
    // Non-copyable, non-movable
377  
    async_mutex(async_mutex const&) = delete;
377  
    async_mutex(async_mutex const&) = delete;
378  
    async_mutex& operator=(async_mutex const&) = delete;
378  
    async_mutex& operator=(async_mutex const&) = delete;
379  

379  

380  
    /** Returns an awaiter that acquires the mutex.
380  
    /** Returns an awaiter that acquires the mutex.
381  

381  

382  
        @return An awaitable yielding `(error_code)`.
382  
        @return An awaitable yielding `(error_code)`.
383  
    */
383  
    */
384  
    lock_awaiter lock() noexcept
384  
    lock_awaiter lock() noexcept
385  
    {
385  
    {
386  
        return lock_awaiter{this};
386  
        return lock_awaiter{this};
387  
    }
387  
    }
388  

388  

389  
    /** Returns an awaiter that acquires the mutex with RAII.
389  
    /** Returns an awaiter that acquires the mutex with RAII.
390  

390  

391  
        @return An awaitable yielding `(error_code,lock_guard)`.
391  
        @return An awaitable yielding `(error_code,lock_guard)`.
392  
    */
392  
    */
393  
    lock_guard_awaiter scoped_lock() noexcept
393  
    lock_guard_awaiter scoped_lock() noexcept
394  
    {
394  
    {
395  
        return lock_guard_awaiter(this);
395  
        return lock_guard_awaiter(this);
396  
    }
396  
    }
397  

397  

398  
    /** Releases the mutex.
398  
    /** Releases the mutex.
399  

399  

400  
        If waiters are queued, the next eligible waiter is
400  
        If waiters are queued, the next eligible waiter is
401  
        resumed with the lock held. Canceled waiters are
401  
        resumed with the lock held. Canceled waiters are
402  
        skipped. If no eligible waiter remains, the mutex
402  
        skipped. If no eligible waiter remains, the mutex
403  
        becomes unlocked.
403  
        becomes unlocked.
404  
    */
404  
    */
405  
    void unlock() noexcept
405  
    void unlock() noexcept
406  
    {
406  
    {
407  
        for(;;)
407  
        for(;;)
408  
        {
408  
        {
409  
            auto* waiter = waiters_.pop_front();
409  
            auto* waiter = waiters_.pop_front();
410  
            if(!waiter)
410  
            if(!waiter)
411  
            {
411  
            {
412  
                locked_ = false;
412  
                locked_ = false;
413  
                return;
413  
                return;
414  
            }
414  
            }
415  
            if(!waiter->claimed_.exchange(
415  
            if(!waiter->claimed_.exchange(
416  
                true, std::memory_order_acq_rel))
416  
                true, std::memory_order_acq_rel))
417  
            {
417  
            {
418  
                waiter->ex_.dispatch(waiter->h_);
418  
                waiter->ex_.dispatch(waiter->h_);
419  
                return;
419  
                return;
420  
            }
420  
            }
421  
        }
421  
        }
422  
    }
422  
    }
423  

423  

424  
    /** Returns true if the mutex is currently locked.
424  
    /** Returns true if the mutex is currently locked.
425  
    */
425  
    */
426  
    bool is_locked() const noexcept
426  
    bool is_locked() const noexcept
427  
    {
427  
    {
428  
        return locked_;
428  
        return locked_;
429  
    }
429  
    }
430  
};
430  
};
431  

431  

432  
} // namespace capy
432  
} // namespace capy
433  
} // namespace boost
433  
} // namespace boost
434  

434  

435  
#endif
435  
#endif