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