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