include/boost/corosio/signal_set.hpp

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