100.00% Lines (96/96) 100.00% Functions (13/13)
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/await_context.hpp>
19 - #include <boost/capy/io_result.hpp> 19 + #include <boost/capy/results.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 <thread> 25   #include <thread>
26   #include <utility> 26   #include <utility>
27   27  
28   /* async_waker implementation notes 28   /* async_waker implementation notes
29   =================================== 29   ===================================
30   30  
31   wake() must be callable from foreign threads (that is the whole 31   wake() must be callable from foreign threads (that is the whole
32   point: the user's thread provides the timing). A waiter-side 32   point: the user's thread provides the timing). A waiter-side
33   claimed_ flag is not enough there -- the 33   claimed_ flag is not enough there -- the
34   waker has to dereference the waiter, and nothing would pin the 34   waker has to dereference the waiter, and nothing would pin the
35   waiter's frame between reading the pointer and claiming it. 35   waiter's frame between reading the pointer and claiming it.
36   36  
37   So the st_ atomic is the single arbiter: 37   So the st_ atomic is the single arbiter:
38   38  
39   empty --arm--> arming --publish--> armed --wake/cancel CAS--> empty 39   empty --arm--> arming --publish--> armed --wake/cancel CAS--> empty
40   arming --stop already requested--> empty 40   arming --stop already requested--> empty
41   empty --wake--> token --wait consumes--> empty 41   empty --wake--> token --wait consumes--> empty
42   42  
43   Whoever wins the armed->empty CAS owns the resume and may 43   Whoever wins the armed->empty CAS owns the resume and may
44   dereference waiter_: the frame cannot die underneath the 44   dereference waiter_: the frame cannot die underneath the
45   winner because the coroutine only resumes when the winner 45   winner because the coroutine only resumes when the winner
46   posts it. The loser never touches the waiter. When the stop 46   posts it. The loser never touches the waiter. When the stop
47   callback wins, a concurrent wake retries, finds empty, and 47   callback wins, a concurrent wake retries, finds empty, and
48   latches a token -- a racing wakeup is deferred, never lost. 48   latches a token -- a racing wakeup is deferred, never lost.
49   49  
50   The stop callback is registered before the waiter enters the 50   The stop callback is registered before the waiter enters the
51   arming state, and a wake() winner destroys it before posting. 51   arming state, and a wake() winner destroys it before posting.
52   While arming, nothing can claim the waiter: wake(), the stop 52   While arming, nothing can claim the waiter: wake(), the stop
53   callback and the destructor wait the few instructions it takes. 53   callback and the destructor wait the few instructions it takes.
54   So the waiter settles a stop requested during registration 54   So the waiter settles a stop requested during registration
55   itself, its last touch of the waker is the publishing store, 55   itself, its last touch of the waker is the publishing store,
56   and the waker can die right after the wake() that claimed it. 56   and the waker can die right after the wake() that claimed it.
57   57  
58   Serialized resumption is still required: the executor must not 58   Serialized resumption is still required: the executor must not
59   run the waiter's continuations concurrently, since a winner can 59   run the waiter's continuations concurrently, since a winner can
60   post the waiter before its await_suspend has returned. async_event 60   post the waiter before its await_suspend has returned. async_event
61   and async_mutex make the same assumption; it is stated explicitly 61   and async_mutex make the same assumption; it is stated explicitly
62   here because wake() invites foreign threads into the picture. 62   here because wake() invites foreign threads into the picture.
63   */ 63   */
64   64  
65   namespace boost { 65   namespace boost {
66   namespace capy { 66   namespace capy {
67   67  
68   /** A single-slot waker that hands one wakeup to a waiting coroutine. 68   /** A single-slot waker that hands one wakeup to a waiting coroutine.
69   69  
70   This is the escape hatch for timing and other external events: 70   This is the escape hatch for timing and other external events:
71   the user provides the thread and the clock, capy provides the 71   the user provides the thread and the clock, capy provides the
72   suspension point. One coroutine suspends in `wait()`; any 72   suspension point. One coroutine suspends in `wait()`; any
73   thread wakes it with `wake()`. 73   thread wakes it with `wake()`.
74   74  
75   A wakeup with no waiter present is latched as a single pending 75   A wakeup with no waiter present is latched as a single pending
76   token, and the next `wait()` consumes it immediately. This 76   token, and the next `wait()` consumes it immediately. This
77   makes the wake-before-wait race benign without any lock 77   makes the wake-before-wait race benign without any lock
78   protocol. Multiple wakes collapse into one token. 78   protocol. Multiple wakes collapse into one token.
79   79  
80   @par Cancellation 80   @par Cancellation
81   81  
82   If the environment's stop token is triggered while suspended, 82   If the environment's stop token is triggered while suspended,
83   the wait completes with `error::canceled`. A wake that loses 83   the wait completes with `error::canceled`. A wake that loses
84   the race against cancellation is latched for the next `wait()` 84   the race against cancellation is latched for the next `wait()`
85   rather than dropped. 85   rather than dropped.
86   86  
87   @par Zero Allocation 87   @par Zero Allocation
88   88  
89   No heap allocation occurs for wait or wake operations. 89   No heap allocation occurs for wait or wake operations.
90   90  
91   @par Thread Safety 91   @par Thread Safety
92   92  
93   Distinct objects: Safe.@n 93   Distinct objects: Safe.@n
94   Shared objects: `wake()` may be called from any thread. 94   Shared objects: `wake()` may be called from any thread.
95   `wait()` must only be awaited by one coroutine at a time. The 95   `wait()` must only be awaited by one coroutine at a time. The
96   executor must never run the coroutine's continuations 96   executor must never run the coroutine's continuations
97   concurrently: use a single-threaded executor, or a strand over 97   concurrently: use a single-threaded executor, or a strand over
98   a multi-threaded one. That is the same threading model as 98   a multi-threaded one. That is the same threading model as
99   `async_event` and `async_mutex`. Awaiting `wait()` directly 99   `async_event` and `async_mutex`. Awaiting `wait()` directly
100   on a multi-threaded executor is undefined. 100   on a multi-threaded executor is undefined.
101   101  
102   This type is non-copyable and non-movable because a suspended 102   This type is non-copyable and non-movable because a suspended
103   waiter holds a pointer into the object. 103   waiter holds a pointer into the object.
104   104  
105   @par Example 105   @par Example
106   @par !example example 106   @par !example example
107   107  
108   */ 108   */
109   class async_waker 109   class async_waker
110   { 110   {
111   public: 111   public:
112   class wait_awaiter; 112   class wait_awaiter;
113   113  
114   private: 114   private:
115   static constexpr int state_empty = 0; // no token, no waiter 115   static constexpr int state_empty = 0; // no token, no waiter
116   static constexpr int state_token = 1; // latched wakeup 116   static constexpr int state_token = 1; // latched wakeup
117   static constexpr int state_armed = 2; // waiter suspended 117   static constexpr int state_armed = 2; // waiter suspended
118   static constexpr int state_arming = 3; // waiter publishing itself 118   static constexpr int state_arming = 3; // waiter publishing itself
119   119  
120   std::atomic<int> st_{state_empty}; 120   std::atomic<int> st_{state_empty};
121   wait_awaiter* waiter_ = nullptr; 121   wait_awaiter* waiter_ = nullptr;
122   122  
123   // Take the armed waiter, waiting out one still publishing itself. 123   // Take the armed waiter, waiting out one still publishing itself.
HITCBC 124   2428 bool claim_armed() noexcept 124   1049 bool claim_armed() noexcept
125   { 125   {
HITCBC 126   2428 int expected = state_armed; 126   1049 int expected = state_armed;
127   for(;;) 127   for(;;)
128   { 128   {
HITCBC 129   4856 if(st_.compare_exchange_weak( 129   2098 if(st_.compare_exchange_weak(
130   expected, state_empty, 130   expected, state_empty,
131   std::memory_order_acq_rel, 131   std::memory_order_acq_rel,
132   std::memory_order_acquire)) 132   std::memory_order_acquire))
HITCBC 133   2012 return true; 133   206 return true;
HITCBC 134   416 if(expected == state_armed) 134   843 if(expected == state_armed)
135   continue; // LCOV_EXCL_LINE spurious weak CAS failure 135   continue; // LCOV_EXCL_LINE spurious weak CAS failure
HITCBC 136   416 if(expected != state_arming) 136   843 if(expected != state_arming)
HITCBC 137   416 return false; 137   843 return false;
138   std::this_thread::yield(); // LCOV_EXCL_LINE race with a waiter still publishing itself 138   std::this_thread::yield(); // LCOV_EXCL_LINE race with a waiter still publishing itself
139   expected = state_armed; // LCOV_EXCL_LINE race with a waiter still publishing itself 139   expected = state_armed; // LCOV_EXCL_LINE race with a waiter still publishing itself
140   } 140   }
141   } 141   }
142   142  
143   public: 143   public:
144   /** Suspends the caller until `wake()` runs, or resumes it with `error::canceled` on a stop request. 144   /** Suspends the caller until `wake()` runs, or resumes it with `error::canceled` on a stop request.
145   */ 145   */
146   class wait_awaiter 146   class wait_awaiter
147   { 147   {
148   friend class async_waker; 148   friend class async_waker;
149   149  
150   async_waker* waker_; 150   async_waker* waker_;
151   continuation cont_; 151   continuation cont_;
152   executor_ref ex_; 152   executor_ref ex_;
153   153  
154   // Declared before stop_cb_buf_: the callback accesses 154   // Declared before stop_cb_buf_: the callback accesses
155   // these members, so they must still be alive if the 155   // these members, so they must still be alive if the
156   // stop_cb_ destructor blocks. 156   // stop_cb_ destructor blocks.
157   bool canceled_ = false; 157   bool canceled_ = false;
158   bool active_ = false; 158   bool active_ = false;
159   bool published_ = false; 159   bool published_ = false;
160   160  
161   // Set by whoever wins the armed->empty transition, before it 161   // Set by whoever wins the armed->empty transition, before it
162   // posts. A claimed waiter belongs to the executor's queue and 162   // posts. A claimed waiter belongs to the executor's queue and
163   // may be discarded after the waker is gone, so its destructor 163   // may be discarded after the waker is gone, so its destructor
164   // must not touch the waker. 164   // must not touch the waker.
165   std::atomic<bool> claimed_{false}; 165   std::atomic<bool> claimed_{false};
166   166  
167   struct cancel_fn 167   struct cancel_fn
168   { 168   {
169   wait_awaiter* self_; 169   wait_awaiter* self_;
170   170  
HITCBC 171   2010 void operator()() const noexcept 171   631 void operator()() const noexcept
172   { 172   {
HITCBC 173   2010 if(self_->waker_->claim_armed()) 173   631 if(self_->waker_->claim_armed())
174   { 174   {
HITCBC 175   2010 self_->canceled_ = true; 175   204 self_->canceled_ = true;
HITCBC 176   2010 self_->claimed_.store( 176   204 self_->claimed_.store(
177   true, std::memory_order_release); 177   true, std::memory_order_release);
HITCBC 178   2010 self_->ex_.post(self_->cont_); 178   204 self_->ex_.post(self_->cont_);
179   } 179   }
HITCBC 180   2010 } 180   631 }
181   }; 181   };
182   182  
183   using stop_cb_t = std::stop_callback<cancel_fn>; 183   using stop_cb_t = std::stop_callback<cancel_fn>;
184   184  
185   // Aligned storage for stop_cb_t. Declared last: its 185   // Aligned storage for stop_cb_t. Declared last: its
186   // destructor may block while the callback accesses the 186   // destructor may block while the callback accesses the
187   // members above. 187   // members above.
188   BOOST_CAPY_MSVC_WARNING_PUSH 188   BOOST_CAPY_MSVC_WARNING_PUSH
189   BOOST_CAPY_MSVC_WARNING_DISABLE(4324) 189   BOOST_CAPY_MSVC_WARNING_DISABLE(4324)
190   alignas(stop_cb_t) 190   alignas(stop_cb_t)
191   unsigned char stop_cb_buf_[sizeof(stop_cb_t)]; 191   unsigned char stop_cb_buf_[sizeof(stop_cb_t)];
192   BOOST_CAPY_MSVC_WARNING_POP 192   BOOST_CAPY_MSVC_WARNING_POP
193   193  
HITCBC 194   2351 stop_cb_t& stop_cb_() noexcept 194   946 stop_cb_t& stop_cb_() noexcept
195   { 195   {
HITCBC 196   2351 return *reinterpret_cast<stop_cb_t*>(stop_cb_buf_); 196   946 return *reinterpret_cast<stop_cb_t*>(stop_cb_buf_);
197   } 197   }
198   198  
199   public: 199   public:
200   /** Destroy the awaiter, leaving the waker unable to reach it. 200   /** Destroy the awaiter, leaving the waker unable to reach it.
201   201  
202   Destroys the stop callback if one is registered. If the awaiter 202   Destroys the stop callback if one is registered. If the awaiter
203   is still armed, it also returns the waker's slot to the empty 203   is still armed, it also returns the waker's slot to the empty
204   state, so a later `wake()` cannot dereference a destroyed 204   state, so a later `wake()` cannot dereference a destroyed
205   awaiter. That case means the frame is being torn down without 205   awaiter. That case means the frame is being torn down without
206   ever being resumed; a wake arriving afterward latches a token 206   ever being resumed; a wake arriving afterward latches a token
207   instead. 207   instead.
208   */ 208   */
HITCBC 209   4884 ~wait_awaiter() 209   4906 ~wait_awaiter()
210   { 210   {
HITCBC 211   4884 if(active_) 211   4906 if(active_)
HITCBC 212   3 stop_cb_().~stop_cb_t(); 212   3 stop_cb_().~stop_cb_t();
HITCBC 213   4889 if(published_ && 213   4911 if(published_ &&
HITCBC 214   5 !claimed_.load(std::memory_order_acquire)) 214   5 !claimed_.load(std::memory_order_acquire))
215   { 215   {
216   // Destroyed while still armed (frame torn down 216   // Destroyed while still armed (frame torn down
217   // without resuming): deregister so a later 217   // without resuming): deregister so a later
218   // wake cannot touch the dead frame. 218   // wake cannot touch the dead frame.
HITCBC 219   1 int expected = state_armed; 219   1 int expected = state_armed;
HITCBC 220   1 waker_->st_.compare_exchange_strong( 220   1 waker_->st_.compare_exchange_strong(
221   expected, state_empty, 221   expected, state_empty,
222   std::memory_order_acq_rel, 222   std::memory_order_acq_rel,
223   std::memory_order_acquire); 223   std::memory_order_acquire);
224   } 224   }
HITCBC 225   4884 } 225   4906 }
226   226  
227   /** Construct an awaiter for the given waker. 227   /** Construct an awaiter for the given waker.
228   228  
229   @param waker The waker to wait on. It must outlive the awaiter. 229   @param waker The waker to wait on. It must outlive the awaiter.
230   */ 230   */
HITCBC 231   2442 explicit wait_awaiter(async_waker* waker) noexcept 231   2453 explicit wait_awaiter(async_waker* waker) noexcept
HITCBC 232   2442 : waker_(waker) 232   2453 : waker_(waker)
233   { 233   {
HITCBC 234   2442 } 234   2453 }
235   235  
236   /** Construct by moving. 236   /** Construct by moving.
237   237  
238   The moved-from awaiter is left inert: its destructor no longer 238   The moved-from awaiter is left inert: its destructor no longer
239   destroys the stop callback and no longer deregisters from the 239   destroys the stop callback and no longer deregisters from the
240   waker. 240   waker.
241   241  
242   @param o The awaiter to move from. 242   @param o The awaiter to move from.
243   */ 243   */
HITCBC 244   2442 wait_awaiter(wait_awaiter&& o) noexcept 244   2453 wait_awaiter(wait_awaiter&& o) noexcept
HITCBC 245   2442 : waker_(o.waker_) 245   2453 : waker_(o.waker_)
HITCBC 246   2442 , cont_(o.cont_) 246   2453 , cont_(o.cont_)
HITCBC 247   2442 , ex_(o.ex_) 247   2453 , ex_(o.ex_)
HITCBC 248   2442 , canceled_(o.canceled_) 248   2453 , canceled_(o.canceled_)
HITCBC 249   2442 , active_(std::exchange(o.active_, false)) 249   2453 , active_(std::exchange(o.active_, false))
HITCBC 250   2442 , published_(std::exchange(o.published_, false)) 250   2453 , published_(std::exchange(o.published_, false))
HITCBC 251   2442 , claimed_(o.claimed_.load(std::memory_order_relaxed)) 251   2453 , claimed_(o.claimed_.load(std::memory_order_relaxed))
252   { 252   {
HITCBC 253   2442 } 253   2453 }
254   254  
255   /** Copy construction is disabled; an armed waiter is registered 255   /** Copy construction is disabled; an armed waiter is registered
256   with the waker by address. 256   with the waker by address.
257   257  
258   @param other The awaiter that would be copied. 258   @param other The awaiter that would be copied.
259   */ 259   */
260   wait_awaiter(wait_awaiter const& other) = delete; 260   wait_awaiter(wait_awaiter const& other) = delete;
261   261  
262   /** Copy assignment is disabled; an armed waiter is registered 262   /** Copy assignment is disabled; an armed waiter is registered
263   with the waker by address. 263   with the waker by address.
264   264  
265   @param other The awaiter that would be assigned from. 265   @param other The awaiter that would be assigned from.
266   266  
267   @return A reference to `*this`. 267   @return A reference to `*this`.
268   */ 268   */
269   wait_awaiter& operator=(wait_awaiter const& other) = delete; 269   wait_awaiter& operator=(wait_awaiter const& other) = delete;
270   270  
271   /** Move assignment is disabled; an armed waiter is registered 271   /** Move assignment is disabled; an armed waiter is registered
272   with the waker by address. 272   with the waker by address.
273   273  
274   @param other The awaiter that would be moved from. 274   @param other The awaiter that would be moved from.
275   275  
276   @return A reference to `*this`. 276   @return A reference to `*this`.
277   */ 277   */
278   wait_awaiter& operator=(wait_awaiter&& other) = delete; 278   wait_awaiter& operator=(wait_awaiter&& other) = delete;
279   279  
280   /** Consume a latched token, completing synchronously. 280   /** Consume a latched token, completing synchronously.
281   281  
282   This is not a pure query: the check is a compare-exchange that 282   This is not a pure query: the check is a compare-exchange that
283   takes the token. Calling it twice is not idempotent: the second 283   takes the token. Calling it twice is not idempotent: the second
284   call reports `false`, because the first already consumed the 284   call reports `false`, because the first already consumed the
285   wakeup. 285   wakeup.
286   286  
287   @return `true` if a pending wakeup token was latched and has now 287   @return `true` if a pending wakeup token was latched and has now
288   been consumed, in which case the awaiting coroutine does not 288   been consumed, in which case the awaiting coroutine does not
289   suspend; otherwise `false`. 289   suspend; otherwise `false`.
290   */ 290   */
HITCBC 291   2442 bool await_ready() noexcept 291   2453 bool await_ready() noexcept
292   { 292   {
HITCBC 293   2442 int expected = state_token; 293   2453 int expected = state_token;
HITCBC 294   2442 return waker_->st_.compare_exchange_strong( 294   2453 return waker_->st_.compare_exchange_strong(
295   expected, state_empty, 295   expected, state_empty,
296   std::memory_order_acq_rel, 296   std::memory_order_acq_rel,
HITCBC 297   2442 std::memory_order_acquire); 297   2453 std::memory_order_acquire);
298   } 298   }
299   299  
300   /** Arm the waker with the awaiting coroutine. 300   /** Arm the waker with the awaiting coroutine.
301   301  
302 - This is the @ref IoAwaitable overload of `await_suspend`. 302 + This is the @ref ContextAwaitable overload of `await_suspend`.
303   Unlike `async_event` and `async_mutex`, it has three outcomes, 303   Unlike `async_event` and `async_mutex`, it has three outcomes,
304   because a `wake()` from another thread can land in the window 304   because a `wake()` from another thread can land in the window
305   between `await_ready` and this call. 305   between `await_ready` and this call.
306   306  
307   @li A stop request is already pending on `env->stop_token`: the 307   @li A stop request is already pending on `env->stop_token`: the
308   awaiter records the cancellation and does not arm. 308   awaiter records the cancellation and does not arm.
309   309  
310   @li The waker's slot is no longer empty. Under the single-waiter 310   @li The waker's slot is no longer empty. Under the single-waiter
311   precondition that means a wakeup was latched after 311   precondition that means a wakeup was latched after
312   `await_ready` looked, so the token is consumed here instead 312   `await_ready` looked, so the token is consumed here instead
313   and the wait succeeds. 313   and the wait succeeds.
314   314  
315   @li Otherwise a stop callback is registered on 315   @li Otherwise a stop callback is registered on
316   `env->stop_token` and the slot moves to the armed state, 316   `env->stop_token` and the slot moves to the armed state,
317   publishing this awaiter to the waker. A stop requested 317   publishing this awaiter to the waker. A stop requested
318   while the callback registered completes the wait canceled 318   while the callback registered completes the wait canceled
319   instead. Whichever of `wake()` and that callback wins the 319   instead. Whichever of `wake()` and that callback wins the
320   armed-to-empty transition posts `h` through 320   armed-to-empty transition posts `h` through
321   `env->executor`; a winning `wake()` first deregisters the 321   `env->executor`; a winning `wake()` first deregisters the
322   callback. The loser does nothing, and a losing `wake()` 322   callback. The loser does nothing, and a losing `wake()`
323   re-latches its token for the next `wait()`. 323   re-latches its token for the next `wait()`.
324   324  
325   @param h The awaiting coroutine, resumed when the waker fires 325   @param h The awaiting coroutine, resumed when the waker fires
326   or the wait is canceled. 326   or the wait is canceled.
327   327  
328   @param env The execution environment. Its executor posts the 328   @param env The execution environment. Its executor posts the
329   resumption and its stop token is watched for the duration of 329   resumption and its stop token is watched for the duration of
330   the wait. It must outlive the wait. 330   the wait. It must outlive the wait.
331   331  
332   @return `h` in the first two cases, which resumes the awaiting 332   @return `h` in the first two cases, which resumes the awaiting
333   coroutine immediately; otherwise `std::noop_coroutine()`, which 333   coroutine immediately; otherwise `std::noop_coroutine()`, which
334   leaves the coroutine suspended and returns control to the 334   leaves the coroutine suspended and returns control to the
335   resumer. 335   resumer.
336   */ 336   */
337   std::coroutine_handle<> 337   std::coroutine_handle<>
HITCBC 338   2369 await_suspend( 338   2354 await_suspend(
339   std::coroutine_handle<> h, 339   std::coroutine_handle<> h,
340 - io_env const* env) noexcept 340 + await_context const* env) noexcept
341   { 341   {
HITCBC 342   2369 if(env->stop_token.stop_requested()) 342   2354 if(env->stop_token.stop_requested())
343   { 343   {
HITCBC 344   18 canceled_ = true; 344   1408 canceled_ = true;
HITCBC 345   18 return h; 345   1408 return h;
346   } 346   }
HITCBC 347   2351 cont_.h = h; 347   946 cont_.h = h;
HITCBC 348   2351 cont_.owner = env->owner; 348   946 cont_.owner = env->owner;
HITCBC 349   2351 ex_ = env->executor; 349   946 ex_ = env->executor;
HITCBC 350   2351 waker_->waiter_ = this; 350   946 waker_->waiter_ = this;
351   351  
352   // Registered before publishing, so the wake() that claims 352   // Registered before publishing, so the wake() that claims
353   // this waiter can deregister it before posting. 353   // this waiter can deregister it before posting.
HITCBC 354   7053 ::new(stop_cb_buf_) stop_cb_t( 354   2838 ::new(stop_cb_buf_) stop_cb_t(
HITCBC 355   2351 env->stop_token, cancel_fn{this}); 355   946 env->stop_token, cancel_fn{this});
HITCBC 356   2351 active_ = true; 356   946 active_ = true;
HITCBC 357   2351 published_ = true; 357   946 published_ = true;
358   358  
HITCBC 359   2351 int expected = state_empty; 359   946 int expected = state_empty;
HITCBC 360   4702 if(!waker_->st_.compare_exchange_strong( 360   1892 if(!waker_->st_.compare_exchange_strong(
361   expected, state_arming, 361   expected, state_arming,
362   std::memory_order_acq_rel, 362   std::memory_order_acq_rel,
363   std::memory_order_acquire)) 363   std::memory_order_acquire))
364   { 364   {
  365 + // LCOV_EXCL_START race: a wake latched between await_ready and here
365   // Single-waiter precondition: a second concurrent 366   // Single-waiter precondition: a second concurrent
366   // wait would find the slot armed. 367   // wait would find the slot armed.
EUB 367   ✗ BOOST_CAPY_ASSERT(expected == state_token); 368   BOOST_CAPY_ASSERT(expected == state_token);
368   369  
369   // A wake latched between await_ready and here; 370   // A wake latched between await_ready and here;
370   // consume it and resume inline. 371   // consume it and resume inline.
EUB 371   ✗ published_ = false; 372   published_ = false;
EUB 372   ✗ stop_cb_().~stop_cb_t(); 373   stop_cb_().~stop_cb_t();
EUB 373   ✗ active_ = false; 374   active_ = false;
EUB 374   ✗ waker_->st_.store( 375   waker_->st_.store(
375   state_empty, std::memory_order_release); 376   state_empty, std::memory_order_release);
EUB 376   ✗ return h; 377   return h;
  378 + // LCOV_EXCL_STOP
377   } 379   }
378   380  
379   // While arming nothing else can claim this waiter, so a stop 381   // While arming nothing else can claim this waiter, so a stop
380   // requested as the callback registered is settled here, and 382   // requested as the callback registered is settled here, and
381   // the waker is not touched again once the waiter is armed. 383   // the waker is not touched again once the waiter is armed.
HITCBC 382   2351 if(env->stop_token.stop_requested()) 384   945 if(env->stop_token.stop_requested())
383   { 385   {
HITGBC 384   ✗ published_ = false; 386   427 published_ = false;
HITGBC 385   ✗ waker_->st_.store( 387   427 waker_->st_.store(
386   state_empty, std::memory_order_release); 388   state_empty, std::memory_order_release);
HITGBC 387   ✗ canceled_ = true; 389   427 canceled_ = true;
HITGBC 388   ✗ return h; 390   427 return h;
389   } 391   }
HITCBC 390   2351 waker_->st_.store(state_armed, std::memory_order_release); 392   518 waker_->st_.store(state_armed, std::memory_order_release);
HITCBC 391   2351 return std::noop_coroutine(); 393   518 return std::noop_coroutine();
392   } 394   }
393   395  
394   /** Complete the wait and report the outcome. 396   /** Complete the wait and report the outcome.
395   397  
396   Destroys the stop callback if one is registered and clears the 398   Destroys the stop callback if one is registered and clears the
397   armed bookkeeping, so the destructor does not deregister a slot 399   armed bookkeeping, so the destructor does not deregister a slot
398   the resumption already consumed. 400   the resumption already consumed.
399   401  
400 - @return An empty `io_result<>` if the wait was woken, whether by 402 + @return An empty `results<>` if the wait was woken, whether by
401   `wake()` or by a token consumed inline. Otherwise one holding 403   `wake()` or by a token consumed inline. Otherwise one holding
402   `error::canceled`, which means the stop token won the race or 404   `error::canceled`, which means the stop token won the race or
403   the waker was destroyed. 405   the waker was destroyed.
404   */ 406   */
HITCBC 405 - 2437 [[nodiscard]] io_result<> await_resume() noexcept 407 + 2448 [[nodiscard]] results<> await_resume() noexcept
406   { 408   {
HITCBC 407   2437 if(active_) 409   2448 if(active_)
408   { 410   {
HITCBC 409   2008 stop_cb_().~stop_cb_t(); 411   629 stop_cb_().~stop_cb_t();
HITCBC 410   2008 active_ = false; 412   629 active_ = false;
411   } 413   }
HITCBC 412   2437 published_ = false; 414   2448 published_ = false;
HITCBC 413   2437 if(canceled_) 415   2448 if(canceled_)
HITCBC 414   2028 return {make_error_code(error::canceled)}; 416   2039 return {make_error_code(error::canceled)};
HITCBC 415   409 return {{}}; 417   409 return {{}};
416   } 418   }
417   }; 419   };
418   420  
419   /// Construct with no token latched. 421   /// Construct with no token latched.
HITCBC 420   307 async_waker() = default; 422   307 async_waker() = default;
421   423  
422   /** Destroy the waker, canceling a coroutine still waiting. 424   /** Destroy the waker, canceling a coroutine still waiting.
423   425  
424   The waiter is posted to its executor to resume with 426   The waiter is posted to its executor to resume with
425   `error::canceled`, as if its stop token had been triggered. An 427   `error::canceled`, as if its stop token had been triggered. An
426   executor that can no longer run it abandons it instead, possibly 428   executor that can no longer run it abandons it instead, possibly
427   before this destructor returns. Either way the waiter no longer 429   before this destructor returns. Either way the waiter no longer
428   refers to the waker. 430   refers to the waker.
429   431  
430   @pre No `wake()` call is in progress or made afterward. 432   @pre No `wake()` call is in progress or made afterward.
431   */ 433   */
HITCBC 432   418 ~async_waker() 434   418 ~async_waker()
433   { 435   {
HITCBC 434   418 if(claim_armed()) 436   418 if(claim_armed())
435   { 437   {
HITCBC 436   2 auto* w = waiter_; 438   2 auto* w = waiter_;
437   // The stop callback reaches into the waker; deregistering 439   // The stop callback reaches into the waker; deregistering
438   // it waits out one already running. 440   // it waits out one already running.
HITCBC 439   2 if(w->active_) 441   2 if(w->active_)
440   { 442   {
441   using cb_t = wait_awaiter::stop_cb_t; 443   using cb_t = wait_awaiter::stop_cb_t;
HITCBC 442   2 w->stop_cb_().~cb_t(); 444   2 w->stop_cb_().~cb_t();
HITCBC 443   2 w->active_ = false; 445   2 w->active_ = false;
444   } 446   }
HITCBC 445   2 w->canceled_ = true; 447   2 w->canceled_ = true;
HITCBC 446   2 w->claimed_.store(true, std::memory_order_release); 448   2 w->claimed_.store(true, std::memory_order_release);
HITCBC 447   2 w->ex_.post(w->cont_); 449   2 w->ex_.post(w->cont_);
448   } 450   }
HITCBC 449   418 } 451   418 }
450   452  
451   /** Copy construction is disabled; an armed waiter points into the 453   /** Copy construction is disabled; an armed waiter points into the
452   waker. 454   waker.
453   455  
454   @param other The waker that would be copied. 456   @param other The waker that would be copied.
455   */ 457   */
456   async_waker(async_waker const& other) = delete; 458   async_waker(async_waker const& other) = delete;
457   459  
458   /** Copy assignment is disabled; an armed waiter points into the waker. 460   /** Copy assignment is disabled; an armed waiter points into the waker.
459   461  
460   @param other The waker that would be assigned from. 462   @param other The waker that would be assigned from.
461   463  
462   @return A reference to `*this`. 464   @return A reference to `*this`.
463   */ 465   */
464   async_waker& operator=(async_waker const& other) = delete; 466   async_waker& operator=(async_waker const& other) = delete;
465   467  
466   /** Move construction is disabled; an armed waiter points into the 468   /** Move construction is disabled; an armed waiter points into the
467   waker. 469   waker.
468   470  
469   @param other The waker that would be moved from. 471   @param other The waker that would be moved from.
470   */ 472   */
471   async_waker(async_waker&& other) = delete; 473   async_waker(async_waker&& other) = delete;
472   474  
473   /** Move assignment is disabled; an armed waiter points into the waker. 475   /** Move assignment is disabled; an armed waiter points into the waker.
474   476  
475   @param other The waker that would be moved from. 477   @param other The waker that would be moved from.
476   478  
477   @return A reference to `*this`. 479   @return A reference to `*this`.
478   */ 480   */
479   async_waker& operator=(async_waker&& other) = delete; 481   async_waker& operator=(async_waker&& other) = delete;
480   482  
481   /** Asynchronously wait until woken. 483   /** Asynchronously wait until woken.
482   484  
483   If a token is latched, completes immediately and consumes 485   If a token is latched, completes immediately and consumes
484   it. Otherwise suspends until `wake()` or the stop token 486   it. Otherwise suspends until `wake()` or the stop token
485   fires. 487   fires.
486   488  
487   @par Preconditions 489   @par Preconditions
488   No other coroutine is currently waiting on this object; a 490   No other coroutine is currently waiting on this object; a
489   coroutine woken or canceled but not yet resumed still is. 491   coroutine woken or canceled but not yet resumed still is.
490   492  
491 - @return An awaitable that await-returns `io_result<>`; 493 + @return An awaitable that await-returns `results<>`;
492   empty on wakeup, `error::canceled` if the stop 494   empty on wakeup, `error::canceled` if the stop
493   token wins. 495   token wins.
494   */ 496   */
HITCBC 495   2442 wait_awaiter wait() noexcept 497   2453 wait_awaiter wait() noexcept
496   { 498   {
HITCBC 497   2442 return wait_awaiter{this}; 499   2453 return wait_awaiter{this};
498   } 500   }
499   501  
500   /** Wake the waiter, or latch the wakeup if none waits. 502   /** Wake the waiter, or latch the wakeup if none waits.
501   503  
502   Callable from any thread. The waiter's resumption is 504   Callable from any thread. The waiter's resumption is
503   posted through its executor; this call never resumes a 505   posted through its executor; this call never resumes a
504   coroutine inline. Multiple calls without an intervening 506   coroutine inline. Multiple calls without an intervening
505   `wait()` collapse into a single token. It may wait briefly 507   `wait()` collapse into a single token. It may wait briefly
506   for a waiter that is still suspending, or for a stop callback 508   for a waiter that is still suspending, or for a stop callback
507   of the waiter that is running on another thread. 509   of the waiter that is running on another thread.
508   */ 510   */
HITCBC 509   413 void wake() noexcept 511   413 void wake() noexcept
510   { 512   {
511   for(;;) 513   for(;;)
512   { 514   {
HITCBC 513   413 int s = st_.load(std::memory_order_acquire); 515   413 int s = st_.load(std::memory_order_acquire);
HITCBC 514   413 if(s == state_token) 516   413 if(s == state_token)
HITCBC 515   413 return; 517   413 return;
HITCBC 516   411 if(s == state_empty) 518   411 if(s == state_empty)
517   { 519   {
HITCBC 518   146 if(st_.compare_exchange_weak( 520   200 if(st_.compare_exchange_weak(
519   s, state_token, 521   s, state_token,
520   std::memory_order_acq_rel, 522   std::memory_order_acq_rel,
521   std::memory_order_acquire)) 523   std::memory_order_acquire))
HITCBC 522   73 return; 524   100 return;
EUB 523 - ✗ continue; 525 + continue; // LCOV_EXCL_LINE spurious weak CAS failure
524   } 526   }
525   // The waiter is still publishing itself; it settles in a 527   // The waiter is still publishing itself; it settles in a
526   // few instructions. 528   // few instructions.
HITCBC 527   338 if(s == state_arming) 529   311 if(s == state_arming)
528   { 530   {
EUB 529 - ✗ std::this_thread::yield(); 531 + std::this_thread::yield(); // LCOV_EXCL_LINE race with a waiter still publishing itself
EUB 530 - ✗ continue; 532 + continue; // LCOV_EXCL_LINE race with a waiter still publishing itself
531   } 533   }
532   // armed: winning this CAS claims the waiter, whose 534   // armed: winning this CAS claims the waiter, whose
533   // frame is pinned until we post its resumption. 535   // frame is pinned until we post its resumption.
HITCBC 534   676 if(st_.compare_exchange_weak( 536   622 if(st_.compare_exchange_weak(
535   s, state_empty, 537   s, state_empty,
536   std::memory_order_acq_rel, 538   std::memory_order_acq_rel,
537   std::memory_order_acquire)) 539   std::memory_order_acquire))
538   { 540   {
HITCBC 539   338 auto* w = waiter_; 541   311 auto* w = waiter_;
540   // Nothing may reach this waker once the waiter is 542   // Nothing may reach this waker once the waiter is
541   // posted; this waits out a stop callback already 543   // posted; this waits out a stop callback already
542   // running, which then finds the slot empty. 544   // running, which then finds the slot empty.
543   using cb_t = wait_awaiter::stop_cb_t; 545   using cb_t = wait_awaiter::stop_cb_t;
HITCBC 544   338 w->stop_cb_().~cb_t(); 546   311 w->stop_cb_().~cb_t();
HITCBC 545   338 w->active_ = false; 547   311 w->active_ = false;
HITCBC 546   338 w->claimed_.store(true, std::memory_order_release); 548   311 w->claimed_.store(true, std::memory_order_release);
HITCBC 547   338 w->ex_.post(w->cont_); 549   311 w->ex_.post(w->cont_);
HITCBC 548   338 return; 550   311 return;
549   } 551   }
EUB 550 - ✗ } 552 + } // LCOV_EXCL_LINE armed CAS lost to a stop callback or failed spuriously
551   } 553   }
552   }; 554   };
553   555  
554   } // namespace capy 556   } // namespace capy
555   } // namespace boost 557   } // namespace boost
556   558  
557   #endif 559   #endif