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