95.65% Lines (22/23) 100.00% Functions (9/9)
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 Steve Gerbino 3   // Copyright (c) 2026 Steve Gerbino
4   // Copyright (c) 2026 Michael Vandeberg 4   // Copyright (c) 2026 Michael Vandeberg
5   // 5   //
6   // Distributed under the Boost Software License, Version 1.0. (See accompanying 6   // Distributed under the Boost Software License, Version 1.0. (See accompanying
7   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 7   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
8   // 8   //
9   // Official repository: https://github.com/cppalliance/corosio 9   // Official repository: https://github.com/cppalliance/corosio
10   // 10   //
11   11  
12   #ifndef BOOST_COROSIO_SIGNAL_SET_HPP 12   #ifndef BOOST_COROSIO_SIGNAL_SET_HPP
13   #define BOOST_COROSIO_SIGNAL_SET_HPP 13   #define BOOST_COROSIO_SIGNAL_SET_HPP
14   14  
15   #include <boost/corosio/detail/config.hpp> 15   #include <boost/corosio/detail/config.hpp>
16   #include <boost/corosio/io/io_signal_set.hpp> 16   #include <boost/corosio/io/io_signal_set.hpp>
17   #include <boost/capy/ex/execution_context.hpp> 17   #include <boost/capy/ex/execution_context.hpp>
18   #include <boost/capy/concept/executor.hpp> 18   #include <boost/capy/concept/executor.hpp>
19   19  
20   #include <concepts> 20   #include <concepts>
21   #include <system_error> 21   #include <system_error>
22   #include <type_traits> 22   #include <type_traits>
23   23  
24   /* 24   /*
25   Signal Set Public API 25   Signal Set Public API
26   ===================== 26   =====================
27   27  
28   This header provides the public interface for asynchronous signal handling. 28   This header provides the public interface for asynchronous signal handling.
29   The implementation is split across platform-specific files: 29   The implementation is split across platform-specific files:
30   - posix/signals.cpp: Uses sigaction() for robust signal handling 30   - posix/signals.cpp: Uses sigaction() for robust signal handling
31   - iocp/signals.cpp: Uses C runtime signal() (Windows lacks sigaction) 31   - iocp/signals.cpp: Uses C runtime signal() (Windows lacks sigaction)
32   32  
33   Key design decisions: 33   Key design decisions:
34   34  
35   1. Abstract flag values: The flags_t enum uses arbitrary bit positions 35   1. Abstract flag values: The flags_t enum uses arbitrary bit positions
36   (not SA_RESTART, etc.) to avoid including <signal.h> in public headers. 36   (not SA_RESTART, etc.) to avoid including <signal.h> in public headers.
37   The POSIX implementation maps these to actual SA_* constants internally. 37   The POSIX implementation maps these to actual SA_* constants internally.
38   38  
39   2. Flag conflict detection: When multiple signal_sets register for the 39   2. Flag conflict detection: When multiple signal_sets register for the
40   same signal, they must use compatible flags. The first registration 40   same signal, they must use compatible flags. The first registration
41   establishes the flags; subsequent registrations must match or use 41   establishes the flags; subsequent registrations must match or use
42   dont_care. 42   dont_care.
43   43  
44   3. Polymorphic implementation: implementation is an abstract base that 44   3. Polymorphic implementation: implementation is an abstract base that
45   platform-specific implementations (posix_signal, win_signal) 45   platform-specific implementations (posix_signal, win_signal)
46   derive from. This allows the public API to be platform-agnostic. 46   derive from. This allows the public API to be platform-agnostic.
47   47  
48   4. The inline add(int) overload avoids a virtual call for the common case 48   4. The inline add(int) overload avoids a virtual call for the common case
49   of adding signals without flags (delegates to add(int, none)). 49   of adding signals without flags (delegates to add(int, none)).
50   */ 50   */
51   51  
52   namespace boost::corosio { 52   namespace boost::corosio {
53   53  
54   /** Waits from a coroutine for one of a registered set of signals. 54   /** Waits from a coroutine for one of a registered set of signals.
55   55  
56   This class provides the ability to perform an asynchronous wait 56   This class provides the ability to perform an asynchronous wait
57   for one or more signals to occur. The signal set registers for 57   for one or more signals to occur. The signal set registers for
58   signals using sigaction() on POSIX systems or the C runtime 58   signals using sigaction() on POSIX systems or the C runtime
59   signal() function on Windows. 59   signal() function on Windows.
60   60  
61   @par Thread Safety 61   @par Thread Safety
62   Distinct objects: Safe.@n 62   Distinct objects: Safe.@n
63   Shared objects: Unsafe. A `signal_set` must not have concurrent 63   Shared objects: Unsafe. A `signal_set` must not have concurrent
64   wait operations. 64   wait operations.
65   65  
66   @par Semantics 66   @par Semantics
67   Wraps platform signal handling (`sigaction` on POSIX, C runtime 67   Wraps platform signal handling (`sigaction` on POSIX, C runtime
68   signal() on Windows). Operations dispatch to OS signal APIs 68   signal() on Windows). Operations dispatch to OS signal APIs
69   via the `io_context` reactor. 69   via the `io_context` reactor.
70   70  
71   @par Supported Signals 71   @par Supported Signals
72   On Windows, the following signals are supported: 72   On Windows, the following signals are supported:
73   SIGINT, SIGTERM, SIGABRT, SIGFPE, SIGILL, SIGSEGV. 73   SIGINT, SIGTERM, SIGABRT, SIGFPE, SIGILL, SIGSEGV.
74   74  
75   @par Example 75   @par Example
76   @par !example wait_for_shutdown 76   @par !example wait_for_shutdown
77   */ 77   */
78   class BOOST_COROSIO_DECL signal_set : public io_signal_set 78   class BOOST_COROSIO_DECL signal_set : public io_signal_set
79   { 79   {
80   public: 80   public:
81   /** Flags for signal registration. 81   /** Flags for signal registration.
82   82  
83   These flags control the behavior of signal handling. Multiple 83   These flags control the behavior of signal handling. Multiple
84   flags can be combined using the bitwise OR operator. 84   flags can be combined using the bitwise OR operator.
85   85  
86   @note Flags only have effect on POSIX systems. On Windows, 86   @note Flags only have effect on POSIX systems. On Windows,
87   only `none` and `dont_care` are supported. Other flags return 87   only `none` and `dont_care` are supported. Other flags return
88   `operation_not_supported`. 88   `operation_not_supported`.
89   */ 89   */
90   enum flags_t : unsigned 90   enum flags_t : unsigned
91   { 91   {
92   /// Use existing flags if signal is already registered. 92   /// Use existing flags if signal is already registered.
93   /// When adding a signal that's already registered by another 93   /// When adding a signal that's already registered by another
94   /// signal_set, this flag indicates acceptance of whatever 94   /// signal_set, this flag indicates acceptance of whatever
95   /// flags were used for the existing registration. 95   /// flags were used for the existing registration.
96   dont_care = 1u << 16, 96   dont_care = 1u << 16,
97   97  
98   /// No special flags. 98   /// No special flags.
99   none = 0, 99   none = 0,
100   100  
101   /// Restart interrupted system calls. 101   /// Restart interrupted system calls.
102   /// Equivalent to SA_RESTART on POSIX systems. 102   /// Equivalent to SA_RESTART on POSIX systems.
103   restart = 1u << 0, 103   restart = 1u << 0,
104   104  
105   /// Don't generate SIGCHLD when children stop. 105   /// Don't generate SIGCHLD when children stop.
106   /// Equivalent to SA_NOCLDSTOP on POSIX systems. 106   /// Equivalent to SA_NOCLDSTOP on POSIX systems.
107   no_child_stop = 1u << 1, 107   no_child_stop = 1u << 1,
108   108  
109   /// Don't create zombie processes on child termination. 109   /// Don't create zombie processes on child termination.
110   /// Equivalent to SA_NOCLDWAIT on POSIX systems. 110   /// Equivalent to SA_NOCLDWAIT on POSIX systems.
111   no_child_wait = 1u << 2, 111   no_child_wait = 1u << 2,
112   112  
113   /// Don't block the signal while its handler runs. 113   /// Don't block the signal while its handler runs.
114   /// Equivalent to SA_NODEFER on POSIX systems. 114   /// Equivalent to SA_NODEFER on POSIX systems.
115   no_defer = 1u << 3, 115   no_defer = 1u << 3,
116   116  
117   /// Reset handler to SIG_DFL after one invocation. 117   /// Reset handler to SIG_DFL after one invocation.
118   /// Equivalent to SA_RESETHAND on POSIX systems. 118   /// Equivalent to SA_RESETHAND on POSIX systems.
119   reset_handler = 1u << 4 119   reset_handler = 1u << 4
120   }; 120   };
121   121  
122   /// Combine two flag values. 122   /// Combine two flag values.
HITCBC 123   7 friend constexpr flags_t operator|(flags_t a, flags_t b) noexcept 123   7 friend constexpr flags_t operator|(flags_t a, flags_t b) noexcept
124   { 124   {
125   return static_cast<flags_t>( 125   return static_cast<flags_t>(
HITCBC 126   7 static_cast<unsigned>(a) | static_cast<unsigned>(b)); 126   7 static_cast<unsigned>(a) | static_cast<unsigned>(b));
127   } 127   }
128   128  
129   /// Mask two flag values. 129   /// Mask two flag values.
HITCBC 130   14591 friend constexpr flags_t operator&(flags_t a, flags_t b) noexcept 130   14573 friend constexpr flags_t operator&(flags_t a, flags_t b) noexcept
131   { 131   {
132   return static_cast<flags_t>( 132   return static_cast<flags_t>(
HITCBC 133   14591 static_cast<unsigned>(a) & static_cast<unsigned>(b)); 133   14573 static_cast<unsigned>(a) & static_cast<unsigned>(b));
134   } 134   }
135   135  
136   /// Compound assignment OR. 136   /// Compound assignment OR.
HITCBC 137   2 friend constexpr flags_t& operator|=(flags_t& a, flags_t b) noexcept 137   2 friend constexpr flags_t& operator|=(flags_t& a, flags_t b) noexcept
138   { 138   {
HITCBC 139   2 return a = a | b; 139   2 return a = a | b;
140   } 140   }
141   141  
142   /// Compound assignment AND. 142   /// Compound assignment AND.
143   friend constexpr flags_t& operator&=(flags_t& a, flags_t b) noexcept 143   friend constexpr flags_t& operator&=(flags_t& a, flags_t b) noexcept
144   { 144   {
145   return a = a & b; 145   return a = a & b;
146   } 146   }
147   147  
148   /// Bitwise NOT (complement). 148   /// Bitwise NOT (complement).
149   friend constexpr flags_t operator~(flags_t a) noexcept 149   friend constexpr flags_t operator~(flags_t a) noexcept
150   { 150   {
151   return static_cast<flags_t>(~static_cast<unsigned>(a)); 151   return static_cast<flags_t>(~static_cast<unsigned>(a));
152   } 152   }
153   153  
154   /** Define backend hooks for signal set operations. 154   /** Define backend hooks for signal set operations.
155   155  
156   Platform backends derive from this to provide signal 156   Platform backends derive from this to provide signal
157   registration via `sigaction` (POSIX) or the C runtime 157   registration via `sigaction` (POSIX) or the C runtime
158   signal() function (Windows). 158   signal() function (Windows).
159   */ 159   */
160   struct implementation : io_signal_set::implementation 160   struct implementation : io_signal_set::implementation
161   { 161   {
162   /** Register a signal with the given flags. 162   /** Register a signal with the given flags.
163   163  
164   @param signal_number The signal to register. 164   @param signal_number The signal to register.
165   @param flags Platform-specific signal handling flags. 165   @param flags Platform-specific signal handling flags.
166   166  
167   @return Error code on failure, empty on success. 167   @return Error code on failure, empty on success.
168   */ 168   */
169   virtual std::error_code add(int signal_number, flags_t flags) = 0; 169   virtual std::error_code add(int signal_number, flags_t flags) = 0;
170   170  
171   /** Unregister a signal. 171   /** Unregister a signal.
172   172  
173   @param signal_number The signal to remove. 173   @param signal_number The signal to remove.
174   174  
175   @return Error code on failure, empty on success. 175   @return Error code on failure, empty on success.
176   */ 176   */
177   virtual std::error_code remove(int signal_number) = 0; 177   virtual std::error_code remove(int signal_number) = 0;
178   178  
179   /** Unregister all signals. 179   /** Unregister all signals.
180   180  
181   @return Error code on failure, empty on success. 181   @return Error code on failure, empty on success.
182   */ 182   */
183   virtual std::error_code clear() = 0; 183   virtual std::error_code clear() = 0;
184   }; 184   };
185   185  
186   /** Destructor. 186   /** Destructor.
187   187  
188   Cancels any pending operations and releases signal resources. 188   Cancels any pending operations and releases signal resources.
189   */ 189   */
190   ~signal_set() override; 190   ~signal_set() override;
191   191  
192   /** Construct an empty signal set. 192   /** Construct an empty signal set.
193   193  
194   @param ctx The execution context that owns this signal set. 194   @param ctx The execution context that owns this signal set.
195   */ 195   */
196   explicit signal_set(capy::execution_context& ctx); 196   explicit signal_set(capy::execution_context& ctx);
197   197  
198   /** Construct a signal set with initial signals. 198   /** Construct a signal set with initial signals.
199 - For the non-throwing form, construct with the context alone,  
200 - then call @ref add for each signal.  
201 -  
202   199  
203   @param ctx The execution context that owns this signal set. 200   @param ctx The execution context that owns this signal set.
204   @param signal First signal number to add. 201   @param signal First signal number to add.
205   @param signals Additional signal numbers to add. 202   @param signals Additional signal numbers to add.
206   203  
207   @throws std::system_error Thrown on failure. 204   @throws std::system_error Thrown on failure.
208   205  
209 - @see @ref add 206 + @see add for the non-throwing form: construct with the
  207 + context alone, then `add()` each signal.
210   */ 208   */
211   template<std::convertible_to<int>... Signals> 209   template<std::convertible_to<int>... Signals>
HITCBC 212   3365 signal_set(capy::execution_context& ctx, int signal, Signals... signals) 210   3361 signal_set(capy::execution_context& ctx, int signal, Signals... signals)
HITCBC 213   3365 : signal_set(ctx) 211   3361 : signal_set(ctx)
214   { 212   {
HITCBC 215   3383 auto check = [](std::error_code ec) { 213   3379 auto check = [](std::error_code ec) {
HITCBC 216   3383 if (ec) 214   3379 if (ec)
MISUBC 217   ✗ throw std::system_error(ec); 215   ✗ throw std::system_error(ec);
218   }; 216   };
HITCBC 219   3365 check(add(signal)); 217   3361 check(add(signal));
HITCBC 220   15 (check(add(signals)), ...); 218   15 (check(add(signals)), ...);
HITCBC 221   3365 } 219   3361 }
222   220  
223   /** Construct an empty signal set from an executor. 221   /** Construct an empty signal set from an executor.
224   222  
225   The signal set is associated with the executor's context. 223   The signal set is associated with the executor's context.
226   224  
227   @param ex The executor whose context owns this signal set. 225   @param ex The executor whose context owns this signal set.
228   */ 226   */
229   template<class Ex> 227   template<class Ex>
230   requires(!std::same_as<std::remove_cvref_t<Ex>, signal_set>) && 228   requires(!std::same_as<std::remove_cvref_t<Ex>, signal_set>) &&
231   capy::Executor<Ex> 229   capy::Executor<Ex>
HITCBC 232   2 explicit signal_set(Ex const& ex) : signal_set(ex.context()) 230   2 explicit signal_set(Ex const& ex) : signal_set(ex.context())
233   { 231   {
HITCBC 234   2 } 232   2 }
235   233  
236   /** Construct a signal set with initial signals from an executor. 234   /** Construct a signal set with initial signals from an executor.
237   235  
238 - For the non-throwing form, construct with the executor alone,  
239 - then call @ref add for each signal.  
240   The signal set is associated with the executor's context. 236   The signal set is associated with the executor's context.
241   237  
242   @param ex The executor whose context owns this signal set. 238   @param ex The executor whose context owns this signal set.
243   @param signal First signal number to add. 239   @param signal First signal number to add.
244   @param signals Additional signal numbers to add. 240   @param signals Additional signal numbers to add.
245   241  
246   @throws std::system_error Thrown on failure. 242   @throws std::system_error Thrown on failure.
247   243  
248 - @see @ref add 244 + @see add for the non-throwing form: construct with the
  245 + executor alone, then `add()` each signal.
249   */ 246   */
250   template<class Ex, std::convertible_to<int>... Signals> 247   template<class Ex, std::convertible_to<int>... Signals>
251   requires capy::Executor<Ex> 248   requires capy::Executor<Ex>
HITCBC 252   2 signal_set(Ex const& ex, int signal, Signals... signals) 249   2 signal_set(Ex const& ex, int signal, Signals... signals)
HITCBC 253   2 : signal_set(ex.context(), signal, signals...) 250   2 : signal_set(ex.context(), signal, signals...)
254   { 251   {
HITCBC 255   2 } 252   2 }
256   253  
257   /** Move constructor. 254   /** Move constructor.
258   255  
259   Transfers ownership of the signal set resources. 256   Transfers ownership of the signal set resources.
260   257  
261   @param other The signal set to move from. 258   @param other The signal set to move from.
262   259  
263   @pre No awaitables returned by @p other's methods exist. 260   @pre No awaitables returned by @p other's methods exist.
264   @pre The execution context associated with @p other must 261   @pre The execution context associated with @p other must
265   outlive this signal set. 262   outlive this signal set.
266   */ 263   */
267   signal_set(signal_set&& other) noexcept; 264   signal_set(signal_set&& other) noexcept;
268   265  
269   /** Move assignment operator. 266   /** Move assignment operator.
270   267  
271   Closes any existing signal set and transfers ownership. 268   Closes any existing signal set and transfers ownership.
272   269  
273   @param other The signal set to move from. 270   @param other The signal set to move from.
274   271  
275   @pre No awaitables returned by either `*this` or @p other's 272   @pre No awaitables returned by either `*this` or @p other's
276   methods exist. 273   methods exist.
277   @pre The execution context associated with @p other must 274   @pre The execution context associated with @p other must
278   outlive this signal set. 275   outlive this signal set.
279   276  
280   @return Reference to this signal set. 277   @return Reference to this signal set.
281   */ 278   */
282   signal_set& operator=(signal_set&& other) noexcept; 279   signal_set& operator=(signal_set&& other) noexcept;
283   280  
284   /// Copy construction is disabled; the handle is uniquely owned. 281   /// Copy construction is disabled; the handle is uniquely owned.
285   signal_set(signal_set const&) = delete; 282   signal_set(signal_set const&) = delete;
286   /// Copy assignment is disabled; the handle is uniquely owned. 283   /// Copy assignment is disabled; the handle is uniquely owned.
287   signal_set& operator=(signal_set const&) = delete; 284   signal_set& operator=(signal_set const&) = delete;
288   285  
289   /** Add a signal to the signal set. 286   /** Add a signal to the signal set.
290   287  
291   This function adds the specified signal to the set with the 288   This function adds the specified signal to the set with the
292   specified flags. It has no effect if the signal is already 289   specified flags. It has no effect if the signal is already
293   in the set with the same flags. 290   in the set with the same flags.
294   291  
295   Another `signal_set` may already have registered the signal 292   Another `signal_set` may already have registered the signal
296   globally. If the flags then differ, an error is returned unless 293   globally. If the flags then differ, an error is returned unless
297   one of them has the `dont_care` flag. 294   one of them has the `dont_care` flag.
298   295  
299   The first signal registration on an execution context installs 296   The first signal registration on an execution context installs
300   the process signal-delivery pipe. If that installation fails, 297   the process signal-delivery pipe. If that installation fails,
301   the error is returned. The next call retries it. 298   the error is returned. The next call retries it.
302   299  
303   @param signal_number The signal to be added to the set. 300   @param signal_number The signal to be added to the set.
304   @param flags The flags to apply when registering the signal. 301   @param flags The flags to apply when registering the signal.
305   On POSIX systems, these map to sigaction() flags. 302   On POSIX systems, these map to sigaction() flags.
306   On Windows, only `none` and `dont_care` are supported. 303   On Windows, only `none` and `dont_care` are supported.
307   Other flags cause `errc::operation_not_supported` to 304   Other flags cause `errc::operation_not_supported` to
308   be returned. 305   be returned.
309   306  
310   @return Success, or an error if the signal could not be added. 307   @return Success, or an error if the signal could not be added.
311   Returns `errc::invalid_argument` if the signal is already 308   Returns `errc::invalid_argument` if the signal is already
312   registered with different flags. 309   registered with different flags.
313   */ 310   */
314   [[nodiscard]] std::error_code add(int signal_number, flags_t flags); 311   [[nodiscard]] std::error_code add(int signal_number, flags_t flags);
315   312  
316   /** Add a signal to the signal set with default flags. 313   /** Add a signal to the signal set with default flags.
317   314  
318   This is equivalent to calling `add(signal_number, none)`. 315   This is equivalent to calling `add(signal_number, none)`.
319   316  
320   @param signal_number The signal to be added to the set. 317   @param signal_number The signal to be added to the set.
321   318  
322   @return Success, or an error if the signal could not be added. 319   @return Success, or an error if the signal could not be added.
323   */ 320   */
HITCBC 324   3448 [[nodiscard]] std::error_code add(int signal_number) 321   3444 [[nodiscard]] std::error_code add(int signal_number)
325   { 322   {
HITCBC 326   3448 return add(signal_number, none); 323   3444 return add(signal_number, none);
327   } 324   }
328   325  
329   /** Remove a signal from the signal set. 326   /** Remove a signal from the signal set.
330   327  
331   This function removes the specified signal from the set. It has 328   This function removes the specified signal from the set. It has
332   no effect if the signal is not in the set. 329   no effect if the signal is not in the set.
333   330  
334   @param signal_number The signal to be removed from the set. 331   @param signal_number The signal to be removed from the set.
335   332  
336   @return Success, or an error if the signal could not be removed. 333   @return Success, or an error if the signal could not be removed.
337   */ 334   */
338   [[nodiscard]] std::error_code remove(int signal_number); 335   [[nodiscard]] std::error_code remove(int signal_number);
339   336  
340   /** Remove all signals from the signal set. 337   /** Remove all signals from the signal set.
341   338  
342   This function removes all signals from the set. It has no effect 339   This function removes all signals from the set. It has no effect
343   if the set is already empty. 340   if the set is already empty.
344   341  
345   @return Success, or an error if resetting any signal handler fails. 342   @return Success, or an error if resetting any signal handler fails.
346   */ 343   */
347   [[nodiscard]] std::error_code clear(); 344   [[nodiscard]] std::error_code clear();
348   345  
349   protected: 346   protected:
350   /** Adopt an existing handle. 347   /** Adopt an existing handle.
351   348  
352   @param h The handle the signal set takes ownership of. 349   @param h The handle the signal set takes ownership of.
353   */ 350   */
354   explicit signal_set(handle h) noexcept : io_signal_set(std::move(h)) {} 351   explicit signal_set(handle h) noexcept : io_signal_set(std::move(h)) {}
355   352  
356   private: 353   private:
357   void do_cancel() noexcept override; 354   void do_cancel() noexcept override;
358   355  
HITCBC 359   203562 implementation& get() const noexcept 356   203558 implementation& get() const noexcept
360   { 357   {
HITCBC 361   203562 return *static_cast<implementation*>(h_.get()); 358   203558 return *static_cast<implementation*>(h_.get());
362   } 359   }
363   }; 360   };
364   361  
365   } // namespace boost::corosio 362   } // namespace boost::corosio
366   363  
367   #endif 364   #endif