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