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