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