include/boost/corosio/signal_set.hpp

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