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