92.96% Lines (66/71) 100.00% Functions (11/11)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2026 Steve Gerbino 2   // Copyright (c) 2026 Steve Gerbino
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_EX_ASYNC_WAKER_HPP 11   #ifndef BOOST_CAPY_EX_ASYNC_WAKER_HPP
12   #define BOOST_CAPY_EX_ASYNC_WAKER_HPP 12   #define BOOST_CAPY_EX_ASYNC_WAKER_HPP
13   13  
14   #include <boost/capy/detail/config.hpp> 14   #include <boost/capy/detail/config.hpp>
15   #include <boost/capy/continuation.hpp> 15   #include <boost/capy/continuation.hpp>
16   #include <boost/capy/error.hpp> 16   #include <boost/capy/error.hpp>
17   #include <boost/capy/ex/executor_ref.hpp> 17   #include <boost/capy/ex/executor_ref.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 <atomic> 21   #include <atomic>
22   #include <coroutine> 22   #include <coroutine>
23   #include <new> 23   #include <new>
24   #include <stop_token> 24   #include <stop_token>
25   #include <utility> 25   #include <utility>
26   26  
27   /* async_waker implementation notes 27   /* async_waker implementation notes
28   =================================== 28   ===================================
29   29  
30   wake() must be callable from foreign threads (that is the whole 30   wake() must be callable from foreign threads (that is the whole
31   point: the user's thread provides the timing). A waiter-side 31   point: the user's thread provides the timing). A waiter-side
32   claimed_ flag is not enough there -- the 32   claimed_ flag is not enough there -- the
33   waker has to dereference the waiter, and nothing would pin the 33   waker has to dereference the waiter, and nothing would pin the
34   waiter's frame between reading the pointer and claiming it. 34   waiter's frame between reading the pointer and claiming it.
35   35  
36   So the three-state st_ atomic is the single arbiter: 36   So the three-state st_ atomic is the single arbiter:
37   37  
38   empty --arm--> armed --wake/cancel CAS--> empty 38   empty --arm--> armed --wake/cancel CAS--> empty
39   empty --wake--> token --wait consumes--> empty 39   empty --wake--> token --wait consumes--> empty
40   40  
41   Whoever wins the armed->empty CAS owns the resume and may 41   Whoever wins the armed->empty CAS owns the resume and may
42   dereference waiter_: the frame cannot die underneath the 42   dereference waiter_: the frame cannot die underneath the
43   winner because the coroutine only resumes when the winner 43   winner because the coroutine only resumes when the winner
44   posts it. The loser never touches the waiter. When the stop 44   posts it. The loser never touches the waiter. When the stop
45   callback wins, a concurrent wake retries, finds empty, and 45   callback wins, a concurrent wake retries, finds empty, and
46   latches a token -- a racing wakeup is deferred, never lost. 46   latches a token -- a racing wakeup is deferred, never lost.
47   47  
48   Serialized resumption is required: await_suspend keeps 48   Serialized resumption is required: await_suspend keeps
49   writing after the publishing armed-CAS (the stop_cb 49   writing after the publishing armed-CAS (the stop_cb
50   placement-new and active_ = true), so a wake/cancel winner 50   placement-new and active_ = true), so a wake/cancel winner
51   can post the continuation while that tail is still running. 51   can post the continuation while that tail is still running.
52   The posted resume must be ordered after await_suspend's 52   The posted resume must be ordered after await_suspend's
53   return, which holds on a single-threaded executor (the one 53   return, which holds on a single-threaded executor (the one
54   thread is still inside await_suspend) and on a strand (the 54   thread is still inside await_suspend) and on a strand (the
55   resume is a later turn, synchronized with the current one). 55   resume is a later turn, synchronized with the current one).
56   A raw multi-threaded executor lets another worker run 56   A raw multi-threaded executor lets another worker run
57   await_resume against those in-flight writes. async_event and 57   await_resume against those in-flight writes. async_event and
58   async_mutex make the same assumption; it is stated explicitly 58   async_mutex make the same assumption; it is stated explicitly
59   here because wake() invites foreign threads into the picture. 59   here because wake() invites foreign threads into the picture.
60   */ 60   */
61   61  
62   namespace boost { 62   namespace boost {
63   namespace capy { 63   namespace capy {
64   64  
65   /** A single-slot waker that hands one wakeup to a waiting coroutine. 65   /** A single-slot waker that hands one wakeup to a waiting coroutine.
66   66  
67   This is the escape hatch for timing and other external events: 67   This is the escape hatch for timing and other external events:
68   the user provides the thread and the clock, capy provides the 68   the user provides the thread and the clock, capy provides the
69   suspension point. One coroutine suspends in `wait()`; any 69   suspension point. One coroutine suspends in `wait()`; any
70   thread wakes it with `wake()`. 70   thread wakes it with `wake()`.
71   71  
72   A wakeup with no waiter present is latched as a single pending 72   A wakeup with no waiter present is latched as a single pending
73   token, and the next `wait()` consumes it immediately. This 73   token, and the next `wait()` consumes it immediately. This
74   makes the wake-before-wait race benign without any lock 74   makes the wake-before-wait race benign without any lock
75   protocol. Multiple wakes collapse into one token. 75   protocol. Multiple wakes collapse into one token.
76   76  
77   @par Cancellation 77   @par Cancellation
78   78  
79   If the environment's stop token is triggered while suspended, 79   If the environment's stop token is triggered while suspended,
80   the wait completes with `error::canceled`. A wake that loses 80   the wait completes with `error::canceled`. A wake that loses
81   the race against cancellation is latched for the next `wait()` 81   the race against cancellation is latched for the next `wait()`
82   rather than dropped. 82   rather than dropped.
83   83  
84   @par Zero Allocation 84   @par Zero Allocation
85   85  
86   No heap allocation occurs for wait or wake operations. 86   No heap allocation occurs for wait or wake operations.
87   87  
88   @par Thread Safety 88   @par Thread Safety
89   89  
90   Distinct objects: Safe.@n 90   Distinct objects: Safe.@n
91   Shared objects: `wake()` may be called from any thread. 91   Shared objects: `wake()` may be called from any thread.
92   `wait()` must only be awaited by one coroutine at a time. The 92   `wait()` must only be awaited by one coroutine at a time. The
93   executor must never run the coroutine's continuations 93   executor must never run the coroutine's continuations
94   concurrently: use a single-threaded executor, or a strand over 94   concurrently: use a single-threaded executor, or a strand over
95   a multi-threaded one. That is the same threading model as 95   a multi-threaded one. That is the same threading model as
96   `async_event` and `async_mutex`. Awaiting `wait()` directly 96   `async_event` and `async_mutex`. Awaiting `wait()` directly
97   on a multi-threaded executor is undefined. 97   on a multi-threaded executor is undefined.
98   98  
99   This type is non-copyable and non-movable because a suspended 99   This type is non-copyable and non-movable because a suspended
100   waiter holds a pointer into the object. 100   waiter holds a pointer into the object.
101   101  
102   @par Example 102   @par Example
103 - @code 103 + @par !example example
104 - async_waker waker;  
105 -  
106 - // user-provided timing thread  
107 - std::thread th([&waker] {  
108 - std::this_thread::sleep_for(100ms);  
109 - waker.wake();  
110 - });  
111 - task<> waiter() {  
112 - auto [ec] = co_await waker.wait();  
113 - // resumed on the executor after ~100ms  
114 - }  
115 - // ... th.join() after the pool drains  
116 - @endcode  
117   104  
118   */ 105   */
119   class async_waker 106   class async_waker
120   { 107   {
121   public: 108   public:
122   class wait_awaiter; 109   class wait_awaiter;
123   110  
124   private: 111   private:
125   static constexpr int state_empty = 0; // no token, no waiter 112   static constexpr int state_empty = 0; // no token, no waiter
126   static constexpr int state_token = 1; // latched wakeup 113   static constexpr int state_token = 1; // latched wakeup
127   static constexpr int state_armed = 2; // waiter suspended 114   static constexpr int state_armed = 2; // waiter suspended
128   115  
129   std::atomic<int> st_{state_empty}; 116   std::atomic<int> st_{state_empty};
130   wait_awaiter* waiter_ = nullptr; 117   wait_awaiter* waiter_ = nullptr;
131   118  
132   public: 119   public:
133   /** Suspends the caller until `wake()` runs, or resumes it with `error::canceled` on a stop request. 120   /** Suspends the caller until `wake()` runs, or resumes it with `error::canceled` on a stop request.
134   */ 121   */
135   class wait_awaiter 122   class wait_awaiter
136   { 123   {
137   friend class async_waker; 124   friend class async_waker;
138   125  
139   async_waker* waker_; 126   async_waker* waker_;
140   continuation cont_; 127   continuation cont_;
141   executor_ref ex_; 128   executor_ref ex_;
142   129  
143   // Declared before stop_cb_buf_: the callback accesses 130   // Declared before stop_cb_buf_: the callback accesses
144   // these members, so they must still be alive if the 131   // these members, so they must still be alive if the
145   // stop_cb_ destructor blocks. 132   // stop_cb_ destructor blocks.
146   bool canceled_ = false; 133   bool canceled_ = false;
147   bool active_ = false; 134   bool active_ = false;
148   bool published_ = false; 135   bool published_ = false;
149   136  
150   struct cancel_fn 137   struct cancel_fn
151   { 138   {
152   wait_awaiter* self_; 139   wait_awaiter* self_;
153   140  
HITCBC 154   10 void operator()() const noexcept 141   9 void operator()() const noexcept
155   { 142   {
HITCBC 156   10 int expected = state_armed; 143   9 int expected = state_armed;
HITCBC 157   20 if(self_->waker_->st_.compare_exchange_strong( 144   18 if(self_->waker_->st_.compare_exchange_strong(
158   expected, state_empty, 145   expected, state_empty,
159   std::memory_order_acq_rel, 146   std::memory_order_acq_rel,
160   std::memory_order_acquire)) 147   std::memory_order_acquire))
161   { 148   {
HITCBC 162   8 self_->canceled_ = true; 149   8 self_->canceled_ = true;
HITCBC 163   8 self_->ex_.post(self_->cont_); 150   8 self_->ex_.post(self_->cont_);
164   } 151   }
HITCBC 165   10 } 152   9 }
166   }; 153   };
167   154  
168   using stop_cb_t = std::stop_callback<cancel_fn>; 155   using stop_cb_t = std::stop_callback<cancel_fn>;
169   156  
170   // Aligned storage for stop_cb_t. Declared last: its 157   // Aligned storage for stop_cb_t. Declared last: its
171   // destructor may block while the callback accesses the 158   // destructor may block while the callback accesses the
172   // members above. 159   // members above.
173   BOOST_CAPY_MSVC_WARNING_PUSH 160   BOOST_CAPY_MSVC_WARNING_PUSH
174   BOOST_CAPY_MSVC_WARNING_DISABLE(4324) 161   BOOST_CAPY_MSVC_WARNING_DISABLE(4324)
175   alignas(stop_cb_t) 162   alignas(stop_cb_t)
176   unsigned char stop_cb_buf_[sizeof(stop_cb_t)]; 163   unsigned char stop_cb_buf_[sizeof(stop_cb_t)];
177   BOOST_CAPY_MSVC_WARNING_POP 164   BOOST_CAPY_MSVC_WARNING_POP
178   165  
HITCBC 179   22 stop_cb_t& stop_cb_() noexcept 166   19 stop_cb_t& stop_cb_() noexcept
180   { 167   {
HITCBC 181   22 return *reinterpret_cast<stop_cb_t*>(stop_cb_buf_); 168   19 return *reinterpret_cast<stop_cb_t*>(stop_cb_buf_);
182   } 169   }
183   170  
184   public: 171   public:
185   /** Destroy the awaiter, leaving the waker unable to reach it. 172   /** Destroy the awaiter, leaving the waker unable to reach it.
186   173  
187   Destroys the stop callback if one is registered. If the awaiter 174   Destroys the stop callback if one is registered. If the awaiter
188   is still armed, it also returns the waker's slot to the empty 175   is still armed, it also returns the waker's slot to the empty
189   state, so a later `wake()` cannot dereference a destroyed 176   state, so a later `wake()` cannot dereference a destroyed
190   awaiter. That case means the frame is being torn down without 177   awaiter. That case means the frame is being torn down without
191   ever being resumed; a wake arriving afterward latches a token 178   ever being resumed; a wake arriving afterward latches a token
192   instead. 179   instead.
193   */ 180   */
HITCBC 194   282 ~wait_awaiter() 181   294 ~wait_awaiter()
195   { 182   {
HITCBC 196   282 if(active_) 183   294 if(active_)
HITCBC 197   1 stop_cb_().~stop_cb_t(); 184   1 stop_cb_().~stop_cb_t();
HITCBC 198   282 if(published_) 185   294 if(published_)
199   { 186   {
200   // Destroyed while still armed (frame torn down 187   // Destroyed while still armed (frame torn down
201   // without resuming): deregister so a later 188   // without resuming): deregister so a later
202   // wake cannot touch the dead frame. 189   // wake cannot touch the dead frame.
HITCBC 203   1 int expected = state_armed; 190   1 int expected = state_armed;
HITCBC 204   1 waker_->st_.compare_exchange_strong( 191   1 waker_->st_.compare_exchange_strong(
205   expected, state_empty, 192   expected, state_empty,
206   std::memory_order_acq_rel, 193   std::memory_order_acq_rel,
207   std::memory_order_acquire); 194   std::memory_order_acquire);
208   } 195   }
HITCBC 209   282 } 196   294 }
210   197  
211   /** Construct an awaiter for the given waker. 198   /** Construct an awaiter for the given waker.
212   199  
213   @param waker The waker to wait on. It must outlive the awaiter. 200   @param waker The waker to wait on. It must outlive the awaiter.
214   */ 201   */
HITCBC 215   141 explicit wait_awaiter(async_waker* waker) noexcept 202   147 explicit wait_awaiter(async_waker* waker) noexcept
HITCBC 216   141 : waker_(waker) 203   147 : waker_(waker)
217   { 204   {
HITCBC 218   141 } 205   147 }
219   206  
220   /** Construct by moving. 207   /** Construct by moving.
221   208  
222   The moved-from awaiter is left inert: its destructor no longer 209   The moved-from awaiter is left inert: its destructor no longer
223   destroys the stop callback and no longer deregisters from the 210   destroys the stop callback and no longer deregisters from the
224   waker. 211   waker.
225   212  
226   @param o The awaiter to move from. 213   @param o The awaiter to move from.
227   */ 214   */
HITCBC 228   141 wait_awaiter(wait_awaiter&& o) noexcept 215   147 wait_awaiter(wait_awaiter&& o) noexcept
HITCBC 229   141 : waker_(o.waker_) 216   147 : waker_(o.waker_)
HITCBC 230   141 , cont_(o.cont_) 217   147 , cont_(o.cont_)
HITCBC 231   141 , ex_(o.ex_) 218   147 , ex_(o.ex_)
HITCBC 232   141 , canceled_(o.canceled_) 219   147 , canceled_(o.canceled_)
HITCBC 233   141 , active_(std::exchange(o.active_, false)) 220   147 , active_(std::exchange(o.active_, false))
HITCBC 234   141 , published_(std::exchange(o.published_, false)) 221   147 , published_(std::exchange(o.published_, false))
235   { 222   {
HITCBC 236   141 } 223   147 }
237   224  
238   /** Copy construction is disabled; an armed waiter is registered 225   /** Copy construction is disabled; an armed waiter is registered
239   with the waker by address. 226   with the waker by address.
240   227  
241   @param other The awaiter that would be copied. 228   @param other The awaiter that would be copied.
242   */ 229   */
243   wait_awaiter(wait_awaiter const& other) = delete; 230   wait_awaiter(wait_awaiter const& other) = delete;
244   231  
245   /** Copy assignment is disabled; an armed waiter is registered 232   /** Copy assignment is disabled; an armed waiter is registered
246   with the waker by address. 233   with the waker by address.
247   234  
248   @param other The awaiter that would be assigned from. 235   @param other The awaiter that would be assigned from.
249   236  
250   @return A reference to `*this`. 237   @return A reference to `*this`.
251   */ 238   */
252   wait_awaiter& operator=(wait_awaiter const& other) = delete; 239   wait_awaiter& operator=(wait_awaiter const& other) = delete;
253   240  
254   /** Move assignment is disabled; an armed waiter is registered 241   /** Move assignment is disabled; an armed waiter is registered
255   with the waker by address. 242   with the waker by address.
256   243  
257   @param other The awaiter that would be moved from. 244   @param other The awaiter that would be moved from.
258   245  
259   @return A reference to `*this`. 246   @return A reference to `*this`.
260   */ 247   */
261   wait_awaiter& operator=(wait_awaiter&& other) = delete; 248   wait_awaiter& operator=(wait_awaiter&& other) = delete;
262   249  
263   /** Consume a latched token, completing synchronously. 250   /** Consume a latched token, completing synchronously.
264   251  
265   This is not a pure query: the check is a compare-exchange that 252   This is not a pure query: the check is a compare-exchange that
266   takes the token. Calling it twice is not idempotent: the second 253   takes the token. Calling it twice is not idempotent: the second
267   call reports `false`, because the first already consumed the 254   call reports `false`, because the first already consumed the
268   wakeup. 255   wakeup.
269   256  
270   @return `true` if a pending wakeup token was latched and has now 257   @return `true` if a pending wakeup token was latched and has now
271   been consumed, in which case the awaiting coroutine does not 258   been consumed, in which case the awaiting coroutine does not
272   suspend; otherwise `false`. 259   suspend; otherwise `false`.
273   */ 260   */
HITCBC 274   141 bool await_ready() noexcept 261   147 bool await_ready() noexcept
275   { 262   {
HITCBC 276   141 int expected = state_token; 263   147 int expected = state_token;
HITCBC 277   141 return waker_->st_.compare_exchange_strong( 264   147 return waker_->st_.compare_exchange_strong(
278   expected, state_empty, 265   expected, state_empty,
279   std::memory_order_acq_rel, 266   std::memory_order_acq_rel,
HITCBC 280   141 std::memory_order_acquire); 267   147 std::memory_order_acquire);
281   } 268   }
282   269  
283   /** Arm the waker with the awaiting coroutine. 270   /** Arm the waker with the awaiting coroutine.
284   271  
285   This is the @ref IoAwaitable overload of `await_suspend`. 272   This is the @ref IoAwaitable overload of `await_suspend`.
286   Unlike `async_event` and `async_mutex`, it has three outcomes, 273   Unlike `async_event` and `async_mutex`, it has three outcomes,
287   because a `wake()` from another thread can land in the window 274   because a `wake()` from another thread can land in the window
288   between `await_ready` and this call. 275   between `await_ready` and this call.
289   276  
290   @li A stop request is already pending on `env->stop_token`: the 277   @li A stop request is already pending on `env->stop_token`: the
291   awaiter records the cancellation and does not arm. 278   awaiter records the cancellation and does not arm.
292   279  
293   @li The waker's slot is no longer empty. Under the single-waiter 280   @li The waker's slot is no longer empty. Under the single-waiter
294   precondition that means a wakeup was latched after 281   precondition that means a wakeup was latched after
295   `await_ready` looked, so the token is consumed here instead 282   `await_ready` looked, so the token is consumed here instead
296   and the wait succeeds. 283   and the wait succeeds.
297   284  
298   @li Otherwise the slot moves to the armed state, publishing this 285   @li Otherwise the slot moves to the armed state, publishing this
299   awaiter to the waker, and a stop callback is registered on 286   awaiter to the waker, and a stop callback is registered on
300   `env->stop_token`. Whichever of `wake()` and that callback 287   `env->stop_token`. Whichever of `wake()` and that callback
301   wins the armed-to-empty transition posts `h` through 288   wins the armed-to-empty transition posts `h` through
302   `env->executor`. The loser does nothing, and a losing 289   `env->executor`. The loser does nothing, and a losing
303   `wake()` re-latches its token for the next `wait()`. 290   `wake()` re-latches its token for the next `wait()`.
304   291  
305   @param h The awaiting coroutine, resumed when the waker fires 292   @param h The awaiting coroutine, resumed when the waker fires
306   or the wait is canceled. 293   or the wait is canceled.
307   294  
308   @param env The execution environment. Its executor posts the 295   @param env The execution environment. Its executor posts the
309   resumption and its stop token is watched for the duration of 296   resumption and its stop token is watched for the duration of
310   the wait. It must outlive the wait. 297   the wait. It must outlive the wait.
311   298  
312   @return `h` in the first two cases, which resumes the awaiting 299   @return `h` in the first two cases, which resumes the awaiting
313   coroutine immediately; otherwise `std::noop_coroutine()`, which 300   coroutine immediately; otherwise `std::noop_coroutine()`, which
314   leaves the coroutine suspended and returns control to the 301   leaves the coroutine suspended and returns control to the
315   resumer. 302   resumer.
316   */ 303   */
317   std::coroutine_handle<> 304   std::coroutine_handle<>
HITCBC 318   48 await_suspend( 305   51 await_suspend(
319   std::coroutine_handle<> h, 306   std::coroutine_handle<> h,
320   io_env const* env) noexcept 307   io_env const* env) noexcept
321   { 308   {
HITCBC 322   48 if(env->stop_token.stop_requested()) 309   51 if(env->stop_token.stop_requested())
323   { 310   {
HITCBC 324   26 canceled_ = true; 311   32 canceled_ = true;
HITCBC 325   26 return h; 312   32 return h;
326   } 313   }
HITCBC 327   22 cont_.h = h; 314   19 cont_.h = h;
HITCBC 328   22 ex_ = env->executor; 315   19 ex_ = env->executor;
HITCBC 329   22 waker_->waiter_ = this; 316   19 waker_->waiter_ = this;
330   317  
HITCBC 331   22 int expected = state_empty; 318   19 int expected = state_empty;
HITCBC 332   44 if(!waker_->st_.compare_exchange_strong( 319   38 if(!waker_->st_.compare_exchange_strong(
333   expected, state_armed, 320   expected, state_armed,
334   std::memory_order_acq_rel, 321   std::memory_order_acq_rel,
335   std::memory_order_acquire)) 322   std::memory_order_acquire))
336   { 323   {
337   // Single-waiter precondition: a second concurrent 324   // Single-waiter precondition: a second concurrent
338   // wait would find the slot armed. 325   // wait would find the slot armed.
MISUBC 339   BOOST_CAPY_ASSERT(expected == state_token); 326   BOOST_CAPY_ASSERT(expected == state_token);
340   327  
341   // A wake latched between await_ready and here; 328   // A wake latched between await_ready and here;
342   // consume it and resume inline. 329   // consume it and resume inline.
MISUBC 343   waker_->st_.store( 330   waker_->st_.store(
344   state_empty, std::memory_order_release); 331   state_empty, std::memory_order_release);
MISUBC 345   return h; 332   return h;
346   } 333   }
HITCBC 347   22 published_ = true; 334   19 published_ = true;
348   335  
HITCBC 349   66 ::new(stop_cb_buf_) stop_cb_t( 336   57 ::new(stop_cb_buf_) stop_cb_t(
HITCBC 350   22 env->stop_token, cancel_fn{this}); 337   19 env->stop_token, cancel_fn{this});
HITCBC 351   22 active_ = true; 338   19 active_ = true;
HITCBC 352   22 return std::noop_coroutine(); 339   19 return std::noop_coroutine();
353   } 340   }
354   341  
355   /** Complete the wait and report the outcome. 342   /** Complete the wait and report the outcome.
356   343  
357   Destroys the stop callback if one is registered and clears the 344   Destroys the stop callback if one is registered and clears the
358   armed bookkeeping, so the destructor does not deregister a slot 345   armed bookkeeping, so the destructor does not deregister a slot
359   the resumption already consumed. 346   the resumption already consumed.
360   347  
361   @return An empty `io_result<>` if the wait was woken, whether by 348   @return An empty `io_result<>` if the wait was woken, whether by
362   `wake()` or by a token consumed inline. Otherwise one holding 349   `wake()` or by a token consumed inline. Otherwise one holding
363   `error::canceled`, which means the stop token won the race. 350   `error::canceled`, which means the stop token won the race.
364   */ 351   */
HITCBC 365   140 [[nodiscard]] io_result<> await_resume() noexcept 352   146 [[nodiscard]] io_result<> await_resume() noexcept
366   { 353   {
HITCBC 367   140 if(active_) 354   146 if(active_)
368   { 355   {
HITCBC 369   21 stop_cb_().~stop_cb_t(); 356   18 stop_cb_().~stop_cb_t();
HITCBC 370   21 active_ = false; 357   18 active_ = false;
371   } 358   }
HITCBC 372   140 published_ = false; 359   146 published_ = false;
HITCBC 373   140 if(canceled_) 360   146 if(canceled_)
HITCBC 374   33 return {make_error_code(error::canceled)}; 361   39 return {make_error_code(error::canceled)};
HITCBC 375   107 return {{}}; 362   107 return {{}};
376   } 363   }
377   }; 364   };
378   365  
379   /// Construct with no token latched. 366   /// Construct with no token latched.
HITCBC 380   1 async_waker() = default; 367   1 async_waker() = default;
381   368  
382   /** Copy construction is disabled; an armed waiter points into the 369   /** Copy construction is disabled; an armed waiter points into the
383   waker. 370   waker.
384   371  
385   @param other The waker that would be copied. 372   @param other The waker that would be copied.
386   */ 373   */
387   async_waker(async_waker const& other) = delete; 374   async_waker(async_waker const& other) = delete;
388   375  
389   /** Copy assignment is disabled; an armed waiter points into the waker. 376   /** Copy assignment is disabled; an armed waiter points into the waker.
390   377  
391   @param other The waker that would be assigned from. 378   @param other The waker that would be assigned from.
392   379  
393   @return A reference to `*this`. 380   @return A reference to `*this`.
394   */ 381   */
395   async_waker& operator=(async_waker const& other) = delete; 382   async_waker& operator=(async_waker const& other) = delete;
396   383  
397   /** Move construction is disabled; an armed waiter points into the 384   /** Move construction is disabled; an armed waiter points into the
398   waker. 385   waker.
399   386  
400   @param other The waker that would be moved from. 387   @param other The waker that would be moved from.
401   */ 388   */
402   async_waker(async_waker&& other) = delete; 389   async_waker(async_waker&& other) = delete;
403   390  
404   /** Move assignment is disabled; an armed waiter points into the waker. 391   /** Move assignment is disabled; an armed waiter points into the waker.
405   392  
406   @param other The waker that would be moved from. 393   @param other The waker that would be moved from.
407   394  
408   @return A reference to `*this`. 395   @return A reference to `*this`.
409   */ 396   */
410   async_waker& operator=(async_waker&& other) = delete; 397   async_waker& operator=(async_waker&& other) = delete;
411   398  
412   /** Asynchronously wait until woken. 399   /** Asynchronously wait until woken.
413   400  
414   If a token is latched, completes immediately and consumes 401   If a token is latched, completes immediately and consumes
415   it. Otherwise suspends until `wake()` or the stop token 402   it. Otherwise suspends until `wake()` or the stop token
416   fires. 403   fires.
417   404  
418   @par Preconditions 405   @par Preconditions
419   No other coroutine is currently waiting on this object. 406   No other coroutine is currently waiting on this object.
420   407  
421   @return An awaitable that await-returns `io_result<>`; 408   @return An awaitable that await-returns `io_result<>`;
422   empty on wakeup, `error::canceled` if the stop 409   empty on wakeup, `error::canceled` if the stop
423   token wins. 410   token wins.
424   */ 411   */
HITCBC 425   141 wait_awaiter wait() noexcept 412   147 wait_awaiter wait() noexcept
426   { 413   {
HITCBC 427   141 return wait_awaiter{this}; 414   147 return wait_awaiter{this};
428   } 415   }
429   416  
430   /** Wake the waiter, or latch the wakeup if none waits. 417   /** Wake the waiter, or latch the wakeup if none waits.
431   418  
432   Callable from any thread. The waiter's resumption is 419   Callable from any thread. The waiter's resumption is
433   posted through its executor; this call never resumes a 420   posted through its executor; this call never resumes a
434   coroutine inline. Multiple calls without an intervening 421   coroutine inline. Multiple calls without an intervening
435   `wait()` collapse into a single token. 422   `wait()` collapse into a single token.
436   */ 423   */
HITCBC 437   109 void wake() noexcept 424   109 void wake() noexcept
438   { 425   {
439   for(;;) 426   for(;;)
440   { 427   {
HITCBC 441   109 int s = st_.load(std::memory_order_acquire); 428   109 int s = st_.load(std::memory_order_acquire);
HITCBC 442   109 if(s == state_token) 429   109 if(s == state_token)
HITCBC 443   109 return; 430   109 return;
HITCBC 444   107 if(s == state_empty) 431   107 if(s == state_empty)
445   { 432   {
HITCBC 446   186 if(st_.compare_exchange_weak( 433   192 if(st_.compare_exchange_weak(
447   s, state_token, 434   s, state_token,
448   std::memory_order_acq_rel, 435   std::memory_order_acq_rel,
449   std::memory_order_acquire)) 436   std::memory_order_acquire))
HITCBC 450   93 return; 437   96 return;
MISUBC 451   continue; 438   continue;
452   } 439   }
453   // armed: winning this CAS claims the waiter, whose 440   // armed: winning this CAS claims the waiter, whose
454   // frame is pinned until we post its resumption. 441   // frame is pinned until we post its resumption.
HITCBC 455   28 if(st_.compare_exchange_weak( 442   22 if(st_.compare_exchange_weak(
456   s, state_empty, 443   s, state_empty,
457   std::memory_order_acq_rel, 444   std::memory_order_acq_rel,
458   std::memory_order_acquire)) 445   std::memory_order_acquire))
459   { 446   {
HITCBC 460   14 auto* w = waiter_; 447   11 auto* w = waiter_;
HITCBC 461   14 w->ex_.post(w->cont_); 448   11 w->ex_.post(w->cont_);
HITCBC 462   14 return; 449   11 return;
463   } 450   }
MISUBC 464   } 451   }
465   } 452   }
466   }; 453   };
467   454  
468   } // namespace capy 455   } // namespace capy
469   } // namespace boost 456   } // namespace boost
470   457  
471   #endif 458   #endif