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