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   /** An asynchronous signal set for coroutine I/O. 54   /** An asynchronous signal set for coroutine I/O.
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   880 friend constexpr flags_t operator&(flags_t a, flags_t b) noexcept 130   880 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   880 static_cast<unsigned>(a) & static_cast<unsigned>(b)); 133   880 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 will own this signal set. 194   @param ctx The execution context that will own 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   199  
200   @param ctx The execution context that will own this signal set. 200   @param ctx The execution context that will own this signal set.
201   @param signal First signal number to add. 201   @param signal First signal number to add.
202   @param signals Additional signal numbers to add. 202   @param signals Additional signal numbers to add.
203   203  
204   @throws std::system_error Thrown on failure. 204   @throws std::system_error Thrown on failure.
205   205  
206   @see add for the non-throwing form: construct with the 206   @see add for the non-throwing form: construct with the
207   context alone, then `add()` each signal. 207   context alone, then `add()` each signal.
208   */ 208   */
209   template<std::convertible_to<int>... Signals> 209   template<std::convertible_to<int>... Signals>
HITCBC 210   62 signal_set(capy::execution_context& ctx, int signal, Signals... signals) 210   62 signal_set(capy::execution_context& ctx, int signal, Signals... signals)
HITCBC 211   62 : signal_set(ctx) 211   62 : signal_set(ctx)
212   { 212   {
HITCBC 213   80 auto check = [](std::error_code ec) { 213   80 auto check = [](std::error_code ec) {
HITCBC 214   80 if (ec) 214   80 if (ec)
MISUBC 215   throw std::system_error(ec); 215   throw std::system_error(ec);
216   }; 216   };
HITCBC 217   62 check(add(signal)); 217   62 check(add(signal));
HITCBC 218   15 (check(add(signals)), ...); 218   15 (check(add(signals)), ...);
HITCBC 219   62 } 219   62 }
220   220  
221   /** Construct an empty signal set from an executor. 221   /** Construct an empty signal set from an executor.
222   222  
223   The signal set is associated with the executor's context. 223   The signal set is associated with the executor's context.
224   224  
225   @param ex The executor whose context will own this signal set. 225   @param ex The executor whose context will own this signal set.
226   */ 226   */
227   template<class Ex> 227   template<class Ex>
228   requires(!std::same_as<std::remove_cvref_t<Ex>, signal_set>) && 228   requires(!std::same_as<std::remove_cvref_t<Ex>, signal_set>) &&
229   capy::Executor<Ex> 229   capy::Executor<Ex>
HITCBC 230   2 explicit signal_set(Ex const& ex) : signal_set(ex.context()) 230   2 explicit signal_set(Ex const& ex) : signal_set(ex.context())
231   { 231   {
HITCBC 232   2 } 232   2 }
233   233  
234   /** Construct a signal set with initial signals from an executor. 234   /** Construct a signal set with initial signals from an executor.
235   235  
236   The signal set is associated with the executor's context. 236   The signal set is associated with the executor's context.
237   237  
238   @param ex The executor whose context will own this signal set. 238   @param ex The executor whose context will own this signal set.
239   @param signal First signal number to add. 239   @param signal First signal number to add.
240   @param signals Additional signal numbers to add. 240   @param signals Additional signal numbers to add.
241   241  
242   @throws std::system_error Thrown on failure. 242   @throws std::system_error Thrown on failure.
243   243  
244   @see add for the non-throwing form: construct with the 244   @see add for the non-throwing form: construct with the
245   executor alone, then `add()` each signal. 245   executor alone, then `add()` each signal.
246   */ 246   */
247   template<class Ex, std::convertible_to<int>... Signals> 247   template<class Ex, std::convertible_to<int>... Signals>
248   requires capy::Executor<Ex> 248   requires capy::Executor<Ex>
HITCBC 249   2 signal_set(Ex const& ex, int signal, Signals... signals) 249   2 signal_set(Ex const& ex, int signal, Signals... signals)
HITCBC 250   2 : signal_set(ex.context(), signal, signals...) 250   2 : signal_set(ex.context(), signal, signals...)
251   { 251   {
HITCBC 252   2 } 252   2 }
253   253  
254   /** Move constructor. 254   /** Move constructor.
255   255  
256   Transfers ownership of the signal set resources. 256   Transfers ownership of the signal set resources.
257   257  
258   @param other The signal set to move from. 258   @param other The signal set to move from.
259   259  
260   @pre No awaitables returned by @p other's methods exist. 260   @pre No awaitables returned by @p other's methods exist.
261   @pre The execution context associated with @p other must 261   @pre The execution context associated with @p other must
262   outlive this signal set. 262   outlive this signal set.
263   */ 263   */
264   signal_set(signal_set&& other) noexcept; 264   signal_set(signal_set&& other) noexcept;
265   265  
266   /** Move assignment operator. 266   /** Move assignment operator.
267   267  
268   Closes any existing signal set and transfers ownership. 268   Closes any existing signal set and transfers ownership.
269   269  
270   @param other The signal set to move from. 270   @param other The signal set to move from.
271   271  
272   @pre No awaitables returned by either `*this` or @p other's 272   @pre No awaitables returned by either `*this` or @p other's
273   methods exist. 273   methods exist.
274   @pre The execution context associated with @p other must 274   @pre The execution context associated with @p other must
275   outlive this signal set. 275   outlive this signal set.
276   276  
277   @return Reference to this signal set. 277   @return Reference to this signal set.
278   */ 278   */
279   signal_set& operator=(signal_set&& other) noexcept; 279   signal_set& operator=(signal_set&& other) noexcept;
280   280  
281   signal_set(signal_set const&) = delete; 281   signal_set(signal_set const&) = delete;
282   signal_set& operator=(signal_set const&) = delete; 282   signal_set& operator=(signal_set const&) = delete;
283   283  
284   /** Add a signal to the signal set. 284   /** Add a signal to the signal set.
285   285  
286   This function adds the specified signal to the set with the 286   This function adds the specified signal to the set with the
287   specified flags. It has no effect if the signal is already 287   specified flags. It has no effect if the signal is already
288   in the set with the same flags. 288   in the set with the same flags.
289   289  
290   If the signal is already registered globally (by another 290   If the signal is already registered globally (by another
291   signal_set) and the flags differ, an error is returned 291   signal_set) and the flags differ, an error is returned
292   unless one of them has the `dont_care` flag. 292   unless one of them has the `dont_care` flag.
293   293  
294   The first signal registration on an execution context 294   The first signal registration on an execution context
295   installs the process signal-delivery pipe; if that 295   installs the process signal-delivery pipe; if that
296   installation fails the error is returned, and the next 296   installation fails the error is returned, and the next
297   call retries it. 297   call retries it.
298   298  
299   @param signal_number The signal to be added to the set. 299   @param signal_number The signal to be added to the set.
300   @param flags The flags to apply when registering the signal. 300   @param flags The flags to apply when registering the signal.
301   On POSIX systems, these map to sigaction() flags. 301   On POSIX systems, these map to sigaction() flags.
302   On Windows, only `none` and `dont_care` are supported; 302   On Windows, only `none` and `dont_care` are supported;
303   other flags cause `errc::operation_not_supported` to 303   other flags cause `errc::operation_not_supported` to
304   be returned. 304   be returned.
305   305  
306   @return Success, or an error if the signal could not be added. 306   @return Success, or an error if the signal could not be added.
307   Returns `errc::invalid_argument` if the signal is already 307   Returns `errc::invalid_argument` if the signal is already
308   registered with different flags. 308   registered with different flags.
309   */ 309   */
310   [[nodiscard]] std::error_code add(int signal_number, flags_t flags); 310   [[nodiscard]] std::error_code add(int signal_number, flags_t flags);
311   311  
312   /** Add a signal to the signal set with default flags. 312   /** Add a signal to the signal set with default flags.
313   313  
314   This is equivalent to calling `add(signal_number, none)`. 314   This is equivalent to calling `add(signal_number, none)`.
315   315  
316   @param signal_number The signal to be added to the set. 316   @param signal_number The signal to be added to the set.
317   317  
318   @return Success, or an error if the signal could not be added. 318   @return Success, or an error if the signal could not be added.
319   */ 319   */
HITCBC 320   145 [[nodiscard]] std::error_code add(int signal_number) 320   145 [[nodiscard]] std::error_code add(int signal_number)
321   { 321   {
HITCBC 322   145 return add(signal_number, none); 322   145 return add(signal_number, none);
323   } 323   }
324   324  
325   /** Remove a signal from the signal set. 325   /** Remove a signal from the signal set.
326   326  
327   This function removes the specified signal from the set. It has 327   This function removes the specified signal from the set. It has
328   no effect if the signal is not in the set. 328   no effect if the signal is not in the set.
329   329  
330   @param signal_number The signal to be removed from the set. 330   @param signal_number The signal to be removed from the set.
331   331  
332   @return Success, or an error if the signal could not be removed. 332   @return Success, or an error if the signal could not be removed.
333   */ 333   */
334   [[nodiscard]] std::error_code remove(int signal_number); 334   [[nodiscard]] std::error_code remove(int signal_number);
335   335  
336   /** Remove all signals from the signal set. 336   /** Remove all signals from the signal set.
337   337  
338   This function removes all signals from the set. It has no effect 338   This function removes all signals from the set. It has no effect
339   if the set is already empty. 339   if the set is already empty.
340   340  
341   @return Success, or an error if resetting any signal handler fails. 341   @return Success, or an error if resetting any signal handler fails.
342   */ 342   */
343   [[nodiscard]] std::error_code clear(); 343   [[nodiscard]] std::error_code clear();
344   344  
345   protected: 345   protected:
346   explicit signal_set(handle h) noexcept : io_signal_set(std::move(h)) {} 346   explicit signal_set(handle h) noexcept : io_signal_set(std::move(h)) {}
347   347  
348   private: 348   private:
349   void do_cancel() noexcept override; 349   void do_cancel() noexcept override;
350   350  
HITCBC 351   253 implementation& get() const noexcept 351   253 implementation& get() const noexcept
352   { 352   {
HITCBC 353   253 return *static_cast<implementation*>(h_.get()); 353   253 return *static_cast<implementation*>(h_.get());
354   } 354   }
355   }; 355   };
356   356  
357   } // namespace boost::corosio 357   } // namespace boost::corosio
358   358  
359   #endif 359   #endif