include/boost/corosio/signal_set.hpp

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