100.00% Lines (84/84) 100.00% Functions (15/15)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) 2   // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3   // 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_ASYNC_EVENT_HPP 11   #ifndef BOOST_CAPY_ASYNC_EVENT_HPP
12   #define BOOST_CAPY_ASYNC_EVENT_HPP 12   #define BOOST_CAPY_ASYNC_EVENT_HPP
13   13  
14   #include <boost/capy/detail/config.hpp> 14   #include <boost/capy/detail/config.hpp>
15   #include <boost/capy/detail/intrusive.hpp> 15   #include <boost/capy/detail/intrusive.hpp>
16   #include <boost/capy/continuation.hpp> 16   #include <boost/capy/continuation.hpp>
17   #include <boost/capy/concept/executor.hpp> 17   #include <boost/capy/concept/executor.hpp>
18   #include <boost/capy/error.hpp> 18   #include <boost/capy/error.hpp>
19   #include <boost/capy/ex/io_env.hpp> 19   #include <boost/capy/ex/io_env.hpp>
20   #include <boost/capy/io_result.hpp> 20   #include <boost/capy/io_result.hpp>
21   21  
22   #include <stop_token> 22   #include <stop_token>
23   23  
24   #include <atomic> 24   #include <atomic>
25   #include <coroutine> 25   #include <coroutine>
26   #include <new> 26   #include <new>
27   #include <utility> 27   #include <utility>
28   28  
29   /* async_event implementation notes 29   /* async_event implementation notes
30   ================================= 30   =================================
31   31  
32   Same cancellation pattern as async_mutex (see that file for the 32   Same cancellation pattern as async_mutex (see that file for the
33   full discussion on claimed_, stop_cb lifetime, member ordering, 33   full discussion on claimed_, stop_cb lifetime, member ordering,
34   and threading assumptions). 34   and threading assumptions).
35   35  
36   Key difference: set() wakes ALL waiters (broadcast), not one. 36   Key difference: set() wakes ALL waiters (broadcast), not one.
37   It pops every waiter from the list and posts the ones it 37   It pops every waiter from the list and posts the ones it
38   claims. Waiters already claimed by a stop callback are skipped. 38   claims. Waiters already claimed by a stop callback are skipped.
39   39  
40   Because set() pops all waiters, a canceled waiter may have been 40   Because set() pops all waiters, a canceled waiter may have been
41 - removed from the list by set() before its await_resume runs. 41 + removed from the list by set() before its await_resume runs. Each
42 - This requires a separate in_list_ flag (unlike async_mutex where 42 + waiter therefore records the list it is linked into, if any: the
43 - active_ served double duty). await_resume only calls remove() 43 + event's, or set()'s own while set() posts the waiters it claimed.
44 - when in_list_ is true. 44 + A waiter destroyed or canceled while linked unlinks itself from
  45 + that list, so a post that tears down one claimed waiter, and with
  46 + it another, never leaves set() holding a freed node.
45   */ 47   */
46   48  
47   namespace boost { 49   namespace boost {
48   namespace capy { 50   namespace capy {
49   51  
50   /** Queues coroutines in `wait()` and resumes all of them when `set()` is called. 52   /** Queues coroutines in `wait()` and resumes all of them when `set()` is called.
51   53  
52   This event provides a way to notify multiple coroutines that some 54   This event provides a way to notify multiple coroutines that some
53   condition has occurred. When a coroutine awaits an unset event, it 55   condition has occurred. When a coroutine awaits an unset event, it
54   suspends and is added to a wait queue. When the event is set, all 56   suspends and is added to a wait queue. When the event is set, all
55   waiting coroutines are resumed. 57   waiting coroutines are resumed.
56   58  
57   @par Cancellation 59   @par Cancellation
58   60  
59   When a coroutine is suspended waiting for the event and its stop 61   When a coroutine is suspended waiting for the event and its stop
60   token is triggered, the waiter completes with `error::canceled` 62   token is triggered, the waiter completes with `error::canceled`
61   instead of waiting for `set()`. 63   instead of waiting for `set()`.
62   64  
63   Cancellation only applies while the coroutine is suspended in the 65   Cancellation only applies while the coroutine is suspended in the
64   wait queue. If the event is already set when `wait()` is called, 66   wait queue. If the event is already set when `wait()` is called,
65   the wait completes immediately even if the stop token is already 67   the wait completes immediately even if the stop token is already
66   signaled. 68   signaled.
67   69  
68   @par Zero Allocation 70   @par Zero Allocation
69   71  
70   No heap allocation occurs for wait operations. 72   No heap allocation occurs for wait operations.
71   73  
72   @par Thread Safety 74   @par Thread Safety
73   75  
74   Distinct objects: Safe.@n 76   Distinct objects: Safe.@n
75   Shared objects: Unsafe. 77   Shared objects: Unsafe.
76   78  
77   The event operations are designed for single-threaded use on one 79   The event operations are designed for single-threaded use on one
78   executor. The stop callback may fire from any thread. 80   executor. The stop callback may fire from any thread.
79   81  
80   This type is non-copyable and non-movable because suspended 82   This type is non-copyable and non-movable because suspended
81   waiters hold intrusive pointers into the event's internal list. 83   waiters hold intrusive pointers into the event's internal list.
82   84  
83   @par Example 85   @par Example
84   @par !example example 86   @par !example example
85   87  
86   */ 88   */
87   class async_event 89   class async_event
88   { 90   {
89   public: 91   public:
90   class wait_awaiter; 92   class wait_awaiter;
91   93  
92   private: 94   private:
93   bool set_ = false; 95   bool set_ = false;
94   detail::intrusive_list<wait_awaiter> waiters_; 96   detail::intrusive_list<wait_awaiter> waiters_;
95   97  
  98 + // Unlink every waiter and move the ones this call claims into into.
  99 + void claim_all(detail::intrusive_list<wait_awaiter>& into) noexcept;
  100 +
96   public: 101   public:
97   /** Suspends the caller until `set()` runs, or resumes it with `error::canceled` on a stop request. 102   /** Suspends the caller until `set()` runs, or resumes it with `error::canceled` on a stop request.
98   */ 103   */
99   class wait_awaiter 104   class wait_awaiter
100   : public detail::intrusive_list<wait_awaiter>::node 105   : public detail::intrusive_list<wait_awaiter>::node
101   { 106   {
102   friend class async_event; 107   friend class async_event;
103   108  
104   async_event* e_; 109   async_event* e_;
105   continuation cont_; 110   continuation cont_;
106   executor_ref ex_; 111   executor_ref ex_;
107   112  
108   // Declared before stop_cb_buf_: the callback 113   // Declared before stop_cb_buf_: the callback
109   // accesses these members, so they must still be 114   // accesses these members, so they must still be
110   // alive if the stop_cb_ destructor blocks. 115   // alive if the stop_cb_ destructor blocks.
111   std::atomic<bool> claimed_{false}; 116   std::atomic<bool> claimed_{false};
112   bool canceled_ = false; 117   bool canceled_ = false;
113   bool active_ = false; 118   bool active_ = false;
114 - bool in_list_ = false; 119 + detail::intrusive_list<wait_awaiter>* list_ = nullptr;
115   120  
116   struct cancel_fn 121   struct cancel_fn
117   { 122   {
118   wait_awaiter* self_; 123   wait_awaiter* self_;
119   124  
HITCBC 120   4 void operator()() const noexcept 125   9 void operator()() const noexcept
121   { 126   {
HITCBC 122   4 if(!self_->claimed_.exchange( 127   9 if(!self_->claimed_.exchange(
123   true, std::memory_order_acq_rel)) 128   true, std::memory_order_acq_rel))
124   { 129   {
HITCBC 125   3 self_->canceled_ = true; 130   8 self_->canceled_ = true;
HITCBC 126   3 self_->ex_.post(self_->cont_); 131   8 self_->ex_.post(self_->cont_);
127   } 132   }
HITCBC 128   4 } 133   9 }
129   }; 134   };
130   135  
131   using stop_cb_t = 136   using stop_cb_t =
132   std::stop_callback<cancel_fn>; 137   std::stop_callback<cancel_fn>;
133   138  
134   // Aligned storage for stop_cb_t. Declared last: 139   // Aligned storage for stop_cb_t. Declared last:
135   // its destructor may block while the callback 140   // its destructor may block while the callback
136   // accesses the members above. 141   // accesses the members above.
137   BOOST_CAPY_MSVC_WARNING_PUSH 142   BOOST_CAPY_MSVC_WARNING_PUSH
138   BOOST_CAPY_MSVC_WARNING_DISABLE(4324) // padded due to alignas 143   BOOST_CAPY_MSVC_WARNING_DISABLE(4324) // padded due to alignas
139   alignas(stop_cb_t) 144   alignas(stop_cb_t)
140   unsigned char stop_cb_buf_[sizeof(stop_cb_t)]; 145   unsigned char stop_cb_buf_[sizeof(stop_cb_t)];
141   BOOST_CAPY_MSVC_WARNING_POP 146   BOOST_CAPY_MSVC_WARNING_POP
142   147  
HITCBC 143   20 stop_cb_t& stop_cb_() noexcept 148   93 stop_cb_t& stop_cb_() noexcept
144   { 149   {
145   return *reinterpret_cast<stop_cb_t*>( 150   return *reinterpret_cast<stop_cb_t*>(
HITCBC 146   20 stop_cb_buf_); 151   93 stop_cb_buf_);
147   } 152   }
148   153  
149   public: 154   public:
150   /** Destroy the awaiter, leaving the event unable to reach it. 155   /** Destroy the awaiter, leaving the event unable to reach it.
151   156  
152   Destroys the stop callback if one is registered, and unlinks 157   Destroys the stop callback if one is registered, and unlinks
153   the awaiter from the event's wait queue if it is still linked. 158   the awaiter from the event's wait queue if it is still linked.
154   Both are necessary when the coroutine frame is torn down while 159   Both are necessary when the coroutine frame is torn down while
155   suspended, so that neither `set()` nor the stop callback can 160   suspended, so that neither `set()` nor the stop callback can
156   reach a destroyed awaiter. 161   reach a destroyed awaiter.
157   */ 162   */
HITCBC 158   52 ~wait_awaiter() 163   200 ~wait_awaiter()
159   { 164   {
HITCBC 160   52 if(active_) 165   200 if(active_)
HITCBC 161   1 stop_cb_().~stop_cb_t(); 166   41 stop_cb_().~stop_cb_t();
HITCBC 162 - 52 if(in_list_) 167 + 200 if(list_)
HITCBC 163 - 1 e_->waiters_.remove(this); 168 + 5 list_->remove(this);
HITCBC 164   52 } 169   200 }
165   170  
166   /** Construct an awaiter for the given event. 171   /** Construct an awaiter for the given event.
167   172  
168   @param e The event to wait on. It must outlive the awaiter. 173   @param e The event to wait on. It must outlive the awaiter.
169   */ 174   */
HITCBC 170   25 explicit wait_awaiter(async_event* e) noexcept 175   100 explicit wait_awaiter(async_event* e) noexcept
HITCBC 171   25 : e_(e) 176   100 : e_(e)
172   { 177   {
HITCBC 173   25 } 178   100 }
174   179  
175   /** Construct by moving. 180   /** Construct by moving.
176   181  
177   The moved-from awaiter is left inert: its destructor no longer 182   The moved-from awaiter is left inert: its destructor no longer
178   destroys the stop callback and no longer unlinks from the 183   destroys the stop callback and no longer unlinks from the
179   event's wait queue. 184   event's wait queue.
180   185  
181   @param o The awaiter to move from. 186   @param o The awaiter to move from.
182   */ 187   */
HITCBC 183   27 wait_awaiter(wait_awaiter&& o) noexcept 188   100 wait_awaiter(wait_awaiter&& o) noexcept
HITCBC 184   54 : e_(o.e_) 189   200 : e_(o.e_)
HITCBC 185   27 , cont_(o.cont_) 190   100 , cont_(o.cont_)
HITCBC 186   27 , ex_(o.ex_) 191   100 , ex_(o.ex_)
HITCBC 187   27 , claimed_(o.claimed_.load( 192   100 , claimed_(o.claimed_.load(
188   std::memory_order_relaxed)) 193   std::memory_order_relaxed))
HITCBC 189   27 , canceled_(o.canceled_) 194   100 , canceled_(o.canceled_)
HITCBC 190   27 , active_(std::exchange(o.active_, false)) 195   100 , active_(std::exchange(o.active_, false))
HITCBC 191 - 54 , in_list_(std::exchange(o.in_list_, false)) 196 + 200 , list_(std::exchange(o.list_, nullptr))
192   { 197   {
HITCBC 193   27 } 198   100 }
194   199  
195   /** Copy construction is disabled; a waiter is linked into the 200   /** Copy construction is disabled; a waiter is linked into the
196   event's wait queue by address. 201   event's wait queue by address.
197   202  
198   @param other The awaiter that would be copied. 203   @param other The awaiter that would be copied.
199   */ 204   */
200   wait_awaiter(wait_awaiter const& other) = delete; 205   wait_awaiter(wait_awaiter const& other) = delete;
201   206  
202   /** Copy assignment is disabled; a waiter is linked into the 207   /** Copy assignment is disabled; a waiter is linked into the
203   event's wait queue by address. 208   event's wait queue by address.
204   209  
205   @param other The awaiter that would be assigned from. 210   @param other The awaiter that would be assigned from.
206   211  
207   @return A reference to `*this`. 212   @return A reference to `*this`.
208   */ 213   */
209   wait_awaiter& operator=(wait_awaiter const& other) = delete; 214   wait_awaiter& operator=(wait_awaiter const& other) = delete;
210   215  
211   /** Move assignment is disabled; a waiter is linked into the 216   /** Move assignment is disabled; a waiter is linked into the
212   event's wait queue by address. 217   event's wait queue by address.
213   218  
214   @param other The awaiter that would be moved from. 219   @param other The awaiter that would be moved from.
215   220  
216   @return A reference to `*this`. 221   @return A reference to `*this`.
217   */ 222   */
218   wait_awaiter& operator=(wait_awaiter&& other) = delete; 223   wait_awaiter& operator=(wait_awaiter&& other) = delete;
219   224  
220   /** Report whether the event is already set. 225   /** Report whether the event is already set.
221   226  
222   @return `true` if the event is set, in which case the awaiting 227   @return `true` if the event is set, in which case the awaiting
223   coroutine does not suspend; otherwise `false`. 228   coroutine does not suspend; otherwise `false`.
224   */ 229   */
HITCBC 225   25 bool await_ready() const noexcept 230   100 bool await_ready() const noexcept
226   { 231   {
HITCBC 227   25 return e_->set_; 232   100 return e_->set_;
228   } 233   }
229   234  
230   /** Enqueue the awaiting coroutine until the event is set. 235   /** Enqueue the awaiting coroutine until the event is set.
231   236  
232   This is the @ref IoAwaitable overload of `await_suspend`. 237   This is the @ref IoAwaitable overload of `await_suspend`.
233   238  
234   If a stop request is already pending on `env->stop_token`, the 239   If a stop request is already pending on `env->stop_token`, the
235   awaiter records the cancellation and does not enqueue. 240   awaiter records the cancellation and does not enqueue.
236   241  
237   Otherwise it stores `h` and `env->executor`, links itself into 242   Otherwise it stores `h` and `env->executor`, links itself into
238   the event's wait queue, and registers a stop callback on 243   the event's wait queue, and registers a stop callback on
239   `env->stop_token`. Exactly one of `set()` and that callback posts 244   `env->stop_token`. Exactly one of `set()` and that callback posts
240   `h` through the stored executor, whichever claims the waiter 245   `h` through the stored executor, whichever claims the waiter
241   first. Only the post is subject to that race. A losing stop 246   first. Only the post is subject to that race. A losing stop
242   callback does nothing at all, but `set()` unlinks every waiter it 247   callback does nothing at all, but `set()` unlinks every waiter it
243   pops whether it claims it or not. That is why @ref await_resume 248   pops whether it claims it or not. That is why @ref await_resume
244   unlinks a canceled waiter only when it is still linked. 249   unlinks a canceled waiter only when it is still linked.
245   250  
246   @param h The awaiting coroutine, resumed when the event is set 251   @param h The awaiting coroutine, resumed when the event is set
247   or the wait is canceled. 252   or the wait is canceled.
248   253  
249   @param env The execution environment. Its executor posts the 254   @param env The execution environment. Its executor posts the
250   resumption and its stop token is watched for the duration of 255   resumption and its stop token is watched for the duration of
251   the wait. It must outlive the wait. 256   the wait. It must outlive the wait.
252   257  
253   @return `h` if a stop request was already pending, which 258   @return `h` if a stop request was already pending, which
254   resumes the awaiting coroutine immediately without enqueuing 259   resumes the awaiting coroutine immediately without enqueuing
255   it. Otherwise `std::noop_coroutine()`, which leaves the 260   it. Otherwise `std::noop_coroutine()`, which leaves the
256   coroutine suspended and returns control to the resumer. 261   coroutine suspended and returns control to the resumer.
257   */ 262   */
258   std::coroutine_handle<> 263   std::coroutine_handle<>
HITCBC 259   21 await_suspend( 264   95 await_suspend(
260   std::coroutine_handle<> h, 265   std::coroutine_handle<> h,
261   io_env const* env) noexcept 266   io_env const* env) noexcept
262   { 267   {
HITCBC 263   21 if(env->stop_token.stop_requested()) 268   95 if(env->stop_token.stop_requested())
264   { 269   {
HITCBC 265   1 canceled_ = true; 270   2 canceled_ = true;
HITCBC 266   1 return h; 271   2 return h;
267   } 272   }
HITCBC 268   20 cont_.h = h; 273   93 cont_.h = h;
HITGNC   274 + 93 cont_.owner = env->owner;
HITCBC 269   20 ex_ = env->executor; 275   93 ex_ = env->executor;
HITCBC 270   20 e_->waiters_.push_back(this); 276   93 e_->waiters_.push_back(this);
HITCBC 271 - 20 in_list_ = true; 277 + 93 list_ = &e_->waiters_;
HITCBC 272   60 ::new(stop_cb_buf_) stop_cb_t( 278   279 ::new(stop_cb_buf_) stop_cb_t(
HITCBC 273   20 env->stop_token, cancel_fn{this}); 279   93 env->stop_token, cancel_fn{this});
HITCBC 274   20 active_ = true; 280   93 active_ = true;
HITCBC 275   20 return std::noop_coroutine(); 281   93 return std::noop_coroutine();
276   } 282   }
277   283  
278   /** Complete the wait and report the outcome. 284   /** Complete the wait and report the outcome.
279   285  
280   Destroys the stop callback if one is registered. If the wait 286   Destroys the stop callback if one is registered. If the wait
281   was canceled while still linked into the event's wait queue, 287   was canceled while still linked into the event's wait queue,
282   unlinks it. `set()` pops every waiter, so a canceled waiter may 288   unlinks it. `set()` pops every waiter, so a canceled waiter may
283   or may not still be linked when it resumes. 289   or may not still be linked when it resumes.
284   290  
285   @return An empty `io_result<>` if the event was set, or one 291   @return An empty `io_result<>` if the event was set, or one
286   holding `error::canceled` if the stop token fired first. 292   holding `error::canceled` if the stop token fired first.
287   */ 293   */
HITCBC 288   22 [[nodiscard]] io_result<> await_resume() noexcept 294   57 [[nodiscard]] io_result<> await_resume() noexcept
289   { 295   {
HITCBC 290   22 if(active_) 296   57 if(active_)
291   { 297   {
HITCBC 292   19 stop_cb_().~stop_cb_t(); 298   52 stop_cb_().~stop_cb_t();
HITCBC 293   19 active_ = false; 299   52 active_ = false;
294   } 300   }
HITCBC 295   22 if(canceled_) 301   57 if(canceled_)
296   { 302   {
HITCBC 297 - 4 if(in_list_) 303 + 38 if(list_)
298   { 304   {
HITCBC 299 - 3 e_->waiters_.remove(this); 305 + 3 list_->remove(this);
HITCBC 300 - 3 in_list_ = false; 306 + 3 list_ = nullptr;
301   } 307   }
HITCBC 302   8 return {make_error_code( 308   76 return {make_error_code(
HITCBC 303   4 error::canceled)}; 309   38 error::canceled)};
304   } 310   }
HITCBC 305   18 return {{}}; 311   19 return {{}};
306   } 312   }
307   }; 313   };
308   314  
309   /// Construct an unset event. 315   /// Construct an unset event.
HITGIC 310   async_event() = default; 316   2 async_event() = default;
311   317  
312   /** Copy construction is disabled; suspended waiters point into the 318   /** Copy construction is disabled; suspended waiters point into the
313   event's wait queue. 319   event's wait queue.
314   320  
315   @param other The event that would be copied. 321   @param other The event that would be copied.
316   */ 322   */
317   async_event(async_event const& other) = delete; 323   async_event(async_event const& other) = delete;
318   324  
319   /** Copy assignment is disabled; suspended waiters point into the 325   /** Copy assignment is disabled; suspended waiters point into the
320   event's wait queue. 326   event's wait queue.
321   327  
322   @param other The event that would be assigned from. 328   @param other The event that would be assigned from.
323   329  
324   @return A reference to `*this`. 330   @return A reference to `*this`.
325   */ 331   */
326   async_event& operator=(async_event const& other) = delete; 332   async_event& operator=(async_event const& other) = delete;
327   333  
328   /** Move construction is disabled; suspended waiters point into the 334   /** Move construction is disabled; suspended waiters point into the
329   event's wait queue. 335   event's wait queue.
330   336  
331   @param other The event that would be moved from. 337   @param other The event that would be moved from.
332   */ 338   */
333   async_event(async_event&& other) = delete; 339   async_event(async_event&& other) = delete;
334   340  
335   /** Move assignment is disabled; suspended waiters point into the 341   /** Move assignment is disabled; suspended waiters point into the
336   event's wait queue. 342   event's wait queue.
337   343  
338   @param other The event that would be moved from. 344   @param other The event that would be moved from.
339   345  
340   @return A reference to `*this`. 346   @return A reference to `*this`.
341   */ 347   */
342   async_event& operator=(async_event&& other) = delete; 348   async_event& operator=(async_event&& other) = delete;
343   349  
  350 + /** Destroy the event, canceling every coroutine still waiting.
  351 +
  352 + Each waiter is posted to its executor to resume with
  353 + `error::canceled`, as if its stop token had been triggered. An
  354 + executor that can no longer run it abandons it instead, possibly
  355 + before this destructor returns. Either way the waiter no longer
  356 + refers to the event.
  357 + */
HITGNC   358 + 30 ~async_event()
  359 + {
HITGNC   360 + 97 while(auto* w = waiters_.pop_front())
  361 + {
HITGNC   362 + 67 w->list_ = nullptr;
HITGNC   363 + 67 if(!w->claimed_.exchange(true, std::memory_order_acq_rel))
  364 + {
HITGNC   365 + 66 w->canceled_ = true;
HITGNC   366 + 66 w->ex_.post(w->cont_);
  367 + }
HITGNC   368 + 67 }
HITGNC   369 + 30 }
  370 +
344   /** Returns an awaiter that waits until the event is set. 371   /** Returns an awaiter that waits until the event is set.
345   372  
346   If the event is already set, completes immediately. 373   If the event is already set, completes immediately.
347   374  
348   @return An awaitable that await-returns `(error_code)`. 375   @return An awaitable that await-returns `(error_code)`.
349   */ 376   */
HITCBC 350   25 wait_awaiter wait() noexcept 377   100 wait_awaiter wait() noexcept
351   { 378   {
HITCBC 352   25 return wait_awaiter{this}; 379   100 return wait_awaiter{this};
353   } 380   }
354   381  
355   /** Resumes every waiting coroutine and marks the event set for later `wait()` calls. 382   /** Resumes every waiting coroutine and marks the event set for later `wait()` calls.
356   383  
357   All waiting coroutines are resumed. Canceled waiters 384   All waiting coroutines are resumed. Canceled waiters
358   are skipped. Subsequent calls to wait() complete 385   are skipped. Subsequent calls to wait() complete
359   immediately until clear() is called. 386   immediately until clear() is called.
  387 +
  388 + @note If posting a waiter's resumption throws, `std::terminate`
  389 + is called.
360   */ 390   */
HITCBC 361 - 17 void set() 391 + 20 void set() noexcept
362   { 392   {
HITCBC 363   17 set_ = true; 393   20 set_ = true;
364 - for(;;) 394 + // A post can destroy a waiter on the spot, and with it whatever
  395 + // owns this event; nothing below touches *this.
HITGNC   396 + 20 detail::intrusive_list<wait_awaiter> claimed;
HITGNC   397 + 20 claim_all(claimed);
HITGNC   398 + 38 while(auto* w = claimed.pop_front())
365   { 399   {
HITCBC 366 - 33 auto* w = waiters_.pop_front(); 400 + 18 w->list_ = nullptr;
HITCBC 367 - 33 if(!w) 401 + 18 w->ex_.post(w->cont_);
DCB 368 - 17 break;  
DCB 369 - 16 w->in_list_ = false;  
DCB 370 - 16 if(!w->claimed_.exchange(  
371 - true, std::memory_order_acq_rel))  
372 - {  
DCB 373 - 16 w->ex_.post(w->cont_);  
374 - }  
HITCBC 375   16 } 402   18 }
HITCBC 376   17 } 403   20 }
377   404  
378   /** Clears the event. 405   /** Clears the event.
379   406  
380   Subsequent calls to wait() suspend until 407   Subsequent calls to wait() suspend until
381   set() is called again. 408   set() is called again.
382   */ 409   */
HITCBC 383   2 void clear() noexcept 410   2 void clear() noexcept
384   { 411   {
HITCBC 385   2 set_ = false; 412   2 set_ = false;
HITCBC 386   2 } 413   2 }
387   414  
388   /** Returns true if the event is currently set. 415   /** Returns true if the event is currently set.
389   416  
390   @return `true` if the event is set; otherwise `false`. 417   @return `true` if the event is set; otherwise `false`.
391   */ 418   */
HITCBC 392   9 bool is_set() const noexcept 419   9 bool is_set() const noexcept
393   { 420   {
HITCBC 394   9 return set_; 421   9 return set_;
395   } 422   }
396   }; 423   };
  424 +
  425 + inline void
HITGNC   426 + 20 async_event::claim_all(
  427 + detail::intrusive_list<wait_awaiter>& into) noexcept
  428 + {
HITGNC   429 + 38 while(auto* w = waiters_.pop_front())
  430 + {
HITGNC   431 + 18 w->list_ = nullptr;
HITGNC   432 + 18 if(!w->claimed_.exchange(true, std::memory_order_acq_rel))
  433 + {
HITGNC   434 + 18 into.push_back(w);
HITGNC   435 + 18 w->list_ = &into;
  436 + }
HITGNC   437 + 18 }
HITGNC   438 + 20 }
397   439  
398   } // namespace capy 440   } // namespace capy
399   } // namespace boost 441   } // namespace boost
400   442  
401   #endif 443   #endif