100.00% Lines (93/93) 100.00% Functions (20/20)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) 2   // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3   // Copyright (c) 2026 Michael Vandeberg 3   // Copyright (c) 2026 Michael Vandeberg
4   // 4   //
5   // Distributed under the Boost Software License, Version 1.0. (See accompanying 5   // Distributed under the Boost Software License, Version 1.0. (See accompanying
6   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7   // 7   //
8   // Official repository: https://github.com/cppalliance/capy 8   // Official repository: https://github.com/cppalliance/capy
9   // 9   //
10   10  
11   #ifndef BOOST_CAPY_ASYNC_MUTEX_HPP 11   #ifndef BOOST_CAPY_ASYNC_MUTEX_HPP
12   #define BOOST_CAPY_ASYNC_MUTEX_HPP 12   #define BOOST_CAPY_ASYNC_MUTEX_HPP
13   13  
14   #include <boost/capy/detail/config.hpp> 14   #include <boost/capy/detail/config.hpp>
15   #include <boost/capy/detail/intrusive.hpp> 15   #include <boost/capy/detail/intrusive.hpp>
16   #include <boost/capy/continuation.hpp> 16   #include <boost/capy/continuation.hpp>
17   #include <boost/capy/concept/executor.hpp> 17   #include <boost/capy/concept/executor.hpp>
18   #include <boost/capy/error.hpp> 18   #include <boost/capy/error.hpp>
19   #include <boost/capy/ex/io_env.hpp> 19   #include <boost/capy/ex/io_env.hpp>
20   #include <boost/capy/io_result.hpp> 20   #include <boost/capy/io_result.hpp>
21   21  
22   #include <stop_token> 22   #include <stop_token>
23   23  
24   #include <atomic> 24   #include <atomic>
25   #include <coroutine> 25   #include <coroutine>
26   #include <new> 26   #include <new>
27   #include <utility> 27   #include <utility>
28   28  
29   /* async_mutex implementation notes 29   /* async_mutex implementation notes
30   ================================ 30   ================================
31   31  
32   Waiters form a doubly-linked intrusive list (fair FIFO). lock_awaiter 32   Waiters form a doubly-linked intrusive list (fair FIFO). lock_awaiter
33   inherits intrusive_list<lock_awaiter>::node; the list is owned by 33   inherits intrusive_list<lock_awaiter>::node; the list is owned by
34   async_mutex::waiters_. 34   async_mutex::waiters_.
35   35  
36   Cancellation via stop_token 36   Cancellation via stop_token
37   --------------------------- 37   ---------------------------
38   A std::stop_callback is registered in await_suspend. Two actors can 38   A std::stop_callback is registered in await_suspend. Two actors can
39   race to resume the suspended coroutine: unlock() and the stop callback. 39   race to resume the suspended coroutine: unlock() and the stop callback.
40   An atomic bool `claimed_` resolves the race -- whoever does 40   An atomic bool `claimed_` resolves the race -- whoever does
41   claimed_.exchange(true) and reads false wins. The loser does nothing. 41   claimed_.exchange(true) and reads false wins. The loser does nothing.
42   42  
43   The stop callback calls ex_.post(h_). The stop_callback is 43   The stop callback calls ex_.post(h_). The stop_callback is
44   destroyed later in await_resume. cancel_fn touches no members 44   destroyed later in await_resume. cancel_fn touches no members
45   after post returns (same pattern as delete-this). 45   after post 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 post. It never touches the 83   claimed_ (atomic) and then calls post. 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   /** Queues coroutines in `lock()` and resumes exactly one when the mutex is free. 94   /** Queues coroutines in `lock()` and resumes exactly one when the mutex is free.
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   Distinct objects: Safe.@n 118   Distinct objects: Safe.@n
119   Shared objects: Unsafe. 119   Shared objects: Unsafe.
120   120  
121   The mutex operations are designed for single-threaded use on one 121   The mutex operations are designed for single-threaded use on one
122   executor. The stop callback may fire from any thread. 122   executor. The stop callback may fire from any thread.
123   123  
124   This type is non-copyable and non-movable because suspended 124   This type is non-copyable and non-movable because suspended
125   waiters hold intrusive pointers into the mutex's internal list. 125   waiters hold intrusive pointers into the mutex's internal list.
126   126  
127   @par Example 127   @par Example
128   @par !example example 128   @par !example example
129   129  
130   */ 130   */
131   class async_mutex 131   class async_mutex
132   { 132   {
133   public: 133   public:
134   class lock_awaiter; 134   class lock_awaiter;
135   class lock_guard; 135   class lock_guard;
136   class lock_guard_awaiter; 136   class lock_guard_awaiter;
137   137  
138   private: 138   private:
139   bool locked_ = false; 139   bool locked_ = false;
140   detail::intrusive_list<lock_awaiter> waiters_; 140   detail::intrusive_list<lock_awaiter> waiters_;
141   141  
142   public: 142   public:
143   /** Suspends the caller until the mutex is free, or resumes it with `error::canceled` on a stop request. 143   /** Suspends the caller until the mutex is free, or resumes it with `error::canceled` on a stop request.
144   */ 144   */
145   class lock_awaiter 145   class lock_awaiter
146   : public detail::intrusive_list<lock_awaiter>::node 146   : public detail::intrusive_list<lock_awaiter>::node
147   { 147   {
148   friend class async_mutex; 148   friend class async_mutex;
149   149  
150   async_mutex* m_; 150   async_mutex* m_;
151   continuation cont_; 151   continuation cont_;
152   executor_ref ex_; 152   executor_ref ex_;
153   153  
154   // These members must be declared before stop_cb_ 154   // These members must be declared before stop_cb_
155   // (see comment on the union below). 155   // (see comment on the union below).
156   std::atomic<bool> claimed_{false}; 156   std::atomic<bool> claimed_{false};
157   bool canceled_ = false; 157   bool canceled_ = false;
158   bool active_ = false; 158   bool active_ = false;
159   159  
160   struct cancel_fn 160   struct cancel_fn
161   { 161   {
162   lock_awaiter* self_; 162   lock_awaiter* self_;
163   163  
HITCBC 164   7 void operator()() const noexcept 164   7 void operator()() const noexcept
165   { 165   {
HITCBC 166   7 if(!self_->claimed_.exchange( 166   7 if(!self_->claimed_.exchange(
167   true, std::memory_order_acq_rel)) 167   true, std::memory_order_acq_rel))
168   { 168   {
HITCBC 169   7 self_->canceled_ = true; 169   7 self_->canceled_ = true;
HITCBC 170   7 self_->ex_.post(self_->cont_); 170   7 self_->ex_.post(self_->cont_);
171   } 171   }
HITCBC 172   7 } 172   7 }
173   }; 173   };
174   174  
175   using stop_cb_t = 175   using stop_cb_t =
176   std::stop_callback<cancel_fn>; 176   std::stop_callback<cancel_fn>;
177   177  
178   // Aligned storage for stop_cb_t. Declared last: 178   // Aligned storage for stop_cb_t. Declared last:
179   // its destructor may block while the callback 179   // its destructor may block while the callback
180   // accesses the members above. 180   // accesses the members above.
181   BOOST_CAPY_MSVC_WARNING_PUSH 181   BOOST_CAPY_MSVC_WARNING_PUSH
182   BOOST_CAPY_MSVC_WARNING_DISABLE(4324) // padded due to alignas 182   BOOST_CAPY_MSVC_WARNING_DISABLE(4324) // padded due to alignas
183   alignas(stop_cb_t) 183   alignas(stop_cb_t)
184   unsigned char stop_cb_buf_[sizeof(stop_cb_t)]; 184   unsigned char stop_cb_buf_[sizeof(stop_cb_t)];
185   BOOST_CAPY_MSVC_WARNING_POP 185   BOOST_CAPY_MSVC_WARNING_POP
186   186  
HITCBC 187   19 stop_cb_t& stop_cb_() noexcept 187   19 stop_cb_t& stop_cb_() noexcept
188   { 188   {
189   return *reinterpret_cast<stop_cb_t*>( 189   return *reinterpret_cast<stop_cb_t*>(
HITCBC 190   19 stop_cb_buf_); 190   19 stop_cb_buf_);
191   } 191   }
192   192  
193   public: 193   public:
194   /** Destroy the awaiter, leaving the mutex unable to reach it. 194   /** Destroy the awaiter, leaving the mutex unable to reach it.
195   195  
196   If the awaiter is suspended in the wait queue, destroys the 196   If the awaiter is suspended in the wait queue, destroys the
197   stop callback and unlinks the awaiter. Neither `unlock()` nor 197   stop callback and unlinks the awaiter. Neither `unlock()` nor
198   the stop callback can then reach a destroyed awaiter when the 198   the stop callback can then reach a destroyed awaiter when the
199   coroutine frame is torn down while suspended. 199   coroutine frame is torn down while suspended.
200   200  
201   @par Preconditions 201   @par Preconditions
202   Called on the executor thread. The stop callback may fire from 202   Called on the executor thread. The stop callback may fire from
203   any thread, so destroying a still-suspended awaiter from 203   any thread, so destroying a still-suspended awaiter from
204   another thread is undefined. 204   another thread is undefined.
205   */ 205   */
HITCBC 206   76 ~lock_awaiter() 206   76 ~lock_awaiter()
207   { 207   {
HITCBC 208   76 if(active_) 208   76 if(active_)
209   { 209   {
HITCBC 210   3 stop_cb_().~stop_cb_t(); 210   3 stop_cb_().~stop_cb_t();
HITCBC 211   3 m_->waiters_.remove(this); 211   3 m_->waiters_.remove(this);
212   } 212   }
HITCBC 213   76 } 213   76 }
214   214  
215   /** Construct an awaiter for the given mutex. 215   /** Construct an awaiter for the given mutex.
216   216  
217   @param m The mutex to acquire. It must outlive the awaiter. 217   @param m The mutex to acquire. It must outlive the awaiter.
218   */ 218   */
HITCBC 219   38 explicit lock_awaiter(async_mutex* m) noexcept 219   38 explicit lock_awaiter(async_mutex* m) noexcept
HITCBC 220   38 : m_(m) 220   38 : m_(m)
221   { 221   {
HITCBC 222   38 } 222   38 }
223   223  
224   /** Construct by moving. 224   /** Construct by moving.
225   225  
226   The moved-from awaiter is left inert: its destructor no longer 226   The moved-from awaiter is left inert: its destructor no longer
227   destroys the stop callback and no longer unlinks from the 227   destroys the stop callback and no longer unlinks from the
228   mutex's wait queue. 228   mutex's wait queue.
229   229  
230   @param o The awaiter to move from. 230   @param o The awaiter to move from.
231   */ 231   */
HITCBC 232   38 lock_awaiter(lock_awaiter&& o) noexcept 232   38 lock_awaiter(lock_awaiter&& o) noexcept
HITCBC 233   76 : m_(o.m_) 233   76 : m_(o.m_)
HITCBC 234   38 , cont_(o.cont_) 234   38 , cont_(o.cont_)
HITCBC 235   38 , ex_(o.ex_) 235   38 , ex_(o.ex_)
HITCBC 236   38 , claimed_(o.claimed_.load( 236   38 , claimed_(o.claimed_.load(
237   std::memory_order_relaxed)) 237   std::memory_order_relaxed))
HITCBC 238   38 , canceled_(o.canceled_) 238   38 , canceled_(o.canceled_)
HITCBC 239   76 , active_(std::exchange(o.active_, false)) 239   76 , active_(std::exchange(o.active_, false))
240   { 240   {
HITCBC 241   38 } 241   38 }
242   242  
243   /** Copy construction is disabled; a waiter is linked into the 243   /** Copy construction is disabled; a waiter is linked into the
244   mutex's wait queue by address. 244   mutex's wait queue by address.
245   245  
246   @param other The awaiter that would be copied. 246   @param other The awaiter that would be copied.
247   */ 247   */
248   lock_awaiter(lock_awaiter const& other) = delete; 248   lock_awaiter(lock_awaiter const& other) = delete;
249   249  
250   /** Copy assignment is disabled; a waiter is linked into the 250   /** Copy assignment is disabled; a waiter is linked into the
251   mutex's wait queue by address. 251   mutex's wait queue by address.
252   252  
253   @param other The awaiter that would be assigned from. 253   @param other The awaiter that would be assigned from.
254   254  
255   @return A reference to `*this`. 255   @return A reference to `*this`.
256   */ 256   */
257   lock_awaiter& operator=(lock_awaiter const& other) = delete; 257   lock_awaiter& operator=(lock_awaiter const& other) = delete;
258   258  
259   /** Move assignment is disabled; a waiter is linked into the 259   /** Move assignment is disabled; a waiter is linked into the
260   mutex's wait queue by address. 260   mutex's wait queue by address.
261   261  
262   @param other The awaiter that would be moved from. 262   @param other The awaiter that would be moved from.
263   263  
264   @return A reference to `*this`. 264   @return A reference to `*this`.
265   */ 265   */
266   lock_awaiter& operator=(lock_awaiter&& other) = delete; 266   lock_awaiter& operator=(lock_awaiter&& other) = delete;
267   267  
268   /** Acquire the mutex if it is free, reporting whether to suspend. 268   /** Acquire the mutex if it is free, reporting whether to suspend.
269   269  
270   This is not a pure query: on the fast path it takes the lock. 270   This is not a pure query: on the fast path it takes the lock.
271   When the mutex is unlocked, it marks the mutex locked and 271   When the mutex is unlocked, it marks the mutex locked and
272   reports that no suspension is needed. The stop token is not 272   reports that no suspension is needed. The stop token is not
273   consulted, so an uncontended `lock()` succeeds even when stop 273   consulted, so an uncontended `lock()` succeeds even when stop
274   has already been requested. 274   has already been requested.
275   275  
276   @return `true` if the mutex was free and is now held by the 276   @return `true` if the mutex was free and is now held by the
277   awaiting coroutine. `false` if the mutex is held elsewhere, in 277   awaiting coroutine. `false` if the mutex is held elsewhere, in
278   which case the coroutine suspends. 278   which case the coroutine suspends.
279   */ 279   */
HITCBC 280   38 bool await_ready() const noexcept 280   38 bool await_ready() const noexcept
281   { 281   {
HITCBC 282   38 if(!m_->locked_) 282   38 if(!m_->locked_)
283   { 283   {
HITCBC 284   17 m_->locked_ = true; 284   17 m_->locked_ = true;
HITCBC 285   17 return true; 285   17 return true;
286   } 286   }
HITCBC 287   21 return false; 287   21 return false;
288   } 288   }
289   289  
290   /** Enqueue the awaiting coroutine until the mutex is released. 290   /** Enqueue the awaiting coroutine until the mutex is released.
291   291  
292   This is the @ref IoAwaitable overload of `await_suspend`. 292   This is the @ref IoAwaitable overload of `await_suspend`.
293   293  
294   If a stop request is already pending on `env->stop_token`, the 294   If a stop request is already pending on `env->stop_token`, the
295   awaiter records the cancellation and does not enqueue. The 295   awaiter records the cancellation and does not enqueue. The
296   mutex is not acquired. 296   mutex is not acquired.
297   297  
298   Otherwise it stores `h` and `env->executor`, links itself into 298   Otherwise it stores `h` and `env->executor`, links itself into
299   the back of the mutex's wait queue, and registers a stop 299   the back of the mutex's wait queue, and registers a stop
300   callback on `env->stop_token`. Whichever of `unlock()` and that 300   callback on `env->stop_token`. Whichever of `unlock()` and that
301   callback claims the awaiter first posts `h` through the stored 301   callback claims the awaiter first posts `h` through the stored
302   executor; the other skips it. 302   executor; the other skips it.
303   303  
304   @param h The awaiting coroutine, resumed when the mutex is 304   @param h The awaiting coroutine, resumed when the mutex is
305   acquired or the wait is canceled. 305   acquired or the wait is canceled.
306   306  
307   @param env The execution environment. Its executor posts the 307   @param env The execution environment. Its executor posts the
308   resumption and its stop token is watched for the duration of 308   resumption and its stop token is watched for the duration of
309   the wait. It must outlive the wait. 309   the wait. It must outlive the wait.
310   310  
311   @return `h` if a stop request was already pending, which 311   @return `h` if a stop request was already pending, which
312   resumes the awaiting coroutine immediately without enqueuing 312   resumes the awaiting coroutine immediately without enqueuing
313   it. Otherwise `std::noop_coroutine()`, which leaves the 313   it. Otherwise `std::noop_coroutine()`, which leaves the
314   coroutine suspended and returns control to the resumer. 314   coroutine suspended and returns control to the resumer.
315   */ 315   */
316   std::coroutine_handle<> 316   std::coroutine_handle<>
HITCBC 317   21 await_suspend( 317   21 await_suspend(
318   std::coroutine_handle<> h, 318   std::coroutine_handle<> h,
319   io_env const* env) noexcept 319   io_env const* env) noexcept
320   { 320   {
HITCBC 321   21 if(env->stop_token.stop_requested()) 321   21 if(env->stop_token.stop_requested())
322   { 322   {
HITCBC 323   2 canceled_ = true; 323   2 canceled_ = true;
HITCBC 324   2 return h; 324   2 return h;
325   } 325   }
HITCBC 326   19 cont_.h = h; 326   19 cont_.h = h;
HITCBC 327   19 ex_ = env->executor; 327   19 ex_ = env->executor;
HITCBC 328   19 m_->waiters_.push_back(this); 328   19 m_->waiters_.push_back(this);
HITCBC 329   57 ::new(stop_cb_buf_) stop_cb_t( 329   57 ::new(stop_cb_buf_) stop_cb_t(
HITCBC 330   19 env->stop_token, cancel_fn{this}); 330   19 env->stop_token, cancel_fn{this});
HITCBC 331   19 active_ = true; 331   19 active_ = true;
HITCBC 332   19 return std::noop_coroutine(); 332   19 return std::noop_coroutine();
333   } 333   }
334   334  
335   /** Complete the acquisition and report the outcome. 335   /** Complete the acquisition and report the outcome.
336   336  
337   Destroys the stop callback if one is registered, and unlinks a 337   Destroys the stop callback if one is registered, and unlinks a
338   canceled awaiter from the wait queue. 338   canceled awaiter from the wait queue.
339   339  
340   @return An empty `io_result<>` if the mutex is now held by the 340   @return An empty `io_result<>` if the mutex is now held by the
341   awaiting coroutine. Otherwise one holding `error::canceled`, 341   awaiting coroutine. Otherwise one holding `error::canceled`,
342   which means the stop token won the race and the mutex is not 342   which means the stop token won the race and the mutex is not
343   held. 343   held.
344   */ 344   */
HITCBC 345   35 [[nodiscard]] io_result<> await_resume() noexcept 345   35 [[nodiscard]] io_result<> await_resume() noexcept
346   { 346   {
HITCBC 347   35 if(active_) 347   35 if(active_)
348   { 348   {
HITCBC 349   16 stop_cb_().~stop_cb_t(); 349   16 stop_cb_().~stop_cb_t();
HITCBC 350   16 if(canceled_) 350   16 if(canceled_)
351   { 351   {
HITCBC 352   7 m_->waiters_.remove(this); 352   7 m_->waiters_.remove(this);
HITCBC 353   7 active_ = false; 353   7 active_ = false;
HITCBC 354   14 return {make_error_code( 354   14 return {make_error_code(
HITCBC 355   7 error::canceled)}; 355   7 error::canceled)};
356   } 356   }
HITCBC 357   9 active_ = false; 357   9 active_ = false;
358   } 358   }
HITCBC 359   28 if(canceled_) 359   28 if(canceled_)
HITCBC 360   4 return {make_error_code( 360   4 return {make_error_code(
HITCBC 361   2 error::canceled)}; 361   2 error::canceled)};
HITCBC 362   26 return {{}}; 362   26 return {{}};
363   } 363   }
364   }; 364   };
365   365  
366   /** Unlocks the mutex automatically when destroyed. 366   /** Unlocks the mutex automatically when destroyed.
367   */ 367   */
368   class [[nodiscard]] lock_guard 368   class [[nodiscard]] lock_guard
369   { 369   {
370   async_mutex* m_; 370   async_mutex* m_;
371   371  
372   public: 372   public:
373   /// Unlock the mutex, if this guard holds one. 373   /// Unlock the mutex, if this guard holds one.
HITCBC 374   9 ~lock_guard() 374   9 ~lock_guard()
375   { 375   {
HITCBC 376   9 if(m_) 376   9 if(m_)
HITCBC 377   2 m_->unlock(); 377   2 m_->unlock();
HITCBC 378   9 } 378   9 }
379   379  
380   /// Construct a guard that holds no mutex. 380   /// Construct a guard that holds no mutex.
HITCBC 381   2 lock_guard() noexcept 381   2 lock_guard() noexcept
HITCBC 382   2 : m_(nullptr) 382   2 : m_(nullptr)
383   { 383   {
HITCBC 384   2 } 384   2 }
385   385  
386   /** Construct a guard that releases the given mutex on destruction. 386   /** Construct a guard that releases the given mutex on destruction.
387   387  
388   Adopts an already-held lock; it does not acquire one. 388   Adopts an already-held lock; it does not acquire one.
389   389  
390   @param m The mutex to unlock on destruction. It must outlive 390   @param m The mutex to unlock on destruction. It must outlive
391   the guard. 391   the guard.
392   */ 392   */
HITCBC 393   2 explicit lock_guard(async_mutex* m) noexcept 393   2 explicit lock_guard(async_mutex* m) noexcept
HITCBC 394   2 : m_(m) 394   2 : m_(m)
395   { 395   {
HITCBC 396   2 } 396   2 }
397   397  
398   /** Construct by moving, transferring the lock. 398   /** Construct by moving, transferring the lock.
399   399  
400   @par Postconditions 400   @par Postconditions
401   `o` holds no mutex, and its destructor unlocks nothing. 401   `o` holds no mutex, and its destructor unlocks nothing.
402   402  
403   @param o The guard to move from. 403   @param o The guard to move from.
404   */ 404   */
HITCBC 405   5 lock_guard(lock_guard&& o) noexcept 405   5 lock_guard(lock_guard&& o) noexcept
HITCBC 406   5 : m_(std::exchange(o.m_, nullptr)) 406   5 : m_(std::exchange(o.m_, nullptr))
407   { 407   {
HITCBC 408   5 } 408   5 }
409   409  
410   /** Assign by moving, transferring the lock. 410   /** Assign by moving, transferring the lock.
411   411  
412   If this guard already holds a mutex, that mutex is unlocked 412   If this guard already holds a mutex, that mutex is unlocked
413   first. Self-assignment is a no-op. 413   first. Self-assignment is a no-op.
414   414  
415   @par Postconditions 415   @par Postconditions
416   `o` holds no mutex, and its destructor unlocks nothing. 416   `o` holds no mutex, and its destructor unlocks nothing.
417   417  
418   @param o The guard to move from. 418   @param o The guard to move from.
419   419  
420   @return A reference to `*this`. 420   @return A reference to `*this`.
421   */ 421   */
422   lock_guard& operator=(lock_guard&& o) noexcept 422   lock_guard& operator=(lock_guard&& o) noexcept
423   { 423   {
424   if(this != &o) 424   if(this != &o)
425   { 425   {
426   if(m_) 426   if(m_)
427   m_->unlock(); 427   m_->unlock();
428   m_ = std::exchange(o.m_, nullptr); 428   m_ = std::exchange(o.m_, nullptr);
429   } 429   }
430   return *this; 430   return *this;
431   } 431   }
432   432  
433   /** Copy construction is disabled; a guard uniquely owns the lock. 433   /** Copy construction is disabled; a guard uniquely owns the lock.
434   434  
435   @param other The guard that would be copied. 435   @param other The guard that would be copied.
436   */ 436   */
437   lock_guard(lock_guard const& other) = delete; 437   lock_guard(lock_guard const& other) = delete;
438   438  
439   /** Copy assignment is disabled; a guard uniquely owns the lock. 439   /** Copy assignment is disabled; a guard uniquely owns the lock.
440   440  
441   @param other The guard that would be assigned from. 441   @param other The guard that would be assigned from.
442   442  
443   @return A reference to `*this`. 443   @return A reference to `*this`.
444   */ 444   */
445   lock_guard& operator=(lock_guard const& other) = delete; 445   lock_guard& operator=(lock_guard const& other) = delete;
446   }; 446   };
447   447  
448   /** Acquires the mutex like `lock_awaiter`, then resumes with a `lock_guard` that unlocks it. 448   /** Acquires the mutex like `lock_awaiter`, then resumes with a `lock_guard` that unlocks it.
449   */ 449   */
450   class lock_guard_awaiter 450   class lock_guard_awaiter
451   { 451   {
452   async_mutex* m_; 452   async_mutex* m_;
453   lock_awaiter inner_; 453   lock_awaiter inner_;
454   454  
455   public: 455   public:
456   /** Construct an awaiter for the given mutex. 456   /** Construct an awaiter for the given mutex.
457   457  
458   @param m The mutex to acquire. It must outlive the awaiter. 458   @param m The mutex to acquire. It must outlive the awaiter.
459   */ 459   */
HITCBC 460   4 explicit lock_guard_awaiter(async_mutex* m) noexcept 460   4 explicit lock_guard_awaiter(async_mutex* m) noexcept
HITCBC 461   4 : m_(m) 461   4 : m_(m)
HITCBC 462   4 , inner_(m) 462   4 , inner_(m)
463   { 463   {
HITCBC 464   4 } 464   4 }
465   465  
466   /** Acquire the mutex if it is free, reporting whether to suspend. 466   /** Acquire the mutex if it is free, reporting whether to suspend.
467   467  
468   Delegates to @ref lock_awaiter::await_ready, so as there this is 468   Delegates to @ref lock_awaiter::await_ready, so as there this is
469   not a pure query: on the fast path it takes the lock. 469   not a pure query: on the fast path it takes the lock.
470   470  
471   @return `true` if the mutex was free and is now held by the 471   @return `true` if the mutex was free and is now held by the
472   awaiting coroutine. `false` if the mutex is held elsewhere, in 472   awaiting coroutine. `false` if the mutex is held elsewhere, in
473   which case the coroutine suspends. 473   which case the coroutine suspends.
474   */ 474   */
HITCBC 475   4 bool await_ready() const noexcept 475   4 bool await_ready() const noexcept
476   { 476   {
HITCBC 477   4 return inner_.await_ready(); 477   4 return inner_.await_ready();
478   } 478   }
479   479  
480   /** Enqueue the awaiting coroutine until the mutex is released. 480   /** Enqueue the awaiting coroutine until the mutex is released.
481   481  
482   This is the @ref IoAwaitable overload of `await_suspend`. It 482   This is the @ref IoAwaitable overload of `await_suspend`. It
483   delegates to @ref lock_awaiter::await_suspend on the wrapped 483   delegates to @ref lock_awaiter::await_suspend on the wrapped
484   awaiter, so it has that function's contract. 484   awaiter, so it has that function's contract.
485   485  
486   @param h The awaiting coroutine, resumed when the mutex is 486   @param h The awaiting coroutine, resumed when the mutex is
487   acquired or the wait is canceled. 487   acquired or the wait is canceled.
488   488  
489   @param env The execution environment. Its executor posts the 489   @param env The execution environment. Its executor posts the
490   resumption and its stop token is watched for the duration of 490   resumption and its stop token is watched for the duration of
491   the wait. It must outlive the wait. 491   the wait. It must outlive the wait.
492   492  
493   @return `h` if a stop request was already pending, which 493   @return `h` if a stop request was already pending, which
494   resumes the awaiting coroutine immediately without enqueuing 494   resumes the awaiting coroutine immediately without enqueuing
495   it. Otherwise `std::noop_coroutine()`, which leaves the 495   it. Otherwise `std::noop_coroutine()`, which leaves the
496   coroutine suspended and returns control to the resumer. 496   coroutine suspended and returns control to the resumer.
497   */ 497   */
498   std::coroutine_handle<> 498   std::coroutine_handle<>
HITCBC 499   2 await_suspend( 499   2 await_suspend(
500   std::coroutine_handle<> h, 500   std::coroutine_handle<> h,
501   io_env const* env) noexcept 501   io_env const* env) noexcept
502   { 502   {
HITCBC 503   2 return inner_.await_suspend(h, env); 503   2 return inner_.await_suspend(h, env);
504   } 504   }
505   505  
506   /** Complete the acquisition and report the outcome. 506   /** Complete the acquisition and report the outcome.
507   507  
508   @return An `io_result<lock_guard>` destructuring as 508   @return An `io_result<lock_guard>` destructuring as
509   `[ec, guard]`. On success `ec` is empty and `guard` holds the 509   `[ec, guard]`. On success `ec` is empty and `guard` holds the
510   mutex, releasing it when destroyed. If the wait was canceled, 510   mutex, releasing it when destroyed. If the wait was canceled,
511   `ec` is `error::canceled` and `guard` holds no mutex. 511   `ec` is `error::canceled` and `guard` holds no mutex.
512   */ 512   */
HITCBC 513   4 [[nodiscard]] io_result<lock_guard> await_resume() noexcept 513   4 [[nodiscard]] io_result<lock_guard> await_resume() noexcept
514   { 514   {
HITCBC 515   4 auto r = inner_.await_resume(); 515   4 auto r = inner_.await_resume();
HITCBC 516   4 if(std::get<0>(r)) 516   4 if(std::get<0>(r))
HITCBC 517   2 return {std::get<0>(r), lock_guard()}; 517   2 return {std::get<0>(r), lock_guard()};
HITCBC 518   2 return {std::error_code(), lock_guard(m_)}; 518   2 return {std::error_code(), lock_guard(m_)};
519   } 519   }
520   }; 520   };
521   521  
522   /// Construct an unlocked mutex. 522   /// Construct an unlocked mutex.
523   async_mutex() = default; 523   async_mutex() = default;
524   524  
525   /** Copy construction is disabled; suspended waiters point into the 525   /** Copy construction is disabled; suspended waiters point into the
526   mutex's wait queue. 526   mutex's wait queue.
527   527  
528   @param other The mutex that would be copied. 528   @param other The mutex that would be copied.
529   */ 529   */
530   async_mutex(async_mutex const& other) = delete; 530   async_mutex(async_mutex const& other) = delete;
531   531  
532   /** Copy assignment is disabled; suspended waiters point into the 532   /** Copy assignment is disabled; suspended waiters point into the
533   mutex's wait queue. 533   mutex's wait queue.
534   534  
535   @param other The mutex that would be assigned from. 535   @param other The mutex that would be assigned from.
536   536  
537   @return A reference to `*this`. 537   @return A reference to `*this`.
538   */ 538   */
539   async_mutex& operator=(async_mutex const& other) = delete; 539   async_mutex& operator=(async_mutex const& other) = delete;
540   540  
541   /** Move construction is disabled; suspended waiters point into the 541   /** Move construction is disabled; suspended waiters point into the
542   mutex's wait queue. 542   mutex's wait queue.
543   543  
544   @param other The mutex that would be moved from. 544   @param other The mutex that would be moved from.
545   */ 545   */
546   async_mutex(async_mutex&& other) = delete; 546   async_mutex(async_mutex&& other) = delete;
547   547  
548   /** Move assignment is disabled; suspended waiters point into the 548   /** Move assignment is disabled; suspended waiters point into the
549   mutex's wait queue. 549   mutex's wait queue.
550   550  
551   @param other The mutex that would be moved from. 551   @param other The mutex that would be moved from.
552   552  
553   @return A reference to `*this`. 553   @return A reference to `*this`.
554   */ 554   */
555   async_mutex& operator=(async_mutex&& other) = delete; 555   async_mutex& operator=(async_mutex&& other) = delete;
556   556  
557   /** Returns an awaiter that acquires the mutex. 557   /** Returns an awaiter that acquires the mutex.
558   558  
559   @return An awaitable that await-returns `(error_code)`. 559   @return An awaitable that await-returns `(error_code)`.
560   */ 560   */
HITCBC 561   34 lock_awaiter lock() noexcept 561   34 lock_awaiter lock() noexcept
562   { 562   {
HITCBC 563   34 return lock_awaiter{this}; 563   34 return lock_awaiter{this};
564   } 564   }
565   565  
566   /** Returns an awaiter that acquires the mutex with RAII. 566   /** Returns an awaiter that acquires the mutex with RAII.
567   567  
568   @return An awaitable that await-returns `(error_code,lock_guard)`. 568   @return An awaitable that await-returns `(error_code,lock_guard)`.
569   */ 569   */
HITCBC 570   4 lock_guard_awaiter scoped_lock() noexcept 570   4 lock_guard_awaiter scoped_lock() noexcept
571   { 571   {
HITCBC 572   4 return lock_guard_awaiter(this); 572   4 return lock_guard_awaiter(this);
573   } 573   }
574   574  
575   /** Releases the mutex. 575   /** Releases the mutex.
576   576  
577   If waiters are queued, the next eligible waiter is 577   If waiters are queued, the next eligible waiter is
578   resumed with the lock held. Canceled waiters are 578   resumed with the lock held. Canceled waiters are
579   skipped. If no eligible waiter remains, the mutex 579   skipped. If no eligible waiter remains, the mutex
580   becomes unlocked. 580   becomes unlocked.
581   */ 581   */
HITCBC 582   26 void unlock() noexcept 582   26 void unlock() noexcept
583   { 583   {
584   for(;;) 584   for(;;)
585   { 585   {
HITCBC 586   27 auto* waiter = waiters_.pop_front(); 586   27 auto* waiter = waiters_.pop_front();
HITCBC 587   27 if(!waiter) 587   27 if(!waiter)
588   { 588   {
HITCBC 589   17 locked_ = false; 589   17 locked_ = false;
HITCBC 590   17 return; 590   17 return;
591   } 591   }
HITCBC 592   10 if(!waiter->claimed_.exchange( 592   10 if(!waiter->claimed_.exchange(
593   true, std::memory_order_acq_rel)) 593   true, std::memory_order_acq_rel))
594   { 594   {
HITCBC 595   9 waiter->ex_.post(waiter->cont_); 595   9 waiter->ex_.post(waiter->cont_);
HITCBC 596   9 return; 596   9 return;
597   } 597   }
HITCBC 598   1 } 598   1 }
599   } 599   }
600   600  
601   /** Returns true if the mutex is currently locked. 601   /** Returns true if the mutex is currently locked.
602   602  
603   @return `true` if the mutex is held; otherwise `false`. 603   @return `true` if the mutex is held; otherwise `false`.
604   */ 604   */
HITCBC 605   27 bool is_locked() const noexcept 605   27 bool is_locked() const noexcept
606   { 606   {
HITCBC 607   27 return locked_; 607   27 return locked_;
608   } 608   }
609   }; 609   };
610   610  
611   } // namespace capy 611   } // namespace capy
612   } // namespace boost 612   } // namespace boost
613   613  
614   #endif 614   #endif