98.90% Lines (90/91) 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   // 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/continuation.hpp>
15   #include <boost/capy/concept/executor.hpp> 16   #include <boost/capy/concept/executor.hpp>
16   #include <boost/capy/error.hpp> 17   #include <boost/capy/error.hpp>
17   #include <boost/capy/ex/io_env.hpp> 18   #include <boost/capy/ex/io_env.hpp>
18   #include <boost/capy/io_result.hpp> 19   #include <boost/capy/io_result.hpp>
19   20  
20   #include <stop_token> 21   #include <stop_token>
21   22  
22   #include <atomic> 23   #include <atomic>
23   #include <coroutine> 24   #include <coroutine>
24   #include <new> 25   #include <new>
25   #include <utility> 26   #include <utility>
26   27  
27   /* async_mutex implementation notes 28   /* async_mutex implementation notes
28   ================================ 29   ================================
29   30  
30   Waiters form a doubly-linked intrusive list (fair FIFO). lock_awaiter 31   Waiters form a doubly-linked intrusive list (fair FIFO). lock_awaiter
31   inherits intrusive_list<lock_awaiter>::node; the list is owned by 32   inherits intrusive_list<lock_awaiter>::node; the list is owned by
32   async_mutex::waiters_. 33   async_mutex::waiters_.
33   34  
34   Cancellation via stop_token 35   Cancellation via stop_token
35   --------------------------- 36   ---------------------------
36   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
37   race to resume the suspended coroutine: unlock() and the stop callback. 38   race to resume the suspended coroutine: unlock() and the stop callback.
38   An atomic bool `claimed_` resolves the race -- whoever does 39   An atomic bool `claimed_` resolves the race -- whoever does
39   claimed_.exchange(true) and reads false wins. The loser does nothing. 40   claimed_.exchange(true) and reads false wins. The loser does nothing.
40   41  
41   The stop callback calls ex_.post(h_). The stop_callback is 42   The stop callback calls ex_.post(h_). The stop_callback is
42   destroyed later in await_resume. cancel_fn touches no members 43   destroyed later in await_resume. cancel_fn touches no members
43   after post returns (same pattern as delete-this). 44   after post returns (same pattern as delete-this).
44   45  
45   unlock() pops waiters from the front. If the popped waiter was 46   unlock() pops waiters from the front. If the popped waiter was
46   already claimed by the stop callback, unlock() skips it and tries 47   already claimed by the stop callback, unlock() skips it and tries
47   the next. await_resume removes the (still-linked) canceled waiter 48   the next. await_resume removes the (still-linked) canceled waiter
48   via waiters_.remove(this). 49   via waiters_.remove(this).
49   50  
50   The stop_callback lives in a union to suppress automatic 51   The stop_callback lives in a union to suppress automatic
51   construction/destruction. Placement new in await_suspend, explicit 52   construction/destruction. Placement new in await_suspend, explicit
52   destructor call in await_resume and ~lock_awaiter. 53   destructor call in await_resume and ~lock_awaiter.
53   54  
54   Member ordering constraint 55   Member ordering constraint
55   -------------------------- 56   --------------------------
56   The union containing stop_cb_ must be declared AFTER the members 57   The union containing stop_cb_ must be declared AFTER the members
57   the callback accesses (h_, ex_, claimed_, canceled_). If the 58   the callback accesses (h_, ex_, claimed_, canceled_). If the
58   stop_cb_ destructor blocks waiting for a concurrent callback, those 59   stop_cb_ destructor blocks waiting for a concurrent callback, those
59   members must still be alive (C++ destroys in reverse declaration 60   members must still be alive (C++ destroys in reverse declaration
60   order). 61   order).
61   62  
62   active_ flag 63   active_ flag
63   ------------ 64   ------------
64   Tracks both list membership and stop_cb_ lifetime (they are always 65   Tracks both list membership and stop_cb_ lifetime (they are always
65   set and cleared together). Used by the destructor to clean up if the 66   set and cleared together). Used by the destructor to clean up if the
66   coroutine is destroyed while suspended (e.g. execution_context 67   coroutine is destroyed while suspended (e.g. execution_context
67   shutdown). 68   shutdown).
68   69  
69   Cancellation scope 70   Cancellation scope
70   ------------------ 71   ------------------
71   Cancellation only takes effect while the coroutine is suspended in 72   Cancellation only takes effect while the coroutine is suspended in
72   the wait queue. If the mutex is unlocked, await_ready acquires it 73   the wait queue. If the mutex is unlocked, await_ready acquires it
73   immediately without checking the stop token. This is intentional: 74   immediately without checking the stop token. This is intentional:
74   the fast path has no token access and no overhead. 75   the fast path has no token access and no overhead.
75   76  
76   Threading assumptions 77   Threading assumptions
77   --------------------- 78   ---------------------
78   - All list mutations happen on the executor thread (await_suspend, 79   - All list mutations happen on the executor thread (await_suspend,
79   await_resume, unlock, ~lock_awaiter). 80   await_resume, unlock, ~lock_awaiter).
80   - The stop callback may fire from any thread, but only touches 81   - The stop callback may fire from any thread, but only touches
81   claimed_ (atomic) and then calls post. It never touches the 82   claimed_ (atomic) and then calls post. It never touches the
82   list. 83   list.
83   - ~lock_awaiter must be called from the executor thread. This is 84   - ~lock_awaiter must be called from the executor thread. This is
84   guaranteed during normal shutdown but NOT if the coroutine frame 85   guaranteed during normal shutdown but NOT if the coroutine frame
85   is destroyed from another thread while a stop callback could 86   is destroyed from another thread while a stop callback could
86   fire (precondition violation, same as cppcoro/folly). 87   fire (precondition violation, same as cppcoro/folly).
87   */ 88   */
88   89  
89   namespace boost { 90   namespace boost {
90   namespace capy { 91   namespace capy {
91   92  
92   /** An asynchronous mutex for coroutines. 93   /** An asynchronous mutex for coroutines.
93   94  
94   This mutex provides mutual exclusion for coroutines without blocking. 95   This mutex provides mutual exclusion for coroutines without blocking.
95   When a coroutine attempts to acquire a locked mutex, it suspends and 96   When a coroutine attempts to acquire a locked mutex, it suspends and
96   is added to an intrusive wait queue. When the holder unlocks, the next 97   is added to an intrusive wait queue. When the holder unlocks, the next
97   waiter is resumed with the lock held. 98   waiter is resumed with the lock held.
98   99  
99   @par Cancellation 100   @par Cancellation
100   101  
101   When a coroutine is suspended waiting for the mutex and its stop 102   When a coroutine is suspended waiting for the mutex and its stop
102   token is triggered, the waiter completes with `error::canceled` 103   token is triggered, the waiter completes with `error::canceled`
103   instead of acquiring the lock. 104   instead of acquiring the lock.
104   105  
105   Cancellation only applies while the coroutine is suspended in the 106   Cancellation only applies while the coroutine is suspended in the
106   wait queue. If the mutex is unlocked when `lock()` is called, the 107   wait queue. If the mutex is unlocked when `lock()` is called, the
107   lock is acquired immediately even if the stop token is already 108   lock is acquired immediately even if the stop token is already
108   signaled. 109   signaled.
109   110  
110   @par Zero Allocation 111   @par Zero Allocation
111   112  
112   No heap allocation occurs for lock operations. 113   No heap allocation occurs for lock operations.
113   114  
114   @par Thread Safety 115   @par Thread Safety
115   116  
  117 + Distinct objects: Safe.@n
  118 + Shared objects: Unsafe.
  119 +
116   The mutex operations are designed for single-threaded use on one 120   The mutex operations are designed for single-threaded use on one
117   executor. The stop callback may fire from any thread. 121   executor. The stop callback may fire from any thread.
118   122  
  123 + This type is non-copyable and non-movable because suspended
  124 + waiters hold intrusive pointers into the mutex's internal list.
  125 +
119   @par Example 126   @par Example
120   @code 127   @code
121   async_mutex cm; 128   async_mutex cm;
122   129  
123   task<> protected_operation() { 130   task<> protected_operation() {
124   auto [ec] = co_await cm.lock(); 131   auto [ec] = co_await cm.lock();
125   if(ec) 132   if(ec)
126   co_return; 133   co_return;
127   // ... critical section ... 134   // ... critical section ...
128   cm.unlock(); 135   cm.unlock();
129   } 136   }
130   137  
131   // Or with RAII: 138   // Or with RAII:
132   task<> protected_operation() { 139   task<> protected_operation() {
133   auto [ec, guard] = co_await cm.scoped_lock(); 140   auto [ec, guard] = co_await cm.scoped_lock();
134   if(ec) 141   if(ec)
135   co_return; 142   co_return;
136   // ... critical section ... 143   // ... critical section ...
137   // unlocks automatically 144   // unlocks automatically
138   } 145   }
139   @endcode 146   @endcode
140   */ 147   */
141   class async_mutex 148   class async_mutex
142   { 149   {
143   public: 150   public:
144   class lock_awaiter; 151   class lock_awaiter;
145   class lock_guard; 152   class lock_guard;
146   class lock_guard_awaiter; 153   class lock_guard_awaiter;
147   154  
148   private: 155   private:
149   bool locked_ = false; 156   bool locked_ = false;
150   detail::intrusive_list<lock_awaiter> waiters_; 157   detail::intrusive_list<lock_awaiter> waiters_;
151   158  
152   public: 159   public:
153   /** Awaiter returned by lock(). 160   /** Awaiter returned by lock().
154   */ 161   */
155   class lock_awaiter 162   class lock_awaiter
156   : public detail::intrusive_list<lock_awaiter>::node 163   : public detail::intrusive_list<lock_awaiter>::node
157   { 164   {
158   friend class async_mutex; 165   friend class async_mutex;
159   166  
160   async_mutex* m_; 167   async_mutex* m_;
161 - std::coroutine_handle<> h_; 168 + continuation cont_;
162   executor_ref ex_; 169   executor_ref ex_;
163   170  
164   // These members must be declared before stop_cb_ 171   // These members must be declared before stop_cb_
165   // (see comment on the union below). 172   // (see comment on the union below).
166   std::atomic<bool> claimed_{false}; 173   std::atomic<bool> claimed_{false};
167   bool canceled_ = false; 174   bool canceled_ = false;
168   bool active_ = false; 175   bool active_ = false;
169   176  
170   struct cancel_fn 177   struct cancel_fn
171   { 178   {
172   lock_awaiter* self_; 179   lock_awaiter* self_;
173   180  
HITCBC 174   6 void operator()() const noexcept 181   6 void operator()() const noexcept
175   { 182   {
HITCBC 176   6 if(!self_->claimed_.exchange( 183   6 if(!self_->claimed_.exchange(
177   true, std::memory_order_acq_rel)) 184   true, std::memory_order_acq_rel))
178   { 185   {
HITCBC 179   6 self_->canceled_ = true; 186   6 self_->canceled_ = true;
HITCBC 180 - 6 self_->ex_.post(self_->h_); 187 + 6 self_->ex_.post(self_->cont_);
181   } 188   }
HITCBC 182   6 } 189   6 }
183   }; 190   };
184   191  
185   using stop_cb_t = 192   using stop_cb_t =
186   std::stop_callback<cancel_fn>; 193   std::stop_callback<cancel_fn>;
187   194  
188   // Aligned storage for stop_cb_t. Declared last: 195   // Aligned storage for stop_cb_t. Declared last:
189   // its destructor may block while the callback 196   // its destructor may block while the callback
190   // accesses the members above. 197   // accesses the members above.
191 - #ifdef _MSC_VER 198 + BOOST_CAPY_MSVC_WARNING_PUSH
192 - # pragma warning(push) 199 + BOOST_CAPY_MSVC_WARNING_DISABLE(4324) // padded due to alignas
193 - # pragma warning(disable: 4324) // padded due to alignas  
194 - #endif  
195   alignas(stop_cb_t) 200   alignas(stop_cb_t)
196   unsigned char stop_cb_buf_[sizeof(stop_cb_t)]; 201   unsigned char stop_cb_buf_[sizeof(stop_cb_t)];
197 - #ifdef _MSC_VER 202 + BOOST_CAPY_MSVC_WARNING_POP
198 - # pragma warning(pop)  
199 - #endif  
200   203  
HITCBC 201   17 stop_cb_t& stop_cb_() noexcept 204   17 stop_cb_t& stop_cb_() noexcept
202   { 205   {
203   return *reinterpret_cast<stop_cb_t*>( 206   return *reinterpret_cast<stop_cb_t*>(
HITCBC 204   17 stop_cb_buf_); 207   17 stop_cb_buf_);
205   } 208   }
206   209  
207   public: 210   public:
HITCBC 208   70 ~lock_awaiter() 211   70 ~lock_awaiter()
209   { 212   {
HITCBC 210   70 if(active_) 213   70 if(active_)
211   { 214   {
HITCBC 212   3 stop_cb_().~stop_cb_t(); 215   3 stop_cb_().~stop_cb_t();
HITCBC 213   3 m_->waiters_.remove(this); 216   3 m_->waiters_.remove(this);
214   } 217   }
HITCBC 215   70 } 218   70 }
216   219  
HITCBC 217   35 explicit lock_awaiter(async_mutex* m) noexcept 220   35 explicit lock_awaiter(async_mutex* m) noexcept
HITCBC 218   35 : m_(m) 221   35 : m_(m)
219   { 222   {
HITCBC 220   35 } 223   35 }
221   224  
HITCBC 222   35 lock_awaiter(lock_awaiter&& o) noexcept 225   35 lock_awaiter(lock_awaiter&& o) noexcept
HITCBC 223   35 : m_(o.m_) 226   35 : m_(o.m_)
HITCBC 224 - 35 , h_(o.h_) 227 + 35 , cont_(o.cont_)
HITCBC 225   35 , ex_(o.ex_) 228   35 , ex_(o.ex_)
HITCBC 226   35 , claimed_(o.claimed_.load( 229   35 , claimed_(o.claimed_.load(
227   std::memory_order_relaxed)) 230   std::memory_order_relaxed))
HITCBC 228   35 , canceled_(o.canceled_) 231   35 , canceled_(o.canceled_)
HITCBC 229   35 , active_(std::exchange(o.active_, false)) 232   35 , active_(std::exchange(o.active_, false))
230   { 233   {
HITCBC 231   35 } 234   35 }
232   235  
233   lock_awaiter(lock_awaiter const&) = delete; 236   lock_awaiter(lock_awaiter const&) = delete;
234   lock_awaiter& operator=(lock_awaiter const&) = delete; 237   lock_awaiter& operator=(lock_awaiter const&) = delete;
235   lock_awaiter& operator=(lock_awaiter&&) = delete; 238   lock_awaiter& operator=(lock_awaiter&&) = delete;
236   239  
HITCBC 237   35 bool await_ready() const noexcept 240   35 bool await_ready() const noexcept
238   { 241   {
HITCBC 239   35 if(!m_->locked_) 242   35 if(!m_->locked_)
240   { 243   {
HITCBC 241   16 m_->locked_ = true; 244   16 m_->locked_ = true;
HITCBC 242   16 return true; 245   16 return true;
243   } 246   }
HITCBC 244   19 return false; 247   19 return false;
245   } 248   }
246   249  
247   /** IoAwaitable protocol overload. */ 250   /** IoAwaitable protocol overload. */
248   std::coroutine_handle<> 251   std::coroutine_handle<>
HITCBC 249   19 await_suspend( 252   19 await_suspend(
250   std::coroutine_handle<> h, 253   std::coroutine_handle<> h,
251   io_env const* env) noexcept 254   io_env const* env) noexcept
252   { 255   {
HITCBC 253   19 if(env->stop_token.stop_requested()) 256   19 if(env->stop_token.stop_requested())
254   { 257   {
HITCBC 255   2 canceled_ = true; 258   2 canceled_ = true;
HITCBC 256   2 return h; 259   2 return h;
257   } 260   }
HITCBC 258 - 17 h_ = h; 261 + 17 cont_.h = h;
HITCBC 259   17 ex_ = env->executor; 262   17 ex_ = env->executor;
HITCBC 260   17 m_->waiters_.push_back(this); 263   17 m_->waiters_.push_back(this);
HITCBC 261   51 ::new(stop_cb_buf_) stop_cb_t( 264   51 ::new(stop_cb_buf_) stop_cb_t(
HITCBC 262   17 env->stop_token, cancel_fn{this}); 265   17 env->stop_token, cancel_fn{this});
HITCBC 263   17 active_ = true; 266   17 active_ = true;
HITCBC 264   17 return std::noop_coroutine(); 267   17 return std::noop_coroutine();
265   } 268   }
266   269  
HITCBC 267   32 io_result<> await_resume() noexcept 270   32 io_result<> await_resume() noexcept
268   { 271   {
HITCBC 269   32 if(active_) 272   32 if(active_)
270   { 273   {
HITCBC 271   14 stop_cb_().~stop_cb_t(); 274   14 stop_cb_().~stop_cb_t();
HITCBC 272   14 if(canceled_) 275   14 if(canceled_)
273   { 276   {
HITCBC 274   6 m_->waiters_.remove(this); 277   6 m_->waiters_.remove(this);
HITCBC 275   6 active_ = false; 278   6 active_ = false;
ECB 276   6 return {make_error_code( 279   return {make_error_code(
HITCBC 277   6 error::canceled)}; 280   6 error::canceled)};
278   } 281   }
HITCBC 279   8 active_ = false; 282   8 active_ = false;
280   } 283   }
HITCBC 281   26 if(canceled_) 284   26 if(canceled_)
ECB 282   2 return {make_error_code( 285   return {make_error_code(
HITCBC 283   2 error::canceled)}; 286   2 error::canceled)};
HITCBC 284   24 return {{}}; 287   24 return {{}};
285   } 288   }
286   }; 289   };
287   290  
288   /** RAII lock guard for async_mutex. 291   /** RAII lock guard for async_mutex.
289   292  
290   Automatically unlocks the mutex when destroyed. 293   Automatically unlocks the mutex when destroyed.
291   */ 294   */
292   class [[nodiscard]] lock_guard 295   class [[nodiscard]] lock_guard
293   { 296   {
294   async_mutex* m_; 297   async_mutex* m_;
295   298  
296   public: 299   public:
HITCBC 297   5 ~lock_guard() 300   9 ~lock_guard()
298   { 301   {
HITCBC 299   5 if(m_) 302   9 if(m_)
HITCBC 300   2 m_->unlock(); 303   2 m_->unlock();
HITCBC 301   5 } 304   9 }
302   305  
HITCBC 303   2 lock_guard() noexcept 306   2 lock_guard() noexcept
HITCBC 304   2 : m_(nullptr) 307   2 : m_(nullptr)
305   { 308   {
HITCBC 306   2 } 309   2 }
307   310  
HITCBC 308   2 explicit lock_guard(async_mutex* m) noexcept 311   2 explicit lock_guard(async_mutex* m) noexcept
HITCBC 309   2 : m_(m) 312   2 : m_(m)
310   { 313   {
HITCBC 311   2 } 314   2 }
312   315  
HITCBC 313   1 lock_guard(lock_guard&& o) noexcept 316   5 lock_guard(lock_guard&& o) noexcept
HITCBC 314   1 : m_(std::exchange(o.m_, nullptr)) 317   5 : m_(std::exchange(o.m_, nullptr))
315   { 318   {
HITCBC 316   1 } 319   5 }
317   320  
318   lock_guard& operator=(lock_guard&& o) noexcept 321   lock_guard& operator=(lock_guard&& o) noexcept
319   { 322   {
320   if(this != &o) 323   if(this != &o)
321   { 324   {
322   if(m_) 325   if(m_)
323   m_->unlock(); 326   m_->unlock();
324   m_ = std::exchange(o.m_, nullptr); 327   m_ = std::exchange(o.m_, nullptr);
325   } 328   }
326   return *this; 329   return *this;
327   } 330   }
328   331  
329   lock_guard(lock_guard const&) = delete; 332   lock_guard(lock_guard const&) = delete;
330   lock_guard& operator=(lock_guard const&) = delete; 333   lock_guard& operator=(lock_guard const&) = delete;
331   }; 334   };
332   335  
333   /** 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.
334   */ 337   */
335   class lock_guard_awaiter 338   class lock_guard_awaiter
336   { 339   {
337   async_mutex* m_; 340   async_mutex* m_;
338   lock_awaiter inner_; 341   lock_awaiter inner_;
339   342  
340   public: 343   public:
HITCBC 341   4 explicit lock_guard_awaiter(async_mutex* m) noexcept 344   4 explicit lock_guard_awaiter(async_mutex* m) noexcept
HITCBC 342   4 : m_(m) 345   4 : m_(m)
HITCBC 343   4 , inner_(m) 346   4 , inner_(m)
344   { 347   {
HITCBC 345   4 } 348   4 }
346   349  
HITCBC 347   4 bool await_ready() const noexcept 350   4 bool await_ready() const noexcept
348   { 351   {
HITCBC 349   4 return inner_.await_ready(); 352   4 return inner_.await_ready();
350   } 353   }
351   354  
352   /** IoAwaitable protocol overload. */ 355   /** IoAwaitable protocol overload. */
353   std::coroutine_handle<> 356   std::coroutine_handle<>
HITCBC 354   2 await_suspend( 357   2 await_suspend(
355   std::coroutine_handle<> h, 358   std::coroutine_handle<> h,
356   io_env const* env) noexcept 359   io_env const* env) noexcept
357   { 360   {
HITCBC 358   2 return inner_.await_suspend(h, env); 361   2 return inner_.await_suspend(h, env);
359   } 362   }
360   363  
HITCBC 361   4 io_result<lock_guard> await_resume() noexcept 364   4 io_result<lock_guard> await_resume() noexcept
362   { 365   {
HITCBC 363   4 auto r = inner_.await_resume(); 366   4 auto r = inner_.await_resume();
HITCBC 364   4 if(r.ec) 367   4 if(r.ec)
HITCBC 365   2 return {r.ec, {}}; 368   2 return {r.ec, {}};
HITCBC 366   2 return {{}, lock_guard(m_)}; 369   2 return {{}, lock_guard(m_)};
ECB 367   4 } 370   }
368   }; 371   };
369   372  
  373 + /// Construct an unlocked mutex.
370   async_mutex() = default; 374   async_mutex() = default;
371   375  
372 - // Non-copyable, non-movable 376 + /// Copy constructor (deleted).
373   async_mutex(async_mutex const&) = delete; 377   async_mutex(async_mutex const&) = delete;
  378 +
  379 + /// Copy assignment (deleted).
374   async_mutex& operator=(async_mutex const&) = delete; 380   async_mutex& operator=(async_mutex const&) = delete;
375   381  
  382 + /// Move constructor (deleted).
  383 + async_mutex(async_mutex&&) = delete;
  384 +
  385 + /// Move assignment (deleted).
  386 + async_mutex& operator=(async_mutex&&) = delete;
  387 +
376   /** Returns an awaiter that acquires the mutex. 388   /** Returns an awaiter that acquires the mutex.
377   389  
378 - @return An awaitable yielding `(error_code)`. 390 + @return An awaitable that await-returns `(error_code)`.
379   */ 391   */
HITCBC 380   31 lock_awaiter lock() noexcept 392   31 lock_awaiter lock() noexcept
381   { 393   {
HITCBC 382   31 return lock_awaiter{this}; 394   31 return lock_awaiter{this};
383   } 395   }
384   396  
385   /** Returns an awaiter that acquires the mutex with RAII. 397   /** Returns an awaiter that acquires the mutex with RAII.
386   398  
387 - @return An awaitable yielding `(error_code,lock_guard)`. 399 + @return An awaitable that await-returns `(error_code,lock_guard)`.
388   */ 400   */
HITCBC 389   4 lock_guard_awaiter scoped_lock() noexcept 401   4 lock_guard_awaiter scoped_lock() noexcept
390   { 402   {
HITCBC 391   4 return lock_guard_awaiter(this); 403   4 return lock_guard_awaiter(this);
392   } 404   }
393   405  
394   /** Releases the mutex. 406   /** Releases the mutex.
395   407  
396   If waiters are queued, the next eligible waiter is 408   If waiters are queued, the next eligible waiter is
397   resumed with the lock held. Canceled waiters are 409   resumed with the lock held. Canceled waiters are
398   skipped. If no eligible waiter remains, the mutex 410   skipped. If no eligible waiter remains, the mutex
399   becomes unlocked. 411   becomes unlocked.
400   */ 412   */
HITCBC 401   24 void unlock() noexcept 413   24 void unlock() noexcept
402   { 414   {
403   for(;;) 415   for(;;)
404   { 416   {
HITCBC 405   24 auto* waiter = waiters_.pop_front(); 417   24 auto* waiter = waiters_.pop_front();
HITCBC 406   24 if(!waiter) 418   24 if(!waiter)
407   { 419   {
HITCBC 408   16 locked_ = false; 420   16 locked_ = false;
HITCBC 409   16 return; 421   16 return;
410   } 422   }
HITCBC 411   8 if(!waiter->claimed_.exchange( 423   8 if(!waiter->claimed_.exchange(
412   true, std::memory_order_acq_rel)) 424   true, std::memory_order_acq_rel))
413   { 425   {
HITCBC 414 - 8 waiter->ex_.post(waiter->h_); 426 + 8 waiter->ex_.post(waiter->cont_);
HITCBC 415   8 return; 427   8 return;
416   } 428   }
MISUBC 417   } 429   }
418   } 430   }
419   431  
420   /** Returns true if the mutex is currently locked. 432   /** Returns true if the mutex is currently locked.
421   */ 433   */
HITCBC 422   26 bool is_locked() const noexcept 434   26 bool is_locked() const noexcept
423   { 435   {
HITCBC 424   26 return locked_; 436   26 return locked_;
425   } 437   }
426   }; 438   };
427   439  
428   } // namespace capy 440   } // namespace capy
429   } // namespace boost 441   } // namespace boost
430   442  
431   #endif 443   #endif