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   co_return; 79   co_return;
80   if (!ec) 80   if (!ec)
81   std::cout << "Received signal " << signum << std::endl; 81   std::cout << "Received signal " << signum << std::endl;
82   @endcode 82   @endcode
83   */ 83   */
84   class BOOST_COROSIO_DECL signal_set : public io_signal_set 84   class BOOST_COROSIO_DECL signal_set : public io_signal_set
85   { 85   {
86   public: 86   public:
87   /** Flags for signal registration. 87   /** Flags for signal registration.
88   88  
89   These flags control the behavior of signal handling. Multiple 89   These flags control the behavior of signal handling. Multiple
90   flags can be combined using the bitwise OR operator. 90   flags can be combined using the bitwise OR operator.
91   91  
92   @note Flags only have effect on POSIX systems. On Windows, 92   @note Flags only have effect on POSIX systems. On Windows,
93   only `none` and `dont_care` are supported; other flags return 93   only `none` and `dont_care` are supported; other flags return
94   `operation_not_supported`. 94   `operation_not_supported`.
95   */ 95   */
96   enum flags_t : unsigned 96   enum flags_t : unsigned
97   { 97   {
98   /// Use existing flags if signal is already registered. 98   /// Use existing flags if signal is already registered.
99   /// When adding a signal that's already registered by another 99   /// When adding a signal that's already registered by another
100   /// signal_set, this flag indicates acceptance of whatever 100   /// signal_set, this flag indicates acceptance of whatever
101   /// flags were used for the existing registration. 101   /// flags were used for the existing registration.
102   dont_care = 1u << 16, 102   dont_care = 1u << 16,
103   103  
104   /// No special flags. 104   /// No special flags.
105   none = 0, 105   none = 0,
106   106  
107   /// Restart interrupted system calls. 107   /// Restart interrupted system calls.
108   /// Equivalent to SA_RESTART on POSIX systems. 108   /// Equivalent to SA_RESTART on POSIX systems.
109   restart = 1u << 0, 109   restart = 1u << 0,
110   110  
111   /// Don't generate SIGCHLD when children stop. 111   /// Don't generate SIGCHLD when children stop.
112   /// Equivalent to SA_NOCLDSTOP on POSIX systems. 112   /// Equivalent to SA_NOCLDSTOP on POSIX systems.
113   no_child_stop = 1u << 1, 113   no_child_stop = 1u << 1,
114   114  
115   /// Don't create zombie processes on child termination. 115   /// Don't create zombie processes on child termination.
116   /// Equivalent to SA_NOCLDWAIT on POSIX systems. 116   /// Equivalent to SA_NOCLDWAIT on POSIX systems.
117   no_child_wait = 1u << 2, 117   no_child_wait = 1u << 2,
118   118  
119   /// Don't block the signal while its handler runs. 119   /// Don't block the signal while its handler runs.
120   /// Equivalent to SA_NODEFER on POSIX systems. 120   /// Equivalent to SA_NODEFER on POSIX systems.
121   no_defer = 1u << 3, 121   no_defer = 1u << 3,
122   122  
123   /// Reset handler to SIG_DFL after one invocation. 123   /// Reset handler to SIG_DFL after one invocation.
124   /// Equivalent to SA_RESETHAND on POSIX systems. 124   /// Equivalent to SA_RESETHAND on POSIX systems.
125   reset_handler = 1u << 4 125   reset_handler = 1u << 4
126   }; 126   };
127   127  
128   /// Combine two flag values. 128   /// Combine two flag values.
HITCBC 129   5 friend constexpr flags_t operator|(flags_t a, flags_t b) noexcept 129   5 friend constexpr flags_t operator|(flags_t a, flags_t b) noexcept
130   { 130   {
131   return static_cast<flags_t>( 131   return static_cast<flags_t>(
HITCBC 132   5 static_cast<unsigned>(a) | static_cast<unsigned>(b)); 132   5 static_cast<unsigned>(a) | static_cast<unsigned>(b));
133   } 133   }
134   134  
135   /// Mask two flag values. 135   /// Mask two flag values.
HITCBC 136   659 friend constexpr flags_t operator&(flags_t a, flags_t b) noexcept 136   732 friend constexpr flags_t operator&(flags_t a, flags_t b) noexcept
137   { 137   {
138   return static_cast<flags_t>( 138   return static_cast<flags_t>(
HITCBC 139   659 static_cast<unsigned>(a) & static_cast<unsigned>(b)); 139   732 static_cast<unsigned>(a) & static_cast<unsigned>(b));
140   } 140   }
141   141  
142   /// Compound assignment OR. 142   /// Compound assignment OR.
HITCBC 143   2 friend constexpr flags_t& operator|=(flags_t& a, flags_t b) noexcept 143   2 friend constexpr flags_t& operator|=(flags_t& a, flags_t b) noexcept
144   { 144   {
HITCBC 145   2 return a = a | b; 145   2 return a = a | b;
146   } 146   }
147   147  
148   /// Compound assignment AND. 148   /// Compound assignment AND.
149   friend constexpr flags_t& operator&=(flags_t& a, flags_t b) noexcept 149   friend constexpr flags_t& operator&=(flags_t& a, flags_t b) noexcept
150   { 150   {
151   return a = a & b; 151   return a = a & b;
152   } 152   }
153   153  
154   /// Bitwise NOT (complement). 154   /// Bitwise NOT (complement).
155   friend constexpr flags_t operator~(flags_t a) noexcept 155   friend constexpr flags_t operator~(flags_t a) noexcept
156   { 156   {
157   return static_cast<flags_t>(~static_cast<unsigned>(a)); 157   return static_cast<flags_t>(~static_cast<unsigned>(a));
158   } 158   }
159   159  
160   /** Define backend hooks for signal set operations. 160   /** Define backend hooks for signal set operations.
161   161  
162   Platform backends derive from this to provide signal 162   Platform backends derive from this to provide signal
163   registration via sigaction (POSIX) or the C runtime 163   registration via sigaction (POSIX) or the C runtime
164   signal() function (Windows). 164   signal() function (Windows).
165   */ 165   */
166   struct implementation : io_signal_set::implementation 166   struct implementation : io_signal_set::implementation
167   { 167   {
168   /** Register a signal with the given flags. 168   /** Register a signal with the given flags.
169   169  
170   @param signal_number The signal to register. 170   @param signal_number The signal to register.
171   @param flags Platform-specific signal handling flags. 171   @param flags Platform-specific signal handling flags.
172   172  
173   @return Error code on failure, empty on success. 173   @return Error code on failure, empty on success.
174   */ 174   */
175   virtual std::error_code add(int signal_number, flags_t flags) = 0; 175   virtual std::error_code add(int signal_number, flags_t flags) = 0;
176   176  
177   /** Unregister a signal. 177   /** Unregister a signal.
178   178  
179   @param signal_number The signal to remove. 179   @param signal_number The signal to remove.
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 remove(int signal_number) = 0; 183   virtual std::error_code remove(int signal_number) = 0;
184   184  
185   /** Unregister all signals. 185   /** Unregister all signals.
186   186  
187   @return Error code on failure, empty on success. 187   @return Error code on failure, empty on success.
188   */ 188   */
189   virtual std::error_code clear() = 0; 189   virtual std::error_code clear() = 0;
190   }; 190   };
191   191  
192   /** Destructor. 192   /** Destructor.
193   193  
194   Cancels any pending operations and releases signal resources. 194   Cancels any pending operations and releases signal resources.
195   */ 195   */
196   ~signal_set() override; 196   ~signal_set() override;
197   197  
198   /** Construct an empty signal set. 198   /** Construct an empty signal set.
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   */ 201   */
202   explicit signal_set(capy::execution_context& ctx); 202   explicit signal_set(capy::execution_context& ctx);
203   203  
204   /** Construct a signal set with initial signals. 204   /** Construct a signal set with initial signals.
205   205  
206   @param ctx The execution context that will own this signal set. 206   @param ctx The execution context that will own this signal set.
207   @param signal First signal number to add. 207   @param signal First signal number to add.
208   @param signals Additional signal numbers to add. 208   @param signals Additional signal numbers to add.
209   209  
210   @throws std::system_error Thrown on failure. 210   @throws std::system_error Thrown on failure.
211   211  
212   @see add for the non-throwing form: construct with the 212   @see add for the non-throwing form: construct with the
213   context alone, then `add()` each signal. 213   context alone, then `add()` each signal.
214   */ 214   */
215   template<std::convertible_to<int>... Signals> 215   template<std::convertible_to<int>... Signals>
HITCBC 216   60 signal_set(capy::execution_context& ctx, int signal, Signals... signals) 216   62 signal_set(capy::execution_context& ctx, int signal, Signals... signals)
HITCBC 217   60 : signal_set(ctx) 217   62 : signal_set(ctx)
218   { 218   {
HITCBC 219   78 auto check = [](std::error_code ec) { 219   80 auto check = [](std::error_code ec) {
HITCBC 220   78 if (ec) 220   80 if (ec)
MISUBC 221   throw std::system_error(ec); 221   throw std::system_error(ec);
222   }; 222   };
HITCBC 223   60 check(add(signal)); 223   62 check(add(signal));
HITCBC 224   15 (check(add(signals)), ...); 224   15 (check(add(signals)), ...);
HITCBC 225   60 } 225   62 }
226   226  
227   /** Construct an empty signal set from an executor. 227   /** Construct an empty signal set from an executor.
228   228  
229   The signal set is associated with the executor's context. 229   The signal set is associated with the executor's context.
230   230  
231   @param ex The executor whose context will own this signal set. 231   @param ex The executor whose context will own this signal set.
232   */ 232   */
233   template<class Ex> 233   template<class Ex>
234   requires(!std::same_as<std::remove_cvref_t<Ex>, signal_set>) && 234   requires(!std::same_as<std::remove_cvref_t<Ex>, signal_set>) &&
235   capy::Executor<Ex> 235   capy::Executor<Ex>
HITCBC 236   2 explicit signal_set(Ex const& ex) : signal_set(ex.context()) 236   2 explicit signal_set(Ex const& ex) : signal_set(ex.context())
237   { 237   {
HITCBC 238   2 } 238   2 }
239   239  
240   /** Construct a signal set with initial signals from an executor. 240   /** Construct a signal set with initial signals from an executor.
241   241  
242   The signal set is associated with the executor's context. 242   The signal set is associated with the executor's context.
243   243  
244   @param ex The executor whose context will own this signal set. 244   @param ex The executor whose context will own this signal set.
245   @param signal First signal number to add. 245   @param signal First signal number to add.
246   @param signals Additional signal numbers to add. 246   @param signals Additional signal numbers to add.
247   247  
248   @throws std::system_error Thrown on failure. 248   @throws std::system_error Thrown on failure.
249   249  
250   @see add for the non-throwing form: construct with the 250   @see add for the non-throwing form: construct with the
251   executor alone, then `add()` each signal. 251   executor alone, then `add()` each signal.
252   */ 252   */
253   template<class Ex, std::convertible_to<int>... Signals> 253   template<class Ex, std::convertible_to<int>... Signals>
254   requires capy::Executor<Ex> 254   requires capy::Executor<Ex>
HITCBC 255   2 signal_set(Ex const& ex, int signal, Signals... signals) 255   2 signal_set(Ex const& ex, int signal, Signals... signals)
HITCBC 256   2 : signal_set(ex.context(), signal, signals...) 256   2 : signal_set(ex.context(), signal, signals...)
257   { 257   {
HITCBC 258   2 } 258   2 }
259   259  
260   /** Move constructor. 260   /** Move constructor.
261   261  
262   Transfers ownership of the signal set resources. 262   Transfers ownership of the signal set resources.
263   263  
264   @param other The signal set to move from. 264   @param other The signal set to move from.
265   265  
266   @pre No awaitables returned by @p other's methods exist. 266   @pre No awaitables returned by @p other's methods exist.
267   @pre The execution context associated with @p other must 267   @pre The execution context associated with @p other must
268   outlive this signal set. 268   outlive this signal set.
269   */ 269   */
270   signal_set(signal_set&& other) noexcept; 270   signal_set(signal_set&& other) noexcept;
271   271  
272   /** Move assignment operator. 272   /** Move assignment operator.
273   273  
274   Closes any existing signal set and transfers ownership. 274   Closes any existing signal set and transfers ownership.
275   275  
276   @param other The signal set to move from. 276   @param other The signal set to move from.
277   277  
278   @pre No awaitables returned by either `*this` or @p other's 278   @pre No awaitables returned by either `*this` or @p other's
279   methods exist. 279   methods exist.
280   @pre The execution context associated with @p other must 280   @pre The execution context associated with @p other must
281   outlive this signal set. 281   outlive this signal set.
282   282  
283   @return Reference to this signal set. 283   @return Reference to this signal set.
284   */ 284   */
285   signal_set& operator=(signal_set&& other) noexcept; 285   signal_set& operator=(signal_set&& other) noexcept;
286   286  
287   signal_set(signal_set const&) = delete; 287   signal_set(signal_set const&) = delete;
288   signal_set& operator=(signal_set const&) = delete; 288   signal_set& operator=(signal_set const&) = delete;
289   289  
290   /** Add a signal to the signal set. 290   /** Add a signal to the signal set.
291   291  
292   This function adds the specified signal to the set with the 292   This function adds the specified signal to the set with the
293   specified flags. It has no effect if the signal is already 293   specified flags. It has no effect if the signal is already
294   in the set with the same flags. 294   in the set with the same flags.
295   295  
296   If the signal is already registered globally (by another 296   If the signal is already registered globally (by another
297   signal_set) and the flags differ, an error is returned 297   signal_set) and the flags differ, an error is returned
298   unless one of them has the `dont_care` flag. 298   unless one of them has the `dont_care` flag.
299   299  
300   The first signal registration on an execution context 300   The first signal registration on an execution context
301   installs the process signal-delivery pipe; if that 301   installs the process signal-delivery pipe; if that
302   installation fails the error is returned, and the next 302   installation fails the error is returned, and the next
303   call retries it. 303   call retries it.
304   304  
305   @param signal_number The signal to be added to the set. 305   @param signal_number The signal to be added to the set.
306   @param flags The flags to apply when registering the signal. 306   @param flags The flags to apply when registering the signal.
307   On POSIX systems, these map to sigaction() flags. 307   On POSIX systems, these map to sigaction() flags.
308   On Windows, only `none` and `dont_care` are supported; 308   On Windows, only `none` and `dont_care` are supported;
309   other flags cause `errc::operation_not_supported` to 309   other flags cause `errc::operation_not_supported` to
310   be returned. 310   be returned.
311   311  
312   @return Success, or an error if the signal could not be added. 312   @return Success, or an error if the signal could not be added.
313   Returns `errc::invalid_argument` if the signal is already 313   Returns `errc::invalid_argument` if the signal is already
314   registered with different flags. 314   registered with different flags.
315   */ 315   */
316   [[nodiscard]] std::error_code add(int signal_number, flags_t flags); 316   [[nodiscard]] std::error_code add(int signal_number, flags_t flags);
317   317  
318   /** Add a signal to the signal set with default flags. 318   /** Add a signal to the signal set with default flags.
319   319  
320   This is equivalent to calling `add(signal_number, none)`. 320   This is equivalent to calling `add(signal_number, none)`.
321   321  
322   @param signal_number The signal to be added to the set. 322   @param signal_number The signal to be added to the set.
323   323  
324   @return Success, or an error if the signal could not be added. 324   @return Success, or an error if the signal could not be added.
325   */ 325   */
HITCBC 326   97 [[nodiscard]] std::error_code add(int signal_number) 326   117 [[nodiscard]] std::error_code add(int signal_number)
327   { 327   {
HITCBC 328   97 return add(signal_number, none); 328   117 return add(signal_number, none);
329   } 329   }
330   330  
331   /** Remove a signal from the signal set. 331   /** Remove a signal from the signal set.
332   332  
333   This function removes the specified signal from the set. It has 333   This function removes the specified signal from the set. It has
334   no effect if the signal is not in the set. 334   no effect if the signal is not in the set.
335   335  
336   @param signal_number The signal to be removed from the set. 336   @param signal_number The signal to be removed from the set.
337   337  
338   @return Success, or an error if the signal could not be removed. 338   @return Success, or an error if the signal could not be removed.
339   */ 339   */
340   [[nodiscard]] std::error_code remove(int signal_number); 340   [[nodiscard]] std::error_code remove(int signal_number);
341   341  
342   /** Remove all signals from the signal set. 342   /** Remove all signals from the signal set.
343   343  
344   This function removes all signals from the set. It has no effect 344   This function removes all signals from the set. It has no effect
345   if the set is already empty. 345   if the set is already empty.
346   346  
347   @return Success, or an error if resetting any signal handler fails. 347   @return Success, or an error if resetting any signal handler fails.
348   */ 348   */
349   [[nodiscard]] std::error_code clear(); 349   [[nodiscard]] std::error_code clear();
350   350  
351   protected: 351   protected:
352   explicit signal_set(handle h) noexcept : io_signal_set(std::move(h)) {} 352   explicit signal_set(handle h) noexcept : io_signal_set(std::move(h)) {}
353   353  
354   private: 354   private:
355   void do_cancel() noexcept override; 355   void do_cancel() noexcept override;
356   356  
HITCBC 357   170 implementation& get() const noexcept 357   201 implementation& get() const noexcept
358   { 358   {
HITCBC 359   170 return *static_cast<implementation*>(h_.get()); 359   201 return *static_cast<implementation*>(h_.get());
360   } 360   }
361   }; 361   };
362   362  
363   } // namespace boost::corosio 363   } // namespace boost::corosio
364   364  
365   #endif 365   #endif