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